Allow Selectors to be used as Closures

I start to see what you mean. I wouldn’t name this object “target”, but “context”. This would avoid any confusion with the targets of the responder chain, which are all UIResponder instances. This confusion is the reason of our difficulties understanding each other.

button.addAction(context: ...) { context in context.doStuff() }

But anyway, again, this belongs to UIKit & Objective-C, with a nice Swift generic wrapper on top. This does not belong to Evolution category, IMHO.

The Objective-C code I posted above provides enough evidence of this.

1 Like

Hello

In any case selectors are part of ObjC OOP message forwarding idea. Messages should have the name, but Swift closures (and ObjC blocks) has no name. Yes, in every day tasks we would like to add actions as closures because of ObjC blocks changed everything in Cocoa programming but not in this area. But this changes can break ObjC/Cocoa OOP patterns or make they too complicated to understand - what message name should have closure? I think that patterns strictness is very important in long perspective. May be there are some solutions that can save patterns strictness and simplify syntax. But some syntax improvements is welcome and can be useful in any case.

This would be outside the scope of Evolution.

But just to follow up on this, we had someone add an extension to our codebase for this a while back. We removed it for a few reasons:

  1. It inverted the memory management model. Target action is an assign back-reference, but this created a strong link which encouraged retain cycles. Stepping it back with weak handling was actually more convoluted and less intuitive than simply doing it with Target-Action. We ended up with a lot of retain cycles, and a few crashes from unowned self where someone reasoned they wouldn’t deallocate… and then did.

  2. It lost some of the Obj-C wins in the responder chain. If we kept the existing methods in place, that’s fine, but we end up with two inverse ways of doing it…

  3. Removing the strongly held target later was an issue. It required a reference held anyway to a context object, which further negated wins from this architecture.

Ultimately the resolution was it was a poorly thought-out developer convenience which encouraged anti patterns.

I can’t say this was a popular appraisal of the issue - devs often just want the most convenient solution and will shoot themselves in the foot to get it…

1 Like

I generally like the idea, but the biggest issue is that it will definitely lead to memory leaks / reference cycles. For example:

let name = NSNotification.Name(rawValue: "Test")

// 1. No leak
NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil, using: {_ in })
// 2. No leak
NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil,
                                           using: { [weak self] (notification) in self?.testFunc(notification: notification)})
// 3. Leak
NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil,
                                           using: { (notification) in self.testFunc(notification: notification)})
// 4. Leak
NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil, using: testFunc)
// 5. No leak
NotificationCenter.default.addObserver(forName: name, object: nil, queue: nil, using: SomeClass.testFunc2)

private func testFunc(notification: Notification) {}
private static func testFunc2(notification: Notification) {}

I’m not sure if its clear to every user what will produce an memory leak. I think the difference between 2 and 3 is pretty clear. How ever, passing a function seems legit. If it is an instance function it will create a memory leak, if it is a static function, it won’t. This is not about why this happens, I know why that happens.

In my opinion its not clear, especially to inexperienced users, why this happens. And I won’t expect them to know that this will happen.

One possible solution would be to convert 4 into 2 at compile time, so no leak will occur.


In the case of buttons for example it wouldn’t be unusual to have one function for multiple buttons and and simply change one thing based on the button (and we all don’t want to duplicate code). So passing one function to every button would create a memory leak, if you are not very carefully.

I hope I made my point clear. This is not about why this happens, this is about that there are some situations that will lead to memory leaks, especially for unexperienced users.