Allow Selectors to be used as Closures

It feels a lot more Swift-like to me to allow a Selector argument to have
the option of being a closure.

For example, instead of:

UIBarButtonItem(title: "Press", style: .Done, target: self, action:
"functionToCall")

func functionToCall() {
    // Do something}

We should be able to do:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: {
    // Do Something})

For simple tasks it would be a lot neater, and indeed faster to code this
way.

Thanks,

Chris

3 Likes

The other concern with patterns like this is that they tend to lead to reference cycles due to unintentional closure capture.

  David

···

On Dec 4, 2015, at 11:25 AM, Cole Kurkowski <crk@fastmail.com> wrote:

I agree that a closure is a much better solution to the target-action pattern in Swift, especially since you could pass a declared method with “self.functionToCall” which is basically a type safe version of the current paradigm. All of that said, I believe the reason it doesn’t work this way right now is compatibility with objective-c and Cocoa. I’d love to see this changed, as it’s one of the least Swifty things I have to use on a regular basis. I’m just not sure exactly how large of an effort it would be to make it compatible.

Thanks for your time,
Cole Kurkowski

On Dec 4, 2015, at 12:49, Chris Byatt <byatt.chris@gmail.com <mailto:byatt.chris@gmail.com>> wrote:

It feels a lot more Swift-like to me to allow a Selector argument to have the option of being a closure.

For example, instead of:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: "functionToCall")

func functionToCall() {
    // Do something
}
We should be able to do:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: {
    // Do Something
})
For simple tasks it would be a lot neater, and indeed faster to code this way.

Thanks,

Chris
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

We could do something with selectors similar to what we do with C function pointers and limit them to taking closures that take no context. We could do this by having a '@convention(objc_selector)' or something like that that represents a context-free (Self: class) -> Args -> Return function type as a selector pointer; invoking it emits objc_msgSend.

-Joe

···

On Dec 4, 2015, at 11:26 AM, David Smith <david_smith@apple.com> wrote:

The other concern with patterns like this is that they tend to lead to reference cycles due to unintentional closure capture.

1 Like

I agree that a closure is a much better solution to the target-action pattern in Swift, especially since you could pass a declared method with “self.functionToCall” which is basically a type safe version of the current paradigm. All of that said, I believe the reason it doesn’t work this way right now is compatibility with objective-c and Cocoa. I’d love to see this changed, as it’s one of the least Swifty things I have to use on a regular basis. I’m just not sure exactly how large of an effort it would be to make it compatible.

Thanks for your time,
Cole Kurkowski

···

On Dec 4, 2015, at 12:49, Chris Byatt <byatt.chris@gmail.com> wrote:

It feels a lot more Swift-like to me to allow a Selector argument to have the option of being a closure.

For example, instead of:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: "functionToCall")

func functionToCall() {
    // Do something
}
We should be able to do:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: {
    // Do Something
})
For simple tasks it would be a lot neater, and indeed faster to code this way.

Thanks,

Chris
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

I think this has more to do with the UIKit API than the Swift language, but since we’re discussing it, here’s my 2¢

We should be able to do:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: {
    // Do Something
})

If action is just a closure, why is target needed? This example doesn’t match what was proposed in the subject “Selectors to be used as Closures”.
I see a couple solutions to this:

1. Make it UIBarButtonIttem(title:style:action:), and pass any closure. That’s the most flexible, but as David mentioned it makes it too easy to create reference cycles.
2. Keep the target/action pattern, but make action’s type match the expected selector signature. To avoid cycles, the button item would keep a weak reference to self, and only call action if target != nil

class MyController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Press", style: .Done, target: self, action: MyController.functionToCall)
    }

    func functionToCall(button: UIBarButtonItem) {
        // Do stuff
    }
}

···

On 04 Dec 2015, at 19:49, Chris Byatt <byatt.chris@gmail.com> wrote:

--
Jorge Bernal | jbernal@gmail.com | jorge@automattic.com
Mobile Engineer @ Automattic | http://automattic.com/

http://koke.me/ | http://jorgebernal.es/ | http://twitter.com/koke

1 Like

Jorge's second suggestion seems like a good one - it definitely makes more
sense to lay it out that way than the current usage of strings which makes
me cry a little bit..

It would be nice to eliminate the need for "myFunction"/"myFunction:".

Chris

···

On Sat, 5 Dec 2015 at 16:21 Jorge Bernal <me@koke.me> wrote:

I think this has more to do with the UIKit API than the Swift language,
but since we’re discussing it, here’s my 2¢

On 04 Dec 2015, at 19:49, Chris Byatt <byatt.chris@gmail.com> wrote:

We should be able to do:

UIBarButtonItem(title: "Press", style: .Done, target: self, action: {
    // Do Something})

If action is just a closure, why is target needed? This example doesn’t
match what was proposed in the subject “Selectors to be used as Closures”.
I see a couple solutions to this:

1. Make it UIBarButtonIttem(title:style:action:), and pass any closure.
That’s the most flexible, but as David mentioned it makes it too easy to
create reference cycles.
2. Keep the target/action pattern, but make action’s type match the
expected selector signature. To avoid cycles, the button item would keep a
weak reference to self, and only call action if target != nil

class MyController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        self.navigationItem.rightBarButtonItem = UIBarButtonItem(title:
"Press", style: .Done, target: self, action: MyController.functionToCall)
    }

    func functionToCall(button: UIBarButtonItem) {
        // Do stuff
    }
}

--
Jorge Bernal | jbernal@gmail.com | jorge@automattic.com
Mobile Engineer @ Automattic | http://automattic.com/

http://koke.me/ | http://jorgebernal.es/ | http://twitter.com/koke

Having to use selectors is still a big pain-point. What’s your take on this today?

It’s certainly possible to dynamically create a subclass, store the closure in an extra ivar, and reuse the standard responder dispatch to a private selector :-)

// Returns a subclass of UIBarButtonItem that holds the closure
let barButtonItem = UIBarButtonItem(title: "Press", style: .Done, action: {
    // Do Something
})

// Somewhat equivalent to:
let barButtonItem = _SomeDynamicUIBarButtonItemSubclass(title: "Press", style: .Done, target: self, selector: #selector(_performAction(_:)))
barButtonItem._action = {
    // Do Something
}
barButtonItem._performAction(self) // Called when button is tapped

Except that the _SomeDynamicUIBarButtonItemSubclass class is created at runtime.

Objective-C KVO uses this technique in order to wrap property setters, unless I’m mistaken.

Even better, to remove memory cycles, the closure should accept the receiver as an argument:

// Returns a subclass of UIBarButtonItem that holds the closure
let barButtonItem = UIBarButtonItem(title: "Press", style: .Done, action: { barButtonItem in
    // Do Something
})

This would be a nice Swift-only addition to UIKit ;-) Wish Olivier Gutknecht had an account here ;-)

You have to be careful with this sort of thing. The target parameter isn’t necessarily the object that the selector will be dispatched to, since there may be a traversal of the responder chain (e.g. via UIResponder.target(forAction:withSender:).

Also, target may be nil. The target/action mechanism is highly dynamic in the “best” Obj-C sense.

The closure would need to accept the actual target (not the target parameter) as an argument. Even if the receiver were passed too, that doesn’t remove the risk of reference cycles, because there would be a pretty big temptation just to capture self.whatever in the closure, instead of $1.whatever.

You have to be careful with this sort of thing.

That’s all the fun with Objective-C :-) This feature, in my proposed approach, doesn’t belong to Swift, but to Objective-C (that Swift has not yet sent to oblivion, unless I’m mistaken).

Also, target may be nil.

I guess such a dynamic button/barButtonItem would use itself as the target.

The closure would need to accept the actual target (not the target parameter) as an argument

The closure would surely get the button/barButtonItem as an argument, since the button/barButtonItem is the target. The nice ergonomics of this API lies in the fact that you can query the button/barButtonItem from the closure (such as performing action depending on the tag) without leaking memory.

YMMV. It’s how I see this funny nice little hack. Plus a suggestion to not ask Swift to do what Objective-C can already do (since we’re talking about Objc UIKit classes, here).

There’s probably a correct solution involving a hypothetical ActionPerformer protocol in Swift. But the danger of accidentally capturing self seems like a pretty big loss to set off against the expected gain, IMO.

Perhaps we need @autoweak for closure parameters?

A quick and dirty demo that works: https://gist.github.com/groue/a8eadeea92c7d5b0ba9f49177fc6d1fa

You can put a regular UIButton in a storyboard, and then refine it from your view controller:

button.onTouchUpInside { button in
    button.setTitle("Tapped!", for: .normal)
}

This is just a POC, not robustly tested software :-)

This kind of syntax is evidently superior to the current pattern available for buttons / views event handling. You decide what to do with button events where you have the most context.
More than one library added this kind of feature to Objective-C, but it is about time we had an official solution that can be used in Swift easily too.

Maybe a Swift UI framework should be discussed in a specific thread.

My interpretation of the “Allow Selectors to be used as Closures” title was that one may need convenience methods on button/barButtonItem, which can already be handled at the Objective-C level, and exposed in Swift with little to no caveat, as demonstrated above.

But this isn't right. In the earlier post:

the target: isn't the performer of the action — in many cases it's going to be nil (meaning First Responder). The target is the place in the responder chain to start searching for an object that is prepared to perform the action. It could be any UIResponder object (or any delegate of a UIResponder that the responder chain search recognizes).

Besides, unless I'm reading your code wrong, you arbitrarily make the button perform the action, when it wasn't even the original target. (self was a view controller or view, or something like that.) Even aside from the responder chain search, that isn't what the original call meant.

Plus, counter is a local variable, so by the time the button was touched, it'd be invisible to everything except the closure, and therefore likely useless. If it was an instance property instead, you'd end up capturing self strongly, and definitely causing a reference cycle.

the target: isn’t the performer of the action

I though you just wanted to execute a closure when the button is pressed. I do not understand what the target is supposed to do here. Could you please explain?

The target is the place in the responder chain to start searching for an object that is prepared to perform the action.

Yes, with a selector. Without any selector, you can’t find much, because the responder chain is fundamentally selector-based: responders in the chain are asked whether they respond to the selector, or not.

Besides, unless I’m reading your code wrong, you arbitrarily make the button perform the action

Yes. Without any selector, there is no way to find any responder in the responder chain, and the only obvious “target” is the button. If you have another idea, please explain.

Plus, counter is a local variable.

It’s just a silly example that increments a counter when the button is tapped, and let me check that the code works. This isn’t supposed to be a lesson on how to write an app.

My point is that we don’t need to ask Swift to allow closures where selectors are used, since this can already be done with Objective-C, within UIKit.

It simply isn’t how the target/action mechanism works — actions are routed dynamically, to dynamically chosen targets. You’re proposing an entirely separate API.

That’s definitely not a silly idea. There are times when you know that you want a particular object (usually a view controller or view) to invoke a specific method — when you really don’t want dynamism, and going through target/action becomes a hack. Using a closure for this would be great.

Unfortunately, though, the object you want isn’t necessarily self, so you’d want to keep the target: parameter, and have it be passed into the closure. You might also want to pass in a second parameter, the sender (which would be the button instance, in your example), but maybe the closure’s code could just capture it from the context if necessary.

I’ve got nothing against that approach, except the danger of capturing self in the closure, regardless of the target.

Actually, they’re asked whether they canPerformAction(_:withSender:), and the default implementation just returns whether they respond to the selector. It’s as dynamic as possible.

Sorry, yes, I know you meant that, but I was trying to emphasize the danger of creating retain cycles, which seems unusually high in this scenario. The value of your proposed mechanism may well outweigh the danger of capturing self.

2 Likes

That's not a silly idea at all. I would put this object in the closure executed by the button. And if you want to avoid retain cycles, just use [weak/unowned reference] as we all do when we want to avoid cycles.

Maybe. The danger here is that it’s quite possible this would create a reference cycle between that object and the button, and that would be a much harder bug to find than capturing self.