Search code examples
swiftautomatic-ref-countingweak-referencesswift-playground

ARC weak var Swift (not closures)


I am trying to better understand ARC and am using Apples Documentation

Going through the first example I do not get the expected result that Apple states; "Because a weak reference does not keep a strong hold on the instance it refers to, it’s possible for that instance to be deallocated while the weak reference is still referring to it. Therefore, ARC automatically sets a weak reference to nil when the instance that it refers to is deallocated."

Im using a playground in XCode 8.3.2

import UIKit

class Person {
    let name: String
    init(name: String) { self.name = name }
    var apartment: Apartment?
    deinit { print("\(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    init(unit: String) { self.unit = unit }
    weak var tenant: Person?
    deinit { print("Apartment \(unit) is being deinitialized") }
}


var john: Person?
var unit4A: Apartment?

john = Person(name: "John Appleseed")
unit4A = Apartment(unit: "4A")

john!.apartment = unit4A
unit4A!.tenant = john

john = nil //This prints "John Appleseed is being deinitialized" (as expected)
unit4a?.tenant?.name //This shows "John Appleseed" (expected nil)
unit4a = nil //Prints "Unit4a is being deinitialized" (as expected)

I understand that this prevents the strong reference cycle so that both can be deinitialized but I'm not understanding why unit4a keeps a reference to the tenant?


Solution

  • You're getting deceived by the playgrounds' output on the right which is not in the same time order as your prints (as in the deinits). If you replace your standalone "echoes" with actual print calls, you'll see that in the console, the tenant gets released after you print unit4a?.tenant?.name and not before. This is because the release of weak objects doesn't happen instantly, but on the next run loop. import UIKit import XCPlayground

    XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
    
    class Person {
        let name: String
        init(name: String) { self.name = name }
        var apartment: Apartment?
        deinit { print("\(name) is being deinitialized") }
    }
    
    class Apartment {
        let unit: String
        init(unit: String) { self.unit = unit }
        weak var tenant: Person?
        deinit { print("Apartment \(unit) is being deinitialized") }
    }
    
    
    var john: Person?
    var unit4a: Apartment?
    
    john = Person(name: "John Appleseed")
    unit4a = Apartment(unit: "4A")
    
    john!.apartment = unit4a
    unit4a!.tenant = john
    
    john = nil
    print(unit4a?.tenant?.name)
    unit4a = nil
    

    Output:

    Optional("John Appleseed")
    John Appleseed is being deinitialized
    Apartment 4A is being deinitialized
    

    If however you change the last bits to:

    john = nil
    
    DispatchQueue.main.asyncAfter(deadline: .now()) {
        print(unit4a?.tenant?.name) //This shows "John Appleseed" (expected nil)
        unit4a = nil //Prints "Unit4a is being deinitialized" (as expected)
    }
    

    The output is what you would expect:

    John Appleseed is being deinitialized
    nil
    Apartment 4A is being deinitialized
    

    In most cases you wouldn't care that the object is released right away, but if you do, check out autorelease pools