Swift: Struct Array vs Class Array - ios

I have a swift array of struct and I am unable edit the first property, whereas I am able edit the first property with an array of class.
In order to edit the first object of the struct array, I have to do [0] then .first
I know structs are valued by type, class are value by reference. But I don't understand the different behavior. Can someone explain?
class PersonObj {
var name = "Dheearj"
}
struct Person {
var name = "Dheearj"
mutating func update(name: String){
self.name = name
}
}
var array = [Person(),Person()]
array[0].update(name:"dheeraj")
array[0].name = "yuuu"
array.first?.name = "dddddd" <--- "Error Here"
var array1 = [PersonObj(),PersonObj()]
array1.first!.name = "ttt"
print(array1.first?.name ?? "")
print(array.first?.name ?? "")
print(array.count)
Screenshot of the error message:

Mutating a struct stored within some other property behaves as though you've copied out the value, modified it, and overwrote it back into place.
Take this line for example: (I replaced the optional chaining with force unwrapping, for simplicity)
array.first!.name = "dddddd"
It behaves as though you did:
var tmp = array.first!
tmp.name = "dddddd"
array.first = tmp
It's easy to see what that doesn't work. Array.first, is a get-only property (it doesn't have a setter).
The case for classses works because the value stored in the array is a reference to the object, and the reference isn't changing (only the values within the object it refers to, which the array doesn't know or care about).

Related

Modify property inside of a behaviourRelay array in RxSwift

I have an array defined using RxSwift as
public var calendarNDays = BehaviorRelay<[CalendarControlDayModel]>(value: [])
CalendarControlDayModel is a structure as below.
struct CalendarControlDayModel {
var date: String = ""
var day: Int = 0
var name: String = ""
}
Once the calendarNDays is updated with elements at some point of time I want to modify the name property of i-th element in the array.
Like self.calendarNDays.value[i].name = "Nancy". However, I get the compilation error "Cannot assign to property: 'value' is a get-only property".
What is the way to modify a particular property of an element in a behaviour relay array?
As the compiler suggests the value in BehaviorRelay is a read-only property.
Therefore in order to make changes to the array you first need to copy it and use the accept method to reflect the changes.
Similar to
var update = calendarNDays.value
update[i].name = “Nancy”
calendarNDays.accept(update)

What's the difference between : and = in swift

Sorry if the title is rather confusing, but I'm curious to know the difference between these two lines:
var title = String()
var title: String
Is one being initialized and one only be declared? Which is more correct?
For example, if I have a struct should I use one of the other?
So the reason I ask this is because I'm learning about how to grab some JSON from a url and then display it in my app. One of the new ways of doing so is using Decodable. So, I have a struct in a model class like so:
struct Videos: Decodable {
var title = String()
var number_of_views : Int
var thumbnail_image_name: String
var channel: Channel
var duration: Int
}
In another class I have this:
URLSession.shared.dataTask(with: url){(data,response,error) in
if(error != nil){
print(error!)
return
}
guard let data = data else { return }
do{
self.Videos2 = try JSONDecoder().decode([Videos].self, from: data)
//self.collectionView?.reloadData()
}catch let jsonErr{
print(jsonErr)
}
}.resume()
So, should I declare or initialize the variables in my struct? I'm assuming I should just declare them like so:
var title: String?
Would that be the correct syntax in my struct?
UPDATE:
I understand this question was more broad then I originally proposed it to be. I'm sorry about that, but thank you so much for all your great answers that clarified a lot up for me.
The difference is that : defines the type of your variable, whereas = assigns an actual value to the variable.
So:
var title = String()
This calls the initializer of the String type, creating a new String instance. It then assigns this value to title. The type of title is inferred to be String because you're assigning an object of type String to it; however, you could also write this line explicitly as:
var title: String = String()
This would mean you are declaring a title variable of type String, and assigning a new String to it.
var title: String
This simply says you're defining a variable of type String. However, you are not assigning a value to it. You will need to assign something to this variable before you use it, or you will get a compile error (and if this is a property rather than just a variable, you'll need to assign it before you get to the end of your type's init() method, unless it's optional with ? after it, in which case it gets implicitly initialized to nil).
EDIT: For your example, I'd probably declare all the variables using let and :, assuming that your JSON provides values for all of those properties. The initializer generated by Decodable should then set all the properties when you create the object. So, something like:
struct Videos: Decodable {
let title: String
let number_of_views : Int
let thumbnail_image_name: String
let channel: Int
let duration: Int
}
This initializes a value
var title = String()
This declares a value but does not initialize it
var title: String
If you attempt to use the latter, such as print(title), you will get a compiler error stating Variable 'title' used before being initialized
It does not matter whether the value is a class or a struct.
The = operator is the assignment operator, it assigns a value to the object on the left of the =
Typically, class or struct properties are declared but not initialized until the init() is called. A simple class might be
class MyClass {
let myProperty: String
init(aString: String) {
self.myProperty = aString
}
}
Whereas inside the scope of a function you may declare a local variable that only lives inside the scope of the function.
func doSomethingToAString(aString: String) -> String {
let extraString = "Something"
let amendedString = aString + extraString
return amendedString
}
In your specific example, the struct synthesizes an initializer that will allow you to initialize the struct with all the values needed to fill your properties. The initializer generated by Decodable should then set all the properties when you create a Videos struct, you will do it something like:
let aVideos = Videos(title: "My Title", number_of_views: 0, thumbnail_image_name: "ImageName", channel: Channel(), duration: 10)
Is one being initialized and one only be declared?
Yes, meaning that the declared cannot be used. If you tried to set a value for it, you would get a compile-time error:
variable 'title' passed by reference before being initialized
Which is more correct?
There is no rule of thumb to determine which is more correct, that would be depends on is there a need to initialize title directly.
On another hand, when it comes to declare properties for a class, saying var title = String() means that you are give title an initial value ("") which means that you are able to create an instance of this class directly, example:
class Foo {
var title = String()
}
let myFoo = Foo()
However, if title declared as var title: String, you will have to implement the init for Foo:
class Foo {
var title: String
init(title: String) {
self.title = title
}
}
let myFoo = Foo(title: "")
Also, you have an option to declare it as lazy:
lazy var title = String()
which means:
A lazy stored property is a property whose initial value is not
calculated until the first time it is used. You indicate a lazy stored
property by writing the lazy modifier before its declaration.
Properties - Lazy Stored Properties

Swift. Refer to instance variables by string name

I've got a number of user properties in a user viewcontroller class ie
//user vars
var email: String?
var givenName: String?
var familyName:String?
var phone: String?
var dob: NSDate?
In a method within that class i retrieve user data from coredata and set the user text fields with that data in a loop
for i in 0 ..< userTextFields.count {
let field = userTextFields[i]
let fieldName = userTextFieldKeyNames[i]
let fieldText = currentUser.valueForKey(fieldName) as? String
field.text = fieldText
}
the fieldName variable in the loop matches the class's ivars above. Is there a way i can reference the ivars within the loop by matching it with the fieldName string so I can set the values of the ivars with the fetched coredata values ie in a literal sense saying something like the following ...
if self.property.name.text == fieldName {
self.property.value == fieldText
}
ie somehow resolving the various property names withing the class ... or is this bad design? .... if so any suggestions on achieving the same result in a better way
Not Swift-ish, as it's bypassing compile time type checking. Best option is to keep them all in a dictionary, and use a protocol to define allowed data types in the dictionary, but even that is rather poor

I am creating objects with var because I mutate them but I get warning: "Variable 'variableName' was never mutated, consider..."

I am creating at launch Dictionaries with var because I will modify them later when user does something. Dictionaries are added inside an Array in a singleton class to be used in multiple places but I get the warning "Variable 'variableName' was never mutated, consider...."
in the place I am creating them
If I make them with let and when I get object form array to modify it if I take it from array with var, no crash, no warning, no nothing...
What is the explanation for this?
UPDATE:
My Singleton Class:
class Config {
static let sharedInstance = Config()
var array_shapes: Array<Dictionary<NSObject,AnyObject>> = Array()
func createInitialShapeArray(){
var avion = createShapeDictionaryFor("Avion", objectName: "Avion", badgeStatus: "0", shapeImageName: "shape_avion");
//.......more objects like avion
array_shapes = [avion,//.....the other objects];
}
func createShapeDictionaryFor(objectID:String, objectName:String, badgeStatus:String, shapeImageName:String) -> Dictionary<NSObject,AnyObject>{
var dict: Dictionary<NSObject,AnyObject> = [:]
dict["objectID"] = objectID
dict["objectName"] = objectName
dict["badgeStatus"] = badgeStatus
dict["shapeImageName"] = shapeImageName
return dict;
}
}
And when I am mutating dictionaries (In main class):
#IBAction func btnPressed_done(sender:UIButton){
pennyPincherGestureRecognizer.recognize();
screenShotMethod()
var dict = Config.sharedInstance.array_shapes[Config.sharedInstance.currentShapeIndex] as Dictionary<NSObject,AnyObject>
dict["badgeStatus"] = "1"
self.initNextShape()
}
var avion has the warning "Variable 'variableName' was never mutated, consider...."
It is not an error trough, it's a warning and I was curious if I could silence them or what can I do to make them dissappear
Facts
You are declaring avion as a local variable of the method createInitialShapeArray
You are not mutating avion in the scope where it is defined
avion is a Dictionary therefore a Struct (value type rules are applied)
Conclusion
There is no need to declare avion as a variable, it should be a constant.
Please note that where you write
array_shapes = [avion, ...]
you are creating a copy of avion (because it's a Dictionary).
So if you change the value inside array_shapes you are changing another value.
Therefore, at the end of the day, you are not mutating avion... and the compiler is right, it should be a constant.
Example
Please consider the following code
func foo() {
var dict = [1: "One"] // <-- Compiler warning
var anotherDict = dict
anotherDict[2] = "Two"
}
Here I am getting the same compiler warning
Variable 'dict' was never mutated; consider changing to 'let' constant
This happens because I am changing anotherDict that is not just another reference to the same value, it is actually a totally different value. This is the rule with Struct(s) and Enum(s) because they are Value Types.
Hope this helps.
In Swift arrays and dictionaries are declared as struct so when you pass them to other function or use them in assignments their value is copied and not passed as reference the same way it's done for classes, this means that when you pass avion to the append() function of your array you pass a copy of the dictionary so the original variable is never mutated.
The same things happens when you try to modify on dictionary in the array thus copying the dictionary of your interest in dict: you aren't modifying the array inside your shared instance but the local variable dict.

Swift: how to make array of mutable dictionaries? [duplicate]

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