I'm using ReactiveCocoa in Swift as followed:
registerButton?.rac_signalForControlEvents(UIControlEvents.TouchUpInside).subscribeNextAs(registerButtonTapped)
private func registerButtonTapped(button: UIButton){
// Method here
}
Which creates a retain cycle.
I know the solution is as followed:
registerButton?.rac_signalForControlEvents(UIControlEvents.TouchUpInside).subscribeNextAs({ [weak self] (button:UIButton) in
self?.registerButtonTapped(button)
})
But this enforces me to use the subscribeNextAs
block and not the nicer oneliner passing the method.
Any idea how to use the oneliner without a retain cycle?
OK, so the answer linked to by Jakub Vano is great, but it's not quite general-purpose. It constrains you to use a function that has no parameters and returns Void
. Using Swift generics, we can be a little smarter about this to use functions that accept any parameters and use any return type.
So the first thing is that, for weak
relationships, you must use an object. Structs can't be referred to weakly. So we'll constrain the instance type to be AnyObject
, a protocol that all classes conform to. Our function declaration looks like the following:
func applyWeakly<Type: AnyObject, Parameters, ReturnValue>(instance: Type, function: (Type -> Parameters -> ReturnValue)) -> (Parameters -> ReturnValue?)
So the function takes an instance, and a function that takes an instance and returns a Parameters -> ReturnValue
function. Remember, closures and functions in Swift are interchangeable. Neat!
Note that we're having to return an optional ReturnValue
because the instance might become nil
. I'll address later how to get around this.
OK so now you need to know a really neat trick: Swift instance methods are actually just curried class methods, which is perfect for our needs 🎉
So now we can call applyWeakly
with the class function that returns an instance function when you call it with an instance. The applyWeakly
implementation is fairly straightforward.
func applyWeakly<Type: AnyObject, Parameters, ReturnValue>(instance: Type, function: (Type -> Parameters -> ReturnValue)) -> (Parameters -> ReturnValue?) {
return { [weak instance] parameters -> ReturnValue? in
guard let instance = instance else { return nil }
return function(instance)(parameters)
}
}
Super. So how would you use this? Let's take a very simple example. We have a class with a parameter that holds a closure, and that closure will refer to its own instance method (this is just demonstrating the reference cycle problem – your question involves two objects but I'm simplifying down to one).
class MyClass {
var closure: (String -> String?)!
func doThing(string: String) -> String {
return "hi, \(string)"
}
init() {
closure = doThing // WARNING! This will cause a reference cycle
closure = applyWeakly(self, function: MyClass.doThing)
}
}
We have to use an implicitly-unwrapped optional for the closure
type so we can refer to an instance method in our init
function. That's ok, just a limitation of this example.
So this is great, and will work, but it's kind of icky that our closure
type is String -> String?
but our doThing
type is String -> String
. In order to return a non-optional string, we need to either force-unwrap an optional (😱) or use unowned
.
Unowned references are like weak ones, except they're non-zeroing. That means if the objects is deallocated and you use a reference to it, your app will explode. In your case, though, it's applicable. Here's more info on unowned vs weak.
The applyUnowned
function would look like this:
func applyUnowned<Type: AnyObject, Parameters, ReturnValue>(instance: Type, function: (Type -> Parameters -> ReturnValue)) -> (Parameters -> ReturnValue) {
return { [unowned instance] parameters -> ReturnValue in
return function(instance)(parameters)
}
}
I hope that clarifies things – happy to answer any follow-up questions. This problem has been in the back of my mind for a while, and I'm glad to have finally recorded my thoughts down.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With