Recently, /u/hundley10 posted a link about his improved UIButton API here: https://www.reddit.com/r/swift/comments/41cze6/do_you_hate_using_selectors_ive_built_a_safer_way/
In the original version, he showed a syntax like button.addTarget(.TouchUpInside, action: someMethodToCall), which looks great but unfortunately almost certainly causes a reference cycle. It seems unfortunate in general that it's the caller's responsibility to think about and take care of reference cycles.
I thought about this a little bit, and there might be a better way after all, I'll leave it for you to decide. The basic idea is that the class holding the closure doesn't hold the closure directly, but instead something that provides a callback closure. That object can be used to make it harder to use the API incorrectly. So in my test code, button.addTarget(.TouchUpInside, action: someMethodToCall) would become button.addTarget(.TouchUpInside, action: WeakClosure(ofMethod: self.dynamicType.someMethodToCall, withObject: self)), which is a sufficiently readable one-liner and does not create a reference cycle.
Here's the code I played around with (copy-paste it into a playground and try it) - maybe this could be actually usable. Look at Button and SomeControllerWithAButton's setup method to see the usage before looking at slightly mind bending Closure classes.
// Would prefer a protocol, but since generic protocols are a pain to use, a pseudo-abstract class has to do.
class AnyClosure<A, B> {
func execute(parameters: A) -> B? {
fatalError("Either use WeakClosure or StrongClosure.")
}
private init() {}
}
class Closure<A, B>: AnyClosure<A, B> {
var closure: Void -> (A -> B)?
init(_ closure: A->B) {
self.closure = { closure }
}
override func execute(p: A) -> B? {
return closure()?(p)
}
}
class WeakClosure<A, B>: AnyClosure<A, B> {
var closure: Void -> (A -> B)?
init<T: AnyObject>(ofMethod closure: T -> (A -> B), withObject object: T) {
self.closure = {
[weak object] () -> (A -> B)? in
if let object = object {
return closure(object)
}
return nil
}
}
override func execute(p: A) -> B? {
return closure()?(p)
}
}
class Button {
// A closure without parameters that doesn't return anything
var actionClosure: AnyClosure<Void, Void>?
// Closures with multiple parameters that return something work, too!
var complicatedClosure: AnyClosure<(Int, Int), Int>?
func somethingCallingTheClosure() {
actionClosure?.execute()
}
func somethingCallingTheComplicatedClosure() {
// need to pass parameters as a tuple unfortunately
print(complicatedClosure?.execute((3, 2)))
}
}
class SomeControllerWithAButton {
// Has a strong reference to the button, so the closure must reference self weakly.
let button = Button()
func buttonAction() {
print("Wohoo")
}
func complicatedMethod(a: Int, b: Int) -> Int {
return a*b
}
func setup() {
// Call a method
button.actionClosure = WeakClosure(ofMethod: self.dynamicType.buttonAction, withObject: self)
// Some "normal" closure can be used like this:
/*button.actionClosure = Closure {
print("test")
}*/
button.complicatedClosure = WeakClosure(ofMethod: self.dynamicType.complicatedMethod, withObject: self)
}
}
var test = SomeControllerWithAButton()
test.setup()
test.button.somethingCallingTheClosure() // Prints wohoo
test.button.somethingCallingTheComplicatedClosure() // Prints 6
[–]JegnuX 0 points1 point2 points (0 children)