Swift Delegate Protocol Cannot Prevent Retain Cycle Issue

How to avoid a retain cycle when using an array of delegates in Swift

The problem is that weakDelegates is a strong reference and its reference to its elements of type WeakDelegateContainer is a strong reference.

Your situation is why the class NSHashTable exists. Initialize using weakObjects(). This will give you a set of ARC-weak references, each of which will be nilified and removed when the referenced object goes out of existence (with no need for any extra bookkeeping on your part, and no need for your WeakDelegateContainer type).

Your set will have to be typed as holding AnyObject, but you can easily mediate to ensure that you are supplying and retrieving SomeDelegate-conformant objects:

let list = NSHashTable<AnyObject>.weakObjects()
func addToList(_ obj:SomeDelegate) {
list.add(obj)
}
func retrieveFromList(_ obj:SomeDelegate) -> SomeDelegate? {
if let result = list.member(obj) as? SomeDelegate {
return result
}
return nil
}
func retrieveAllFromList() -> [SomeDelegate] {
return list.allObjects as! [SomeDelegate]
}

The function retrieveAllFromList() lists only objects that still exist. Any object that has gone out existence has been changed to nil in the NSHashTable and is not included in allObjects. That is what I mean by "no extra bookkeeping"; the NSHashTable has already done the bookkeeping.

Here is code that tests it:

func test() {
let c = SomeClass() // adopter of SomeDelegate
self.addToList(c)
if let cc = self.retrieveFromList(c) {
cc.someFunction()
}
print(self.retrieveAllFromList()) // one SomeClass object
delay(1) {
print(self.retrieveAllFromList()) // empty
}
}

Alternatively, you can use NSPointerArray. Its elements are pointer-to-void, which can be a little verbose to use in Swift, but you only have to write your accessor functions once (credit to https://stackoverflow.com/a/33310021/341994):

let parr = NSPointerArray.weakObjects()
func addToArray(_ obj:SomeDelegate) {
let ptr = Unmanaged<AnyObject>.passUnretained(obj).toOpaque()
self.parr.addPointer(ptr)
}
func fetchFromArray(at ix:Int) -> SomeDelegate? {
if let ptr = self.parr.pointer(at:ix) {
let obj = Unmanaged<AnyObject>.fromOpaque(ptr).takeUnretainedValue()
if let del = obj as? SomeDelegate {
return del
}
}
return nil
}

Here is code to test it:

    let c = SomeClass()
self.addToArray(c)
for ix in 0..<self.parr.count {
if let del = self.fetchFromArray(at:ix) {
del.someFunction() // called
}
}
delay(1) {
print(self.parr.count) // 1
for ix in 0..<self.parr.count {
if let del = self.fetchFromArray(at:ix) {
del.someFunction() // not called
}
}
}

Interestingly, after our SomeClass goes out of existence, our array's count remains at 1 — but cycling through it to call someFunction, there is no call to someFunction. That is because the SomeClass pointer in the array has been replaced by nil. Unlike NSHashTable, the array is not automatically purged of its nil elements. They do no harm, because our accessor code has guarded against error, but if you would like to compact the array, here's a trick for doing it (https://stackoverflow.com/a/40274426/341994):

    self.parr.addPointer(nil)
self.parr.compact()

Confusions about weak delegate in swift

should make the delegate to be "weak"

The answer is that if MyProtocol is not restricted to classes, you cannot make it weak, the compiler won't let you.

The reason for the above is that structs are value types. There isn't a reference that can be strong or weak, because logically the entire struct is copied in when you assign the delegate.

how can I avoid retain cycle?

This means that you have got to be careful that your delegate contains no strong references back to the instance of the class. So, for instance

struct ConcreteDelegate: MyProtocol
{
fun someFunc() {}
var instance: AClass

init()
{
instance = AClass()
instance.delegate = self
}
}

Causes a reference cycle. It can be broken by declaring instance as

    weak var instance: AClass! 

Alternatively, and a better solution (IMO), your protocol functions can pass the instance as a parameter so the delegate never needs to store a reference to the instance.

protocol MyProtocol {
func someFunc(caller: AClass)
}

You'll see the above approach adopted in Cocoa in lots of places, for example with the table view data source protocol.

Swift delegation - when to use weak pointer on delegate

You generally make class protocols weak to avoid the risk of a “strong reference cycle” (formerly known as a “retain cycle”). (Note, we now do that by adding the AnyObject protocol to a protocol’s inheritance list; see Class-Only Protocols; we do not use the class keyword anymore.) Failure to make the delegate weak does not mean that you inherently have a strong reference cycle, but merely that you could have one.

With struct types, though, the strong reference cycle risk is greatly diminished because struct types are not “reference” types, so it is harder to create strong reference cycle. But if the delegate object is a class object, then you might want to make the protocol a class protocol and make it weak.

In my opinion, making class delegates weak is only partially to alleviate the risk of a strong reference cycle. It also is a question of ownership. Most delegate protocols are situations where the object in question has no business claiming ownership over the delegate, but merely where the object in question is providing the ability to inform the delegate of something (or request something of it). E.g., if you want a view controller to have some text field delegate methods, the text field has no right to make a claim of ownership over the view controller.

weak Delegate and class protocol

You say:

However, I am not sure when I would need it as in all my cases, not doing weak delegate works

You only need the weak protocol-delegate pattern when you have a potential for a strong reference cycle, i.e. a circular series of strong references. For example, consider:

  • an object (the "parent") that has a property (the "child"), i.e. the parent has a strong reference to the child;

  • the child has a delegate property; and

  • you set the child's delegate to refer to the parent object.

In that case, it's critical that the delegate be weak reference or else you'll have a strong reference cycle.

Note, this is a trivial example and sometimes the chain of strong references can be rather complicated. For example, consider a UIView subclass that has a delegate property. The potential strong reference cycle can be quite long, from the view controller to its root view, through a series of subviews of subviews, all the way down to the UIView with the delegate that might potentially reference back to the view controller. That will result in a strong reference cycle, too, and we'd be inclined to use a weak reference for that delegate for that reason.

But when you employ protocol-delegate pattern for passing data between view controllers, though, this generally isn't a problem (with the exception of view controller containment) because the presenting view controller doesn't own the presented view controller. The view controller hierarchy generally maintains the strong references to the view controllers. So, when you dismiss the presented view controller, it is deallocated and the potential strong reference cycle is resolved.

Often, we'll instinctually employ the weak protocol-delegate pattern (simply because it prevents strong reference cycles from occurring at all). But sometimes you will use strong references. The most common strong reference pattern is NSURLSession whose delegate is a strong reference. As the documentation for init(configuration:delegate:delegateQueue:) warns us:

The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session by calling the invalidateAndCancel() or finishTasksAndInvalidate() method, your app leaks memory until it exits.

While this might seem paradoxical, the advantage of this strong reference pattern is that the session knows that it can safely call its delegate methods without fear of the object having been deallocated. (As an aside, this strong delegate behavior of NSURLSession rarely rears its ugly head, because we often use the completion handler methods and don't employ the delegate methods at all, and when we do employ delegate methods, we often have some object other than a view controller as the delegate for the session.)

In short, you really have to evaluate each situation and determine whether the weak reference that we instinctually lean towards is better, or whether you have one of those cases where your protocol is better served with strong references.

Retain Cycle in Swift delegate

weak references only apply to classes, not structs or enums, which are value types. But protocols by default can apply to any of those types.

Define your MainToolBarDelegate as a class-only protocol:

protocol MainToolBarDelegate: AnyObject {

}

Then you'll be able to declare your delegate as weak.

Multiple delegate in one class - Swift 5

Yes, you can have multiple protocols, if needed. It might be illustrative to consider a few UIKit patterns:

  • Table views and collection views have distinct protocols with different functional purposes, one for “data sources” and another for “delegates”.

  • URLSession employs a slightly different pattern, with a single delegate reference, a URLSessionDelegate, which has a hierarchy of different subprotocols to cover different functional needs (e.g., URLSessionTaskDelegate inherits URLSessionDelegate, etc.).

While different situations call for slightly different approaches, the idea is that a complicated interface can be broken down into different protocols (and optionally, either separate protocol references or single one) which cover distinct functional needs.

But you say:

Actually, my only purpose is to pass data to multiple view controller. I have a sign up function and when this is triggers, I am passing data to view controllers. my problem is I have 2 types of sign up, sign up for individual and sign up for corporate that is sharing a single sign up function. I only want to pass data to the view controllers that are related to the sign up type. I thought creating two delegates will solve my problem, delegate just for individual sign up and corporate signup.

It is really hard to be specific on the basis of so little information, but on the surface, this doesn’t sound like a compelling use-case for multiple protocols, much less separate delegate references. The difference here is not different functional domains, but rather slightly different payloads (a “user” vs “corporate” account type).

I might advise a single protocol that passes back an “authentication” object which includes a “user type” or “permissions structure”, in which the caller determines “oh, on the basis of the returned authentication result I will transition to such-and-such view” or what have you. But to the extent the sign-up can stick with a single protocol/interface, keeping objects as loosely coupled as possible, the better, IMHO.



Related Topics



Leave a reply



Submit