In an array containing objects, changing one item's property doesn't change the object itself - swift

This will not happen if I just change the properties instead of replacing the reference to a new object.
Here is a class Person, which is a reference type,
class Person {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
}
Here is an instance of Person,
var someone = Person(firstName: "Johnny", lastName: "Appleseed")
then I make an array containing values of type Person
var lotsOfPeople = [someone, someone, someone]
I suppose lotsOfPeople containing 3 references to someone.
But if I change the third value in lotsOfPeople,
lotsOfPeople[2] = Person(firstName: "Lucy", lastName: "Swift")
someone itself isn't changed.
print(someone.firstName) // Johnny
I think it means lotsOfPeople[2] is not a reference to someone.
How could this happen?

The problem is that you are replacing the reference at lotsOfPeople[2] to point to a new object. That is why the original Person is not changed.
If you did lotsOfPeople[2].firstName = "Lucy" then it would change.
Or do:
let person = lotsOfPeople[2]
person.firstName = "Lucy"
then you would also see the original change.

Related

Is there any way to make the method return a mutable value?

as shown in the code below:
struct Person {
var name: String
}
struct Group {
var person: Person
func callAsFunction() -> Person {
// Person is immutable value
person
}
}
var james = Person(name: "James")
var group = Group(person: james)
group().name = "Wong" //ERROR: Cannot assign to property: function call returns immutable value
group() return an immutable value, that can't be changed! So Is there any way to make the callAsFunction() method return a mutable value?
Thanks ;)
Updated:
My idea is to transfer all the calls and visits of the Group to the Person object in the Group, just like using Person directly.
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
My needs are a bit like proxy objects in the Ruby language. Accessing an object (Group) actually accesses another object (Person) inside it, as if the Group does not exist.
ruby proxy patterns:
https://refactoring.guru/design-patterns/proxy/ruby/example
CallAsFunction is the closest implementation so far, but requires that Person cannot be a Struct, otherwise it cannot be assigned to its properties.
Maybe it's not possible to implement this feature in Swift yet?
You're using the wrong dynamic method. What you want is dynamicMemberLookup. Watch closely. First, the preparation:
struct Person {
var name: String
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember kp:WritableKeyPath<Person,String>) -> String {
get { self.person[keyPath:kp] }
set { self.person[keyPath:kp] = newValue }
}
}
Now look at what that allows you to say:
var group = Group(person: Person(name: "James"))
group.name = "Wong"
print(group.person) // Person(name: "Wong")
Do you see? We set the name of the Group even though it has no name property, and the result was that we set the name of the Group's person which does have a name property.
The callAsFunction simply returns (a copy of the) Person, which is a value type. You cannot then mutate the property of it like that. It is equivalent to the following:
struct Person {
var name: String
}
Person(name: "Foo").name = "Bar"
That returns the same error:
If Person was a reference type, it would have worked, but not for a value type. And even if you took your value type, and first assigned it to a variable before mutating it, you would only be mutating your copy, not the original.
If you want the behavior you want, you would use a #dynamicMemberLookup as suggested by matt (+1) and outlined in SE-0195.
You said:
I can't use dynamicMemberLookup because I don't know what method or property there will be in Person. For example, there may be 100 methods and properties in Person (not only one name property as demonstrated), and it is impossible for me to write 100 subscript methods with dynamicMemberLookup.
You do not need “100 subscript methods.” It is the motivating idea behind #dynamicMemberLookup, namely that the properties will be determined dynamically. E.g., here is Person with two properties, but Group only has the one #dynamicMemberLookup.
struct Person {
var name: String
var city: String
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript(dynamicMember keyPath: WritableKeyPath<Person, String>) -> String {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}
var group = Group(person: Person(name: "James", city: "New York"))
group.name = "Wong"
group.city = "Los Angeles"
print(group.person) // Person(name: "Wong", city: "Los Angeles")
If you want to handle different types, make it generic:
struct Person {
var name: String
var city: String
var age: Int
}
#dynamicMemberLookup
struct Group {
var person: Person
subscript<T>(dynamicMember keyPath: WritableKeyPath<Person, T>) -> T {
get { person[keyPath: keyPath] }
set { person[keyPath: keyPath] = newValue }
}
}
And
var group = Group(person: Person(name: "James", city: "New York", age: 41))
group.name = "Wong"
group.city = "Los Angeles"
group.age = 42
print(group.person) // Person(name: "Wong", city: "Los Angeles", age: 42)

swift 3.1.1 ERROR extra argument '{property}' in call

I was testing out inheritance in Swift 3 and ran into an unexpected error. No matter how I alter the super.init() call, I can't make the error trace less problematic than this.
I have read through the documentation and other similar posts here on SO, but they don't have any examples of this particular problem. I am trying to create a class with properties that are either variable or constant and then creating another class that inherits from the first class and adds new properties.
The error comes from my subclass's init function: I set the new properties first, then call super.init() with the superclass's appropriate arguments. Swift then tells me that the third parameter "birthday" is an "extra argument."
Is there some sort of problem between my superclass having three properties and my subclass having two? I can't think of any other problem that could throw an error like this. Not sure which part is confusing me.
Here's my code:
class Person {
var name: String
var age: Int
let birthday: String
init(name: String, age: Int, birthday: String) {
self.name = name
self.age = age
self.birthday = birthday
}
}
class Student: Person {
var isEnrolled: Bool
var numberOfClasses: Int
init(isEnrolled: Bool, numberOfClasses: Int) {
self.isEnrolled = isEnrolled
self.numberOfClasses = numberOfClasses
super.init(name: name, age: age, birthday: birthday) {
self.name = name
self.age = age
self.birthday = birthday
}
}
}
Your initializer does not have a closure, so naturally, your approach cannot work. It says birthday is an extra argument, because the (non-existent) closure is the fourth argument in the list.
Student's initializer does not know what name, age or birthday is. You should include those parameters in the initializer as well and call super.init at the end.
init(name: String, age: Int, birthday: String, isEnrolled: Bool, numberOfClasses: Int) {
self.isEnrolled = isEnrolled
self.numberOfClasses = numberOfClasses
super.init(name: name, age: age, birthday: birthday)
}

How can I create a optional value from a my class

I create a class User, and I want to create an optional out of it, compiler then fires an error
class User {
var firstName: String = ""
var lastName: String = ""
}
var Tom = User?(firstName: "Tom", lastName: "Soya")
error: cannot invoke initializer for type 'User?' with an argument list of type '(firstName: String, lastName: String)'
Question: What's wrong with it? Do I need to put more stuff inside class before I can create an optional value? If so what is it?
Thanks
The problem is that your User class has no initializers. Thus, there is only one way to initialize it: namely, by saying User(). So if you are going to define User like this:
class User {
var firstName: String = ""
var lastName: String = ""
}
Then the best you can do is this:
var tom : User? = User()
tom?.firstName = "Tom"
tom?.lastName = "Soya"
#matt's answer is correct, but here's another way, which I think is clearer:
var tom = Optional(User())
See Optional.init(_:).

Why I can change/reassigned a constant value that Instantiated from a class

I've created the following class
class Person {
var firstName: String
var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
func fullName() -> String {
return "\(firstName) \(lastName)"
}
}
Then I instantiated a constant value from the class
let john = Person(firstName: "Johnny", lastName: "Applessed")
Question: Why I can change the content of the variable john? Isn't it a constant? Can someone explain that for me, thanks a lot.
john.firstName = "John"
print(john.firstName) // -> John
As #Wain has said – it's due to the nature of reference types. The instance being a let constant only means you cannot assign a new reference to it – but says nothing about the actual mutability of the instance itself.
If you change your class to a struct, you'll see how the behaviour differs with value types, as changing a property changes the actual value of your Person – therefore you are unable to do so if it's a let constant. However I somewhat doubt you'll want to make your Person a struct, as two people with the same name shouldn't be considered to be the same person.
If you only wish your properties to be assigned upon initialisation (and then read-only for the lifetime of the instance), then I would recommend making them let constants (instead of making their setters private). This will ensure that you cannot even change their value from within your class, once assigned.
The rule is as long you give a property a value before the super.init() call – you can make it a let constant (in this case, you just have to assign them in the initialiser before using self).
class Person {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
...
The class instance itself is a constant, so you can't change it to reference another instance, but the instance is mutable because it's properties are created as vars.
Change firstName to have a private setter and see what you can do:
private(set) var firstName: String
When you're using a constant instance of a class in swift, doesn't mean you can't change the class attributes. It' means you can't instantiate a new object in this constant
let person = Person(firstName: "Johnny", lastName: "Appleseed")
person = Person(firstName: "John", lastName: "Appleseed") //--->It gets error: Cannor assign to value: 'person' is a 'let' constant
But you can create a constant inside class and set this values in the init
class Person {
let firstName: String
let lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
func fullName() -> String {
return "\(firstName) \(lastName)"
}
}
//Tip: Don't init the class constants in declaration time or will get the same above error. Just init this constants at constructor/initialization of class.
And Now you have the expected result you want, even if create a 'var' instance of this object
var person = Person(firstName: "Johnny", lastName: "Appleseed")
person.firstName = "John" //--->It gets error: Cannor assign to value: 'person' is a 'let' constant
person = Person(firstName: "John", lastName: "Snow")
person.firstName = "Johnny" //--->It gets error: Cannor assign to value: 'person' is a 'let' constant
Your thinking was not wrong, but a little confuse cause you would be totally right if it was a struct instead a class.

How to copy a "Dictionary" in Swift?

How to copy a "Dictionary" in Swift?
That is, get another object with same keys/values but different memory address.
Furthermore, how to copy an object in Swift?
Thanks,
A 'Dictionary' is actually a Struct in swift, which is a value type. So copying it is as easy as:
let myDictionary = ...
let copyOfMyDictionary = myDictionary
To copy an object (which is a reference type) has a couple of different answers. If the object adopts the NSCopying protocol, then you can just do:
let myObject = ...
let copyOfMyObject = myObject.copy()
If your object doesn't conform to NSCopying then you may not be able to copy the object. Depending on the object's class it may provide it's own method to get a duplicate copy, or if the object has no internal private state then you could create a new object with the same properties.
[Edited to correct a mistake in the previous answer - NSObject (both the Class and the Protocol) does not provide a copy or copyWithZone method and therefore is insufficient for being able to copy an object]
All of the answers given here are great, but they miss a key point regarding warning you about the caveats of copying.
In Swift, you have either value types (struct, enum, tuple, array, dict etc) or reference types (classes).
If you need to copy a class object, then, you have to implement the methods copyWithZone in your class and then call copy on the object.
But if you need to copy a value type object, for eg. an Array, you can copy it directly by just assigning it to a new variable like so:
let myArray = ...
let copyOfMyArray = myArray
But this is only shallow copying.
If your array contains class objects and you want to make their copy as well, then you have to copy each array element individually. This will allow you to make a deep copy.
This is extra information that I thought would add to the info already presented in the well-written answers above.
Object
class Person: NSObject, NSCopying {
var firstName: String
var lastName: String
var age: Int
init(firstName: String, lastName: String, age: Int) {
self.firstName = firstName
self.lastName = lastName
self.age = age
}
func copyWithZone(zone: NSZone) -> AnyObject {
let copy = Person(firstName: firstName, lastName: lastName, age: age)
return copy
}
}
Usage
let paul = Person(firstName: "Paul", lastName: "Hudson", age: 35)
let sophie = paul.copy() as! Person
sophie.firstName = "Sophie"
sophie.age = 5
print("\(paul.firstName) \(paul.lastName) is \(paul.age)")
print("\(sophie.firstName) \(sophie.lastName) is \(sophie.age)")
Source: https://www.hackingwithswift.com/example-code/system/how-to-copy-objects-in-swift-using-copy