Search code examples
swiftreferencenullweak

What's different between ? and ! in weak, strong reference in Swift


I'm beginner in Swift. I have some questions need to resolve but I can't do it by myself.

Here is some problem for me:

class Author {
    weak var book: Book?        
    deinit {
        print("Dealloc Author")
    }
}

class Book {
    var author: Author?        
    deinit {
        print("Dealloc Book")
    }
}

var authorObj:Author? = Author()
authorObj!.book = Book()
authorObj!.book!.author = authorObj

This compiles fine:

class Author {
    weak var book: Book?        
    deinit {
        print("Dealloc Author")
    }
}

class Book {
    var author: Author?        
    deinit {
        print("Dealloc Book")
    }
}

var authorObj:Author? = Author()
authorObj!.book = Book()
authorObj!.book?.author = authorObj

authorObj = nil
  • So can you guys explain for me, what's different between ? and ! in authorObj!.book?.author = authorObj and authorObj!.book!.author = authorObj?

I have two more questions:

  • authorObj is a strong reference same as authorObj.book.author, it's strong reference too? Because it dont have weak or unowned before var.

  • Only authorObj.book is weak reference. But when I assign authorObj to nil, all are deinited. Why? I assign only authorObj to nil but Author() instance still have 1 strong reference authorObj.book.author


Solution

  • So can you guys explain for me, what's different between ? and ! in authorObj!.book?.author = authorObj and authorObj!.book!.author = authorObj?

    When you use ? to unwrap an optional it is referred to as optional chaining. If the optional is nil, the result of the entire chain will be nil. The advantage of using ? is that your app won't crash if the value being unwrapped is nil.

    So:

    authorObj!.book?.author = authorObj
    

    will crash if authorObj is nil (because of the forced unwrap !).

    and:

    authorObj!.book!.author = authorObj
    

    will crash if either authorObj or book is nil.

    The safe way to write this would be:

    authorObj?.book?.author = authorObj
    

    If authorObj or book is nil, this will do nothing and it won't crash.

    authorObj is a strong reference same as authorObj.book.author, it's strong reference too? Because it dont have weak or unowned before var.

    It only makes sense to talk about a single variable when talking about weak vs. strong. It doesn't make sense to ask if authorObj.book is weak; you can say that Author holds a weak reference to book.

    Only authorObj.book is weak reference. But when I assign authorObj to nil, all are deinited. Why? I assign only authorObj to nil but Author() instance still have 1 strong reference authorObj.book.author

    When you assign nil to authorObj, that was the last strong reference to authorObj, so Automatic Reference Counting (ARC) decrements the reference counter and then frees all of the references inside of authorObj. If those are strong references, it decrements the reference count and if that was the last reference to that object, the object is freed as well. If any other object is holding a weak reference to any object that is freed, then ARC will set that value to nil in all of the weak pointers.


    To test this in a playground, put your commands inside a function called test and add print statements so that you can see when things happen.

    class Author {
        weak var book: Book?
    
        deinit {
            print("Dealloc Author")
        }
    }
    
    class Book {
        var author: Author?
    
        deinit {
            print("Dealloc Book")
        }
    }
    
    func test() {
        print("one")
        var authorObj: Author? = Author()
        print("two")
        authorObj!.book = Book()
        print("three")
        authorObj!.book?.author = authorObj
        print("four")
    }
    
    test()
    

    Output:

    one
    two
    Dealloc Book
    three
    four
    Dealloc Author
    

    The thing to note is that the Book is deallocated before step three. Why? Because there are no strong pointers to it. You allocated it and then assigned the only reference to it to a weak pointer inside of Author, so ARC immediately freed it.

    That explains why authorObj!.book!.author = authorObj crashes, because authorObj!.book is nil since the Book which was just assigned to it has been freed.


    Now, try assigning Book() to a local variable book:

    func test() {
        print("one")
        var authorObj: Author? = Author()
        print("two")
        let book = Book()
        authorObj!.book = book
        print("three")
        authorObj!.book?.author = authorObj
        print("four")
        authorObj = nil
        print("five")
    }
    
    test()
    

    This time, the output is quite different:

    one
    two
    three
    four
    five
    Dealloc Book
    Dealloc Author
    

    Now, the local variable book holds a strong reference to the Book that was allocated, so it doesn't get immediately freed.

    Note, even though we assigned nil to authorObj in step four, it wasn't deallocated until after book was deallocated after step five.

    The local variable book holds a strong reference to Book(), and Book holds a strong reference to Author, so when we assign nil to authorObj in step four, the authorObj can't be freed because book still holds a strong reference to it. When test ends, the local variable book is freed, so the strong reference to authorObj is freed, and finally authorObj can be deallocated since the last strong reference to it is gone.