Swift Reference Strong Weak Unowned

223 阅读4分钟

Short answer:

  • Strong reference causes object's reference counter increased by one.

  • Weak reference dosen't causes object's reference counter increased by one, and it is nil-able.

  • Unowned reference dosen't causes object's reference counter increased by one, and it is nil-able with implicitly forced unwrapped.

Verbose answer:

In Swift, when we use objects with reference semantics, i.e. instances of class, we don't need to manually manage memory for those objects due to ARC (Automatically Reference Counting). The ARC will automatically delete the memory occupied by object when there is no reference to that object. The logic is quite straightforward. 

Since reference is the way to access an object of reference semantics, if there is no way to access that object by users, thus that implies user doesn't need that object anymore, so that object should be deleted. And  To sum up, if no reference to an object, that object should be deleted.

When we use a variable (global, local, or member variable) to reference an object, ARC will increase the reference counter of that object by one; when that variable is out of scope, ARC will decrease the referencn counter of that object by one, and check if the counter is zero, if so, delete that object.

So far so good, right? But what if there is an object A has a member variable b refering to another object B that has a member variable a refering to object A.

class A { public var b: B?
    init() {print("init A")}
    deinit {print("destroy A")}}
class B { public var a: A?
    init(){print("init B")}
    deinit{print("destroy B")}}
class C { init(){print("init B")}    
          deinit{print("destroy B")}}
func goMemoryLeak(){ 
var _a = A(); var _b = B(); var _c = C(); _a.b = _b; _b.a = _a }
goMemoryLeak()

By running above code on Swift Playground App, we can see the output:

init A
init B
init C
destroy C

There is no destruction of A and B but destruction of C. The reason is in object A and B there is a member variable refering to each other, therefore after the function finished, the local variable references, i.e. _a, _b, and _c, are removed, so the object C has zero reference then was deleted, but for object A and B, beside _a and _b, they have _a.b and _b.a references. It looks like a cycle that _a.b refers to _b and _b.a refers to a.

In other words, just right before function finished, object A and B each has 2 references. after function finished, there is still one more reference to each A and B. Therefore, ARC doesn't kick in to delete them. So it causes memory leaks, that is the memory occupied by those zombie objects cannot be reused until the App is terminated.

The way to solve this problem is weak reference in the old day of Objective-C. That is to declare one of the member variable is weak reference to break that cycle between A and B. That variable weak reference doesn't causes reference counter increase.  Moreover because we have weak reference, we should give a name to above normal reference which will cause reference counter increased by one, 'Strong reference'. Thus the reference counter is counting how many strong references to its object.

Now assume the A.b member variable is a weak reference, then just right before function finished, A has two strong references, _a and _b.a; B has one strong reference, _b; C has one strong reference, _c. Right After function finished, _a, _b and _c are deleted, this time, A has one strong references, _b.a. B and C has no strong reference. Then B and C are deleted. Due to the deletion of B, A becomes no strong reference as well, A also is deleted.

class A { public weak var b: B?
    init() {print("init A")}
    deinit {print("destroy A")}}
class B { public var a: A?
    init(){print("init B")}
    deinit{print("destroy B")}}
class C { init(){print("init B")}    
          deinit{print("destroy B")}}
func goMemoryLeak(){ 
var _a = A(); var _b = B(); var _c = C(); _a.b = _b; _b.a = _a }
goMemoryLeak()

Outputs:

init A
init B
init C
destroy C
destroy B
destroy A

Happy ending. No memory leak anymore.

So far so good unitl the advent of Swift. 

In a case that Object A with a weak variable b reference to object B exists after B is deleted, that is _a.b for our example, _a.b is nil this time. And because the weak reference declaration of variable b, the type of b must be nil-able, i.e. B?. When we use the variable b, we need to check first if b is nil. That is quite annoying when we are sure that b is certainly not nil, but we need to use weak reference to break that reference cycle.

By the way, why is it not annoying in Objective-C? The quit answer is the Type system in Swift is much more strict. In Objective-C, no matter strong reference or weak reference, they are pointers. Thus if you get a pointer, strong or weak, you are not sure it points to some object or nil. For guaranttee, you should be better check it before use it. Or use it bluntly. But in Swift, it is not safe to use an object that is nil. So it requires a check before use.

Back on track, now our demand is if we sure the variable with weak reference is no way to be nil in its lifespan, use it as strong reference. How to ensure that no way to be nil conditon? That is the responsibility for programmer. You ensure it, you respond it. If it is nil when it is used, fatal error.

Fair enough. Swift introduces unowned reference for our demand. Unowned reference is a weak reference with implicity forced unwrap, that is to be used as strong reference but doesn't cause reference counter increased.

One more question, why it is called unowned? Because it is from a concept that enforce it should not be nil in its lifespan. In Object-Oriented programming, there is a relationship named 'Owns-A'. The owner object owns another object, if the owener ends its life, it cleans up its occupied. So the lifespan of those occupied is dependent on their owner. When the owner use them, the owner is sure that those are exist.

By this way, when we see this 'Owns-A' relation in our design, and want to break a reference cycle at the same time, we should consider to use unowned reference instead of weak reference. Moreover, as unowned reference is stronger than weak one, Swift compiler can do more optimization on it. That is a beneficial move, design requirement enforcement and performance gain.