简体   繁体   中英

Swift - Avoiding strong reference cycles in closure-based configuration

Please note: I have read many of the (very many) answers and articles on the subject of avoiding strong reference cycles. However, I'm looking for guidance on how to handle a specific by-product of avoiding these cycles.

In the following example, the class Foo is designed to be configured using a closure. A reference to the closure is cached for later use.

The closure will be called whenever the Model data is needed. In order for Foo to work properly, the data must exist.

class Foo
{
    typealias ModelGetter = (() -> Model)
    fileprivate var _modelGetter: ModelGetter!

    ...

    func configure(with modelGetter: @escaping ModalGetter)
    {
        _modelGetter = modelGetter
    }

    func printLastestModel()
    {
        // Get the latest model, do something with it.
        precondition(_modelGetter != nil)
        let model = _modelGetter()
        print(model)
    }
}

In the above code, _modelGetter is implicitly unwrapped. Although I could define it as an Optional , and unwrap it as needed, Foo always needs the closure to be set in order to work properly, hence the implicit unwrapping.

Making an instance of Foo, and configuring it:

let foo = Foo()
foo.configure(with: { self.makeModel() })
foo.printLatestModel()

But, this creates a retain cycle.

So, [weak self] is used, and we check for self 's optionality:

foo.configure(with: { [weak self] in
    guard let strongSelf = self else { return **WHAT** }
    return strongSelf.makeModel()
})

Problem

This requires that even though self may be nil, the closure still needs to return a Model (ie WHAT?) to the caller of the closure (the Foo instance.) But, since self is nil, I don't have a Model to hand over.

Question

Can someone recommend a pattern to use in this situation? Preferably, I don't want Foo to be checking if the modelGetter is valid or not, or left wondering if the Model is valid. For Foo's purposes, if Foo exists, then it must always be able to get the Model it needs.

Or, should I just redesign Foo's needs, to take in to account the possibility of not being able to procure a Model? Thankyou for any help.

If Class is the owner of Foo , it's recommended to set [unowned self] instead of [weak self] . It will solve the problem. If one class is the owner of another it will never appear as a problem, but if the logic is wrong it will appear a crash. This is not bad because it signals to you that you broke something in the project.

But, this creates a retain cycle.

No it doesn't. foo owns a reference to the closure and the closure owns a reference to self (which is clearly not foo ). You will only get a retain cycle, if you have a strong property of type Foo in self and you set it to foo .

On the assumption that you are going to do that, I would use one of two patterns:

  • I make the assumption as a programmer that self will always be there for the lifetime of foo . In that case, I would use [unowned self] instead of [weak self] . If my assumption proves to be wrong, the program will abort and I'll be able to fix the bug based on the stack trace. Obviously, you'll want a fairly extensive test suite to validate the assumption as far as possible.

  • I decide that it is valid for self to disappear in which case you have three choices about how to handle the disappearance:

    • Make the return type of the closure optional ie () -> Model? and make Foo capable of handling a nil model
    • Have a default Model that you return if self is nil .
    • Make the closure declare that it throws and throw an error if self is nil .

I think I'd probably go with [unowned self] and make sure I explicitly kept a strong reference to it somewhere.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM