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.
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
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
I would like to ask a question about a good example of how to define a model with many aspects in swift, especially when the project gets bigger and bigger and one model has many aspects. The questions is pretty long but I just would like to know how ppl design a model in a big project. Any comments or thought would be appreciated.
Let's say there is a model called "Book" and it is defined like below:
class Book {
var id: String
var title: String
var author: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
Book has a failable initialiser that parses properties from JSON sent from the server.
On view controller A, it describes the more detailed information about the mode Book, so some properties are added to the model when it is used on view controller r A:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
On another view controller B, we want to show the history of book purchase. So the model needs additional properties as below:
class Book {
var id: String
var title: String
var author: String
// required on View Controller A
var price: Int
var seriersName: String
var reviewNumber: Int
var detailedDescription: String
// required on View Controller B (Not required on VC A)
var purchasedDate: NSDate
var expireDate: NSDate
init?(json: [String: AnyObject]) {
// parse the model from JSON
}
}
This definition of Book lacks flexibility because the JSON passed to the failabel initialiser must have all of the properties even on an VC that uses only some of the properties.
Solution A:
I think the simplest solution for this is just declaring those additional properties as optional, but I personally think this is not so cool because whenever those optional properties are used they need to be checked if they are not nil.
if let seriesName = book.seriesName {
self.seriesNameLable.title = seriesName
}
This kind of optional binding code will be overflowed all over the codes I assume. Implicit optional binding might be able to used but that is not really safe to use.
Solution B:
Another solution might be to define different models that inherits Book, like BookA and BookB. But what if we need a model that has BookA and BookB's aspects at the same time?
I guess there is no single solution for this kind of problem but I would like to know how other ppl define a model in a big project. (I wonder if someone would have a cool solution using some "swift specific" features like protocol and its extension :). I would appreciate any kind of opinions... Thank you.
Disclaimer: I'm not a Swift programmer, this are extrapolations I make from other languages with the same features and my Swift syntax might not be 100% accurate
Using protocols I would do something like:
class EBook: Book, HasOnlineSource {
...
}
class OtherKindOfBook: Book, WithCollectorEditions, HasBleh, WithFoo {...}
But you must ask yourself:
Do I need this to change dinamically?
If that is the case, you would need to go with Delegation through composition.
Are different parts of the application using the core models differently?
Or in other words, are there different users of those models needing different behavior? In that case, extensions are very useful since allow to expose different behaviors for the same model depending on their context. For instance, the Reporting module can send the message numberOfReaders while the Selling module could ask for promotionalCodes. Both are using the same model, but interacting with different protocols. In your case, you have different controllers wanting different things, so this might apply.
Using delegates
This follows the Composition over inheritance principle, but after reviewing how delegates work in Swift, I understood that they are not a native implementation but still a design pattern (a feature request you might say), the delegation is being made by hand.
Other languages allow you to make a JSSONSerializableBook with a BookProtocol, but instead of implementing what is required on BookProtocol you can set a delegate upon initialization which will implement such protocol. This delegate will be an internal collaborator of JSSONSerializableBook and all messages that are part of BookProtocol received by JSSONSerializableBook will be delegated to it.
Or in other words, the message passing is handled automatically (here is the Kotlin documentatin on delegates if you want to check how other language implements it).
If you want to do the same on Swift, you must explicitly make the message passing to your delegate.
This design has several advantages but since there is no native support for message passing it becomes very verbose to implement.
For further reference you can check the papers on mixins and traits to have some insight on the design decisions behind this features.
This is why Swift has optionals.
If not all Books have, say, a purchasedDate then that should be an optional and then your initialiser doesn't need to fail if that field isn't present in the JSON.
It is your view controllers that will enforce the business logic - so if ViewControllerB gets a Book without a purchasedDate it should generate some sort of error.
It also may be that your base class needs to be decomposed into smaller objects.
For example, you could probably have a PurchaseEvent that is associated with a Book rather than storing that data in the Book itself.
Think of the characteristics of a book in the real world; it has the basic properties you first listed and may even have a price (on the back cover) but it doesn't tell you when it was sold and for how much.
However, #Logain's idea of using Protocols is also good, although it may be tricky when it comes to keeping the server JSON side in sync with the app code.
First of all, if you have a view controller responsible for presenting “more detailed information” about a book, then let's call it BookDetailsViewController, instead of the meaningless “View Controller A”. Similarly we should talk about BookHistoryViewController, not “View Controller B”.
If you want to treat the “more detailed information” as all-or-nothing, then encapsulate it as such, in a Details object that the Book object optionally reference. Same with the history. So here's a model:
class Book {
let id: String
let title: String
let author: String
// These can't be lets because each requires self to initialize.
private(set) var details: Details?
private(set) var history: History?
init?(json: [String: AnyObject]) {
guard let
id = json["id"] as? String,
title = json["title"] as? String,
author = json["author"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.id = ""
self.title = ""
self.author = ""
return nil
}
self.id = id
self.title = title
self.author = author
self.details = Details(book: self, json: json)
self.history = History(book: self, json: json)
}
class Details {
unowned let book: Book
let price: Int
let seriesName: String
let reviewNumber: Int
let detailedDescription: String
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
price = json["price"] as? Int,
seriesName = json["seriesName"] as? String,
reviewNumber = json["reviewNumber"] as? Int,
detailedDescription = json["detailedDescription"] as? String
else {
// These assignments will be unnecessary in the next version of Swift.
self.price = 0
self.seriesName = ""
self.reviewNumber = 0
self.detailedDescription = ""
return nil
}
self.price = price
self.seriesName = seriesName
self.reviewNumber = reviewNumber
self.detailedDescription = detailedDescription
}
}
class History {
unowned let book: Book
let purchasedDate: NSDate
let expireDate: NSDate
init?(book: Book, json: [String: AnyObject]) {
self.book = book
guard let
purchasedDate = (json["purchasedDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) }),
expireDate = (json["expireDate"] as? Double).map({ NSDate(timeIntervalSince1970: $0) })
else {
// These assignments will be unnecessary in the next version of Swift.
self.purchasedDate = NSDate()
self.expireDate = NSDate()
return nil
}
self.purchasedDate = purchasedDate
self.expireDate = expireDate
}
}
}
Given this model, you can allow the user to ask for a BookDetailsViewController or a BookHistoryViewController only when the appropriate property isn't nil.
class BookViewController: UIViewController {
#IBOutlet var detailsButton: UIButton!
#IBOutlet var historyButton: UIButton!
var book: Book!
override func viewDidLoad() {
super.viewDidLoad()
detailsButton.hidden = book.details == nil
historyButton.hidden = book.history == nil
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
switch segue.identifier {
case .Some("details"): prepareForDetailsSegue(segue)
case .Some("history"): prepareForHistorySegue(segue)
case let other: fatalError("unknown segue identifier \(other)")
}
}
private func prepareForDetailsSegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookDetailsViewController
destination.details = book.details!
}
private func prepareForHistorySegue(segue: UIStoryboardSegue) {
let destination = segue.destinationViewController as! BookHistoryViewController
destination.history = book.history!
}
}
class BookDetailsViewController: UIViewController {
var details: Book.Details!
// etc.
}
class BookHistoryViewController: UIViewController {
var history: Book.History!
// etc.
}
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.
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.