Working through Apple's Swift Programming Guide I came across this example in the explanation of ARC;
class Person {
let name: String
init(name: String) {
self.name = name
println("\(name) is being initialized")
}
deinit {
println("\(name) is being deinitialized")
}
}
var reference1: Person?
var reference2: Person?
var reference3: Person?
I understand the idea that because the variables are of the option type they are initialized with a value of nil and do not reference a Person instance. So the following makes sense to me;
reference1 = Person(name: "John Appleseed")
However I was experimenting and was surprised to see I could also do this;
reference1 = Person(name: "Johnny Appleseed")
I was expecting the code to error since I was trying to change the constant "name" property. Not only can I change this property I also got the message "Johnny Appleseed is being initialized". How can I be initializing a constant twice?
You're not actually changing the name property of your existing Person instance.
What you're doing is creating a new Person, and giving him the name "Johnny". Your old Person with the name "John" will be deallocated automatically:
reference1 = Person(name: "John Appleseed")
reference1 = Person(name: "Johnny Appleseed") // "John" is now gone.
Unless you have some other variable pointing to "John", that instance will be deallocated.
This would cause a compilation error:
reference1 = Person(name: "John Appleseed")
reference1.name = "Johnny Appleseed"
Because you'd be trying to change the value of a property defined using let.
By calling Person(name: "Johnny Appleseed") you creating a new Person object that replaces the old Person object referenced by reference1. The constant property name of the old Person object is not changed at all, thus no error is issued.
Related
If I want to update existing class object is there any easier way than duplicating init to update method? (my Person object has about ten properties which are defined firstly or can be edited at later app usage)
problems listed in example below:
person1 reinitialization does not update reference to a person1 object in a parent-child array - child array does not contain updated object
person2 object behaves as wanted (child array contains updated object) but uses duplicated initializer code in update method to make “edit in place” which looks really bad in terms of DRY
any hints how to do it Swiftly?
import UIKit
class Person {
var name: String
var surname: String
var age: Int?
init(name: String, surname: String, age: Int? = nil) {
self.name = name
self.surname = surname
self.age = age
}
func update(name: String, surname: String, age: Int? = nil) {
self.name = name
self.surname = surname
self.age = age
}
}
class Parent {
var child = [Person]()
}
var parent = Parent()
var person1 = Person(name: "John", surname: "Doe")
print(person1.name)
parent.child.append(person1)
person1 = Person(name: "Jack", surname: "Doe")
print(person1.name)
print(parent.child.first!.name)
var person2 = Person(name: "Tom", surname: "Cruise")
print(person2.name)
parent.child.append(person2)
person2.update(name: "Jim", surname: "Cruise")
print(person2.name)
print(parent.child.last!.name)
to make it more clear think about the array of user profiles where you can add a new profile or edit an existing one. and above problem is about editing existing user profile
Here is the explanation for both of your queries,
1. parent child array contains the reference to the person1 object, i.e. person1 and parent child array store the reference to same object.
var person1 = Person(name: "John", surname: "Doe")
parent.child.append(person1)
Now, you're assigning a new object to person1 instead of making changes to the same object, i.e.
person1 = Person(name: "Jack", surname: "Doe")
This won't reflect in the parent child array. It will still contain the reference to the previous Person object.
This is the reason you are getting different values of name in person1 and parent child array
print(person1.name) //Jack
print(parent.child.first!.name) //John
2. Instead of adding a separate method for updating the object - update(name:surname:age:), you can simply update the values individually using the . operator on object's properties, i.e
var person2 = Person(name: "Tom", surname: "Cruise")
parent.child.append(person2)
person2.name = "Jim"
Since in the above code, instead of assigning a new Person object to person2, we're just updating its values.
So, person2 and parent child array's object will still reference the same object and hence will print the same name values.
print(person2.name) //Tom
print(parent.child.last!.name) //Tom
using unowned to reference to an object:
when the object is deallocated, the other object that has a reference was marked unowned, that object is also deallocated at the same time. But what about that object also contains other reference to other object without marking unowned?
class Base {
let str: String
init(str: String){
self.str = str
}
deinit {
print("leaving Base class with string, \(str).")
}
}
class Country {
let name: String
var capitalCity: City!
init(name: String, capitalName: String) {
self.name = name
self.capitalCity = City(name: capitalName, country: self)
}
deinit {
print("Leaving Country class")
}
}
class City {
let name: String
let baseClass: Base = Base(str: "ABC") // reference to other object.
unowned let country: Country
init(name: String, country: Country) {
self.name = name
self.country = country
}
deinit {
print("Leaving City class")
}
}
var hk: Country? = Country(name: "Hong Kong", capitalName: "Central")
let central = City(name: "Central", country: hk!)
let base = central.baseClass
print("here")
hk = nil
print("here")
print(base.str)
print("here")
//print:
//here
//Leaving Country class
//Leaving City class
//leaving Base class with string, ABC.
//here
//ABC
//here
this object with two properties that points to two different objects. one property was marked unowned. the other property was not marked unowned. So, I thought it would have made a strong reference due to not mark unowned for one of the properties. but this object was still deallocated from my test code. the strange thing is after deallocation, i can still access the property. why? and how unowned works? how should i mark it?
class Country {
...
init(name: String, capitalName: String) {
...
self.capitalCity = City(name: capitalName, country: self) // <-- o_O
The "leaving Base class" you see is not related to central.
In your Contry's constructor you have created a new instance of City, and thus a new Base will also be created. So what is destroyed is just hk!.capitalCity.baseClass which is unrelated to central.baseClass.
These have nothing to do with unowned.
Let's look at these lines of code:
var hk: Country? = Country(name: "Hong Kong", capitalName: "Central")
let central = City(name: "Central", country: hk!)
let base = central.baseClass
print("here")
hk = nil
print("here")
print(base.str)
print("here")
After the first line, three objects are created: Country, City and Base. Here are the references:
1. Country -> City: strong
2. City -> Country: weak
3. City -> Base: strong
After the second line, another two objects are created: another City and another Base. References:
4. second City -> Country: weak
5. second City -> second Base: strong
All right, now we deallocate hk! Which references will point to nothing? It's 2 and 4. This means that we can ignore them. Which reference will be destroyed? It's reference 1!
Now reference 1 is destroyed, there are no strong reference pointing to the first City you created! Hence, the first city will be deallocated and print the message.
After the first city is deallocated, there are no strong reference pointing to the first Base you created, and hence it is deallocated as well, printing the message.
What you are accessing in the second to last line of code, is the second City!
Here's a picture:
Dotted lines represent weak references. Non-dotted lines represent strong references.
"The second city also has no strong reference attached! Why isn't it deallocated?" you asked. Well, that is because it is stored in a local variable called central!
PS Hong Kong is not a country. And Central is not its capital. Central is just a busy district in Hong Kong.
I'm trying to understand why the following is valid in Swift. I'm assuming it has to do with the way things are scoped in Swift.
let name = "test" //assigns "test" to name
var optionalName: String? = "John Appleseed"
var greeting = "Hello!"
if let name = optionalName { //assigns "John Appleseed" to name
let name = "no error" //assigns "no error" to name
greeting = "Hello, \(name)" //assigns "Hello, no error" to greeting
}
println(name) //prints "test"
What I believe is happening is this is creating 3 separate name constants all in different scopes. The first let name is in the global scope. Then the optional binding let name is another scope, and then within the if the let name is another scope. Then the final print goes back out to the global scope.
You have already understood:)
It uses the first variable it finds with such name, starting from the inner scope and going up.
In swift, I have the following code:
struct Person {
var name: String
var nose: Int = 1
}
var mike = Person(name: "mike", nose: 1)
var john = Person(name: "john") //error: missing argument for parameter 'nose' in call
Although nose has a default value, I have to specify a value for it. My question is, does default value of nose become totally useless and I can simply write the declaration as the following?
struct Person {
var name: String
var nose: Int
}
Thanks!
From The Swift Programming Language
Structure types automatically receive a memberwise initializer if they
do not define any of their own custom initializers. This is true even
if the structure’s stored properties do not have default values.
The memberwise initializer is a shorthand way to initialize the member
properties of new structure instances. Initial values for the
properties of the new instance can be passed to the memberwise
initializer by name.
As you haven't declared any initialisers for your struct, the only initialiser that is available is the memberwise initialiser.
You can declare an initialiser that just takes a name in order to use the default value for nose. This then requires you to explicitly declare the memberwise initialiser -
struct Person {
var name: String
var nose: Int = 1
init(_ name:String) {
self.name=name;
}
init (name:String, nose:Int){
self.name=name;
self.nose=nose;
}
}
var mike = Person(name: "mike", nose: 1)
var john = Person("john")
I like this form because it is so flexible:
struct Person {
var name: String
var nose: Int
init(name: String = "Anon", nose: Int = 1) {
self.name=name;
self.nose=nose;
}
}
var sally = Person(nose: 2, name: "Sally")
var anon1 = Person(nose: 1)
var anon2 = Person()
var mike = Person(name: "mike", nose: 1)
var john = Person(name: "john")
Note 5 different initialiser combinations possible. Essentially it allows you to treat Person like a person builder (Builder pattern).
I created this class for my object City
class City: NSObject {
var _name:String = ""
var name:String {
get {
return _name
}
set (newVal) {
_name = newVal
}
}
}
then when I create my object I do:
var city:City!
city.name = "London" //crash here
println("name city is\(city.name)");
it crash when I set the name with message "fatal error: unexpectedly found nil while unwrapping an Optional value"
This is not actually an answer (see other answers for a solution, such as #Greg's and #zelib's), but an attempt to fix some mistakes I see in your code
No need to create computed + stored property (unless you have a reason for that):
class City: NSObject {
var name: String = ""
}
If you inherit from NSObject, you automatically lose all swift features - avoid it (unless you have a reason for that)
class City {
var name: String = ""
}
You are using an empty string as absence of value - swift provides optionals for that
class City {
var name: String?
}
Alternative to 3., a city without a name wouldn't make much sense, so you probably want each instance to have a name. Use non optional property and an initializer:
class City {
var name: String
init(name: String) {
self.name = name
}
}
Avoid implicitly unwrapped optionals (unless you have a reason for that):
var city: City
Just like any other object oriented programming language, and object should be initialized before accessing it.
Like:
var city:City
This is just reference of the object. So, actual memory is not created here. You need to create actual object for City Class.
Fix it by adding following statement:
city = City()
You haven't initialised the city variable and when you trying to use it it crash.
initialise it first before you use it:
city = City()
city.name = "London"
You are getting error because you are not initializing your city variable instead you just implicitly unwrap it without initializing at any stage. To initialize it you must use the following code
var city:City = City()
You have to call the init method. So you would do it like this :
var city:City=City() //Calls init and creates an instance
city.name="foo"
If you don't define an init method(it's always a good practice that you do), the default init method is called.