Swift 4 Array get reference - arrays

I ran into an issue with arrays in Swift. The problem is that it's a value type in Swift. I'm trying to find a workaround.
Here is the code that I have:
class Object: Codable{
var name : String?
}
var objects: Array<Object>?
objects = Array<Object>()
if var obj = objects { // <----- Creates a copy of array here
let o = Object()
o.name = "1"
objects?.append(o)
print(obj) //<----- this one is missing "o" object
print(objects)
}
I cannot use NSMutableArray because I have an array inside another codable class.
What's everybody's experience on this one? If somebody can share a solutions for that.

Getting used to arrays as value types isn't too tough really. If i were you my version of the code would just look like this
var objects: Array<Object>?
objects = Array<Object>()
if var unwrappedObjs = objects {
let o = Object()
o.name = "1"
unwrappedObjs.append(o)
objects = unwrappedObjs
}
or alternatively maybe this:
var objects: Array<Object>?
objects = Array<Object>()
if objects != nil {
let o = Object()
o.name = "1"
objects?.append(o)
}
Lastly you could always try making your own "ReferenceArray" class that wraps the array APIs and gives you reference semantics but that seems like overkill. Sooner rather than later, arrays as value types will seem natural to reason about.

bitwit already mentioned this to a point, but I think that your biggest mistake is simply not accepting the new object as the source. Unless it's important to retain the Array<Object>? you should replace it with the Array<Object> one.
var objects: Array<Object>?
objects = Array<Object>()
if var objects = objects { // <----- Creates a copy of array here
let o = Object()
o.name = "1"
objects.append(o) // objects is now the non-optional one
print(objects)
}
If it needs to be in the same scope, use guard:
var objects: Array<Object>?
objects = Array<Object>()
guard var objects = objects else { // <----- Creates a copy of array here
fatalError()
}
let o = Object()
o.name = "1"
objects.append(o) // objects is now the non-optional one
print(objects)
If you absolutely need an array to be referenced, you can make a container class:
public class ReferenceContainer<Element> {
public var element: Element
init(_ element: Element) {
self.element = element
}
}

Related

Problem on RealmSwift: "Invalid array input: more values (1) than properties (0)." while trying to persist an Array of String

I'm trying to persist in a table view cell, the result of a quiz test with questions and I needed the array of answers given (String Array) so I decided to use RealmSwift.
I created this class and of course I created also a RealmString object in the same file to handle the possibility to persist arrays of String in Realm in this way:
class RealmString: Object {
dynamic var stringValue = ""
}
class Test: Object {
#objc dynamic var ID = UUID().uuidString
#objc dynamic var testScore : String = String()
#objc dynamic var testTitle : String = String()
#objc dynamic var testSubTitle : String = String()
#objc dynamic var dateOfExecution: String = String()
#objc dynamic var answersGiven: [String] {
get {
return _backingAnswersGiven.map { $0.stringValue }
}
set {
_backingAnswersGiven.removeAll()
_backingAnswersGiven.append(objectsIn: (newValue.map({ RealmString(value: [$0]) })))
}
}
let _backingAnswersGiven = List<RealmString>()
override static func ignoredProperties() -> [String] {
return ["answersGiven"]
}
override static func primaryKey() -> String? {
return "ID"
}
Now in the view controller:
I have a variable that stores the result (is an Int array that will take ten answers with values from 0 to 5, and these will later be converted to String)
i.e.: [0,2,2,3,4,5,2,1,0,2] -> ["0","2","2","3","4","5","2","1","0","2"]
and when an option is selected in a question the value is set with this function, everything works fine.
public var questionResults: [Int] = []
func setValueToQuestion(questionNumber: Int) {
questionResults[questionNumber] = optionChosen
}
When the test is completed successfully everything is saved in this way:
let test = Test()
test.ID = currentTest?.ID ?? UUID().uuidString
test.testTitle = testTitleLabel.text!
test.testScore = resultNumberLabel.text!
test.testSubTitle = resultLabel.text!
test.dateOfExecution = dateTimeString
test.answersGiven = questionResults.map({String($0)})
DBManager.sharedInstance.addData(object: test)
I tried the code separately also adding breakpoints and everything works in the flow, expect this line:
test.answersGiven = questionResults.map({String($0)})
that raises the error shown in the title: "Invalid array input: more values (1) than properties (0)."
I guess it can be an error of mapping maybe?
This value is then treated in the rest of flow as a simple swift array of String = [String]
There are a few issues which may be leading to that error.
First the RealmString property is not persisted because it needs #objc
dynamic var stringValue = ""
should be
#objc dynamic var stringValue = ""
Secondly, and this is important, Realm does not support primitives in Lists. Well, it kinda does but not very well.
EDIT: Release 10.7 added support for filters/queries as well as aggregate functions on primitives so the below info is no longer completely valid. However, it's still something to be aware of.
See my answer to this question but in a nutshell, you need another class to store the string in - kind of like your RealmString class.
class StringClass: Object {
#objc dynamic var myString = ""
}
and then change the Test object property to use the StringClass property
#objc dynamic var answersGiven = [StringClass]()
and then I see you're trying to use a backed var and computed property but I am not sure why. It may be simpler to use use the var itself
let _backingAnswersGiven = List<RealmString>()
since the List collection already handles what's being computed.
For example, if you set the list you can set it to another list (which wipes out the current list). Or when you get the list let myStringList = test._backingAnswersGiven, gets all of the StringClasses in the list without having to .map over them.

Create an array of Struct arrays?

I have a query that builds a Struct array from some objects. I want to append multiple of these structs into a single array so that i can use it populate a collectionview that is nested inside a collectionview. Im not sure this is the best idea but its all I've got a the moment.
So my Struct is:
struct CollectionStruct {
var name : String
var description : String
var title : String
var image : PFFile
var id: String
}
In my viewcontroller the variable to hold the struct is thus:
var packArray : [CollectionStruct] = []
var partArray : [CollectionStruct] = []
var packId = ""
So below what I'm doing is using an item of the previously created packArray to query the DB and build my partArray. Because i want to do this multiple times and append the partArray into a larger parent array i was thinking of using a for loop like:
var i = 0
for item in self.packArray as [AnyObject] {
self.packId = self.packArray[i].id
BuildArray.buildArrayFromQuery(queryForCollection: "Part", selectedPackID: self.packId, delegateSender: "DownloadPart", completeBlock: { (result) in
self.partArray = result
// append this to an array of partArray????
})
}
I don't even know where to start with this. Is it a bad idea?
essentially what i thought was to call this parent array in cellforitem maybe something like:
innerCell.imageCell.image = parentArray[indexPath.item].partArray[indexPath.item].image
but i could be way off.
----- EDIT -----
so i can use a multidimensional array inside the closure once self.partArray is set:
self.partArray = result
var parentArray = [[self.partArray]]
parentArray.append([self.partArray])
but this is unusable outside the closure. How to declare this multidimensional array before self.partArray is set?
------- EDIT -------
Looking into globals for this at the moment.
class GlobalPartArray {
private init() { }
static let sharedInstance = GlobalPartArray()
var globalPartArray = [CollectionStruct]()
}
class GlobalParentArray {
private init() { }
static let sharedInstance = GlobalParentArray()
var globalParentArray = [[GlobalPartArray]]()
}
then inside closure:
GlobalPartArray.sharedInstance.globalPartArray = result
//can not convert [CollectionStruct] to [GlobalPartArray]
GlobalParentArray.sharedInstance.globalParentArray.append(GlobalPartArray.sharedInstance.globalPartArray)
but it throws an error when trying to convert. I obviously need to convert this data somehow before appending to the GlobalParentArray.

How to make shallow copy of array in swift

I have searched for a while but couldn't find reasonable answer for this. I want to add/remove objects in one array to make effect in 2nd array which points to first array.
class Person
{
var name:String = ""
}
var arr1:[Person] = [Person]()
let p1 = Person()
p1.name = "Umair"
let p2 = Person()
p2.name = "Ali"
arr1.append(p1)
arr1.append(p2)
var arr2 = arr1
print("\(arr1.count)") //"2\n"
print("\(arr2.count)") //"2\n"
arr1.removeFirst()
print("\(arr1.count)") //"1\n"
print("\(arr2.count)") //"2\n"
Why changing arr1 does not affect arr2. Please help me out to accomplish this.
Arrays are value types. When we copy them, each copy is independent of the other. This is a virtue in Swift. What you are trying to do requires references so that effects on one can be seen by others. Try this code. Create a class (reference type) containing your data. Now changes to the container can be seen in the other.
class Person
{
var name: String
init(_ name: String) {
self.name = name
}
}
let p1 = Person("Umair")
let p2 = Person("Ali")
class Container {
var people = [Person]()
init(people: [Person]) {
self.people = people
}
}
let arr1 = Container(people: [p1, p2])
let arr2 = arr1
print(arr1.people)
print(arr2.people)
arr1.people.removeFirst()
print(arr1.people)
print(arr2.people)
Even if you're using Swift, you can still use NSArray.
Per Apple's documentation,
NSArray is an object representing a static ordered collection, for use instead of an Array constant in cases that require reference semantics.
The only downside is you'll have to import Foundation. This isn't a problem if you're creating an iOS or Mac app, as you're depending on it already.

Swift empty array does not have a member named .insert

I am new to Swift.
I am trying to get some data from a webservice and to loop the JSON data to make a simple array.
DataManager.getDataFromEndpoint{ (endpointData) -> Void in
let json = JSON(data: endpointData)
if let programsOnAir = json["data"]["data"]["on_air"].array{
var onAirArray = []
for onAir in programsOnAir {
var eventName = onAir["event_name"].string
var eventCover = onAir["event_cover"].string
var tuple = (name: eventName!, cover: eventCover!)
onAirArray.insert(tuple, atIndex: 1)
}
println(onAirArray)
}
}
I get an error where the member .insert does not exist
BUt if I init the array like this var onAirArray = [name: "something, cover: "somethingelse"] then it works.
I need to work with empty arrays and I need to be them mutable, because I have no idea what I may get from the JSON given by the API endpoint.
What am I doing wrong?
The problem is with this line:
var onAirArray = []
Since you haven't given the array an explicit type, this is creating a new instance of NSArray, which doesn't have a method called insert. Which is why this is probably the exact error message you're receiving.
'NSArray' does not have a member named 'insert'
To fix this, explicitly state the type of your array.
var onAirArray: [(String, String)] = []

Modifying an array of dictionaries in Swift

I’m new to Swift and have been having some troubles figuring out some aspects of Arrays and Dictionaries.
I have an array of dictionaries, for which I have used Type Aliases - e.g.
typealias myDicts = Dictionary<String, Double>
var myArray : [myDicts] = [
["id":0,
"lat”:55.555555,
"lng”:-55.555555,
"distance":0],
["id":1,
"lat": 44.444444,
"lng”:-44.444444,
"distance":0]
]
I then want to iterate through the dictionaries in the array and change the “distance” key value. I did it like this:
for dict:myDicts in myArray {
dict["distance"] = 5
}
Or even specifically making sure 5 is a double with many different approaches including e.g.
for dict:myDicts in myArray {
let numberFive : Double = 5
dict["distance"] = numberFive
}
All my attempts cause an error:
#lvalue $T5' is not identical to '(String, Double)
It seems to be acting as if the Dictionaries inside were immutable “let” rather than “var”. So I randomly tried this:
for (var dict:myDicts) in myArray {
dict["distance"] = 5
}
This removes the error and the key is indeed assigned 5 within the for loop, but this doesn't seem to actually modify the array itself in the long run. What am I doing wrong?
The implicitly declared variable in a for-in loop in Swift is constant by default (let), that's why you can't modify it directly in the loop.
The for-in documentation has this:
for index in 1...5 {
println("\(index) times 5 is \(index * 5)")
}
In the example above, index is a constant whose value is automatically
set at the start of each iteration of the loop. As such, it does not
have to be declared before it is used. It is implicitly declared
simply by its inclusion in the loop declaration, without the need for
a let declaration keyword.
As you've discovered, you can make it a variable by explicitly declaring it with var. However, in this case, you're trying to modify a dictionary which is a struct and, therefore, a value type and it is copied on assignment. When you do dict["distance"] = 5 you're actually modifying a copy of the dictionary and not the original stored in the array.
You can still modify the dictionary in the array, you just have to do it directly by looping over the array by index:
for index in 0..<myArray.count {
myArray[index]["distance"] = 5
}
This way, you're sure to by modifying the original dictionary instead of a copy of it.
That being said, #matt's suggestion to use a custom class is usually the best route to take.
You're not doing anything wrong. That's how Swift works. You have two options:
Use NSMutableDictionary rather than a Swift dictionary.
Use a custom class instead of a dictionary. In a way this is a better solution anyway because it's what you should have been doing all along in a situation where all the dictionaries have the same structure.
The "custom class" I'm talking about would be a mere "value class", a bundle of properties. This was kind of a pain to make in Objective-C, but in Swift it's trivial, so I now do this a lot. The thing is that you can stick the class definition for your custom class anywhere; it doesn't need a file of its own, and of course in Swift you don't have the interface/implementation foo to grapple with, let alone memory management and other stuff. So this is just a few lines of code that you can stick right in with the code you've already got.
Here's an example from my own code:
class Model {
var task : NSURLSessionTask!
var im : UIImage!
var text : String!
var picurl : String!
}
We then have an array of Model and away we go.
So, in your example:
class MyDict : NSObject {
var id = 0.0
var lat = 0.0
var lng = 0.0
var distance = 0.0
}
var myArray = [MyDict]()
let d1 = MyDict()
d1.id = 0
d1.lat = 55.55
d1.lng = -55.55
d1.distance = 0
let d2 = MyDict()
d2.id = 0
d2.lat = 44.44
d2.lng = -44.44
d2.distance = 0
myArray = [d1,d2]
// now we come to the actual heart of the matter
for d in myArray {
d.distance = 5
}
println(myArray[0].distance) // it worked
println(myArray[1].distance) // it worked
Yes, the dictionary retrieved in the loop is immutable, hence you cannot change.
I'm afraid your last attempt just creates a mutable copy of it.
One possible workaround is to use NSMutableDictionary:
typealias myDicts = NSMutableDictionary
Have a class wrapper for the Swift dictionary or array.
class MyDictionary: NSObject {
var data : Dictionary<String,Any>!
init(_ data: Dictionary<String,Any>) {
self.data = data
}}
MyDictionary.data

Resources