[swift-users] Calling default implementation of protocol methods as selectors

Charles Srstka cocoadev at charlessoft.com
Sun Jun 4 12:34:37 CDT 2017


Here’s what I’d try.

(disclaimer: written in Mail, may contain errors, yadda yadda yadda)

public class BlockTapGestureRecognizer: UITapGestureRecognizer {
    private class Target: NSObject {
        private let closure: (UITapGestureRecognizer) -> ()
        
        init(closure: @escaping (UITapGestureRecognizer) -> ()) {
            self.closure = closure
            super.init()
        }
        
        @objc func performClosure(_ sender: Any?) {
            guard let recognizer = sender as? UITapGestureRecognizer else {
                print("Unexpected sender (expected UITapGestureRecognizer)")
                return
            }
            
            self.closure(recognizer)
        }
    }
    
    private let target: Target
    
    public init(closure: @escaping (UITapGestureRecognizer) -> ()) {
        self.target = Target(closure: closure)
        super.init(target: self.target, action: #selector(Target.performClosure(_:)))
    }
}

No need for weird hacks passing nil as the target or action.

Charles

> On Jun 4, 2017, at 11:08 AM, Nate Birkholz via swift-users <swift-users at swift.org> wrote:
> 
> Technically it does work(!), but I am hesitant to use it in what amounts to sample code for my current job search, haha.
> 
> On Sun, Jun 4, 2017 at 4:55 AM, Zhao Xin <owenzx at gmail.com> wrote:
> Will this work?
> 
> class TapGestureRecognizer: UITapGestureRecognizer {
>     var onTap: (() -> Void)?
>     init(onTap: (() -> Void)?) {
>         self.onTap = onTap
>         super.init(target: nil, action: nil)
>         self.removeTarget(nil, action: nil)
>         self.addTarget(self, action: #selector(internalTapHandler))
>         print(self)
>     }
>     
>     @objc private func internalTapHandler() {
>         onTap?()
>     }
> }
> 
> Zhao Xin
> 
> On Sun, Jun 4, 2017 at 5:24 PM, Nate Birkholz <nbirkholz at gmail.com> wrote:
> Also, note that I tried the following:
> 
> class BlockTapGestureRecognizer: UIGestureRecognizer {
>     var onTap: (() -> Void)?
>     init(onTap: (() -> Void)?) {
>         self.onTap = onTap
>         super.init(target: nil, action: nil)
>         self.addTarget(self, action: #selector(internalTapHandler))
>         print(self)
>     }
> 
>     @objc private func internalTapHandler() {
>         onTap?()
>     }
> }
> 
> And the object prints looking okay:
> 
> <Artmospherez.BlockTapGestureRecognizer: 0x1701998b0; baseClass = UIGestureRecognizer; state = Possible; view = <UIView 0x100c60870>; target= <(action=internalTapHandler, target=<Artmospherez.BlockTapGestureRecognizer 0x1701998b0>)>>
> 
> But it doesn't in practice work. The documentation for the (target: action:) initializer states:
> 
> target: An object that is the recipient of action messages sent by the receiver when it recognizes a gesture. nil is not a valid value.
> action: A selector that identifies the method implemented by the target to handle the gesture recognized by the receiver. The action selector must conform to the signature described in the class overview. NULL is not a valid value.
> 
> So something is going on inside there when the nil values are passed to the recognizer. As the documentation states, nil is not a valid value and it must cause troubles.
> 
> Or I did something wrong?
> 
> 
> 
> On Sun, Jun 4, 2017 at 1:55 AM, Nate Birkholz <nbirkholz at gmail.com> wrote:
> Ah, I didn't read the rest of the thread. As Zhao Xin notes, you cannot call super.init(target: self, action: #selector()), and you cannot call super.init() in a subclass init or convenience init, it *has* to be the designated init method init(target:action:). That's too bad, closure-based gesture recognizers would be snazzy and swifty and I'd love to see them as part of UIKit. 
> 
> I could write my own custom implementations of subclassed UIKit.UIGestureRecognizerSubclass(es), but as the screens in question have tap, swipe up, swipe down, and swipe right gesture recognizers, I feel its overkill to write all that to avoid repeating ~10 lines of code.
> 
> On Sun, Jun 4, 2017 at 1:21 AM, Nate Birkholz <nbirkholz at gmail.com> wrote:
> I briefly considered something like this but didn't explore it. Elegant.
> 
> Sent from my iPhone, please excuse brevity and errors
> 
> On Jun 3, 2017, at 9:38 PM, Geordie Jay <geojay at gmail.com> wrote:
> 
>> I am dealing with a variant of this on Android right now. I have just subclassed e.g. UITapGestureRecognizer to perform the 2nd variant above and externally accept a closure as its argument. I'm writing this on my phone so forgive any syntax errors or accidental omissions:
>> 
>> class TapGestureRecognizer: UITapGestureRecognizer {
>> var onTap: (() -> Void)?
>> init(onTap: (() -> Void)?) {
>> self.onTap = onTap
>> super.init(target: self, action: #selector(internalTapHandler))
>> }
>> 
>> @objc private func internalTapHandler() {
>> onTap?()
>> }
>> }
>> 
>> class Baz: Foo {
>> init() {
>> let tapRecognizer = TapGestureRecognizer(onTap: self.bar)
>> }
>> }
>> 
>> 
>> Cheers,
>> Geordie
>> On Sat 3. Jun 2017 at 16:53, Nate Birkholz via swift-users <swift-users at swift.org> wrote:
>> Thanks, the second had occurred to me, but felt a little too much like in practice it would make the code harder to understand.
>> 
>> On Fri, Jun 2, 2017 at 9:58 PM, Zhao Xin <owenzx at gmail.com> wrote:
>> I found two workarounds.
>> 
>> 1.
>> protocol Foo: class {
>>     func bar()
>> }
>> 
>> class Base:Foo {
>>     @objc func bar() {
>>         print("bar")
>>     }
>> }
>> 
>> class Baz: Base {
>>     override init() {
>>         super.init()
>>         let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(bar))
>>     }
>> }
>> 
>> 2.
>> protocol Foo: class {
>>     func bar()
>> }
>> 
>> extension Foo {
>>     func bar() {
>>         print("bar")
>>     }
>> }
>> 
>> class Baz: Foo {
>>     init() {
>>         let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(delegate))
>>     }
>>     
>>     @objc func delegate() {
>>         bar()
>>     }
>> }
>> 
>> 
>> Zhao Xin
>> 
>> 
>> 
>> 
>> 
>> 
>> On Sat, Jun 3, 2017 at 10:35 AM, Nate Birkholz via swift-users <swift-users at swift.org> wrote:
>> protocol Foo: class {
>>     func bar()
>> }
>> 
>> extension Foo {
>>     func bar() {
>>          print("bar")
>>     }
>> }
>> 
>> class Baz: Foo {
>>     init() {
>>         let tapRecognizer = UITapGestureRecognizer(target: self, action: #selector(bar))
>>     }
>> }
>> 
>> the #selector tells me: "Argument of '#selector' refers to instance method 'bar()' that is not exposed to Objective-C" and asks me to add @objc to the method definition. 
>> 
>> Adding @objc to the method tells me: "@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes"
>> 
>> Adding @objc to the protocol doesn't fix it, just introduces new issues.
>> 
>> "dynamic" cannot be applied to a protocol, so cannot be used alternatively. 
>> 
>> Is there a way to get around this? If a method is called by a gesture recognizer, is there no way to have a default protocol implementation? I'd like to use default implementations if possible to make my code more DRY.
>> 
>> Is there a roadmap/plan for swift-native selector dispatch?
>> 
>> Thanks. I look forward to the inevitable reply revealing the dumb thing I missed. :)
>> 
>> -- 
>> Nate Birkholz
>> 
>> _______________________________________________
>> swift-users mailing list
>> swift-users at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-users
>> 
>> 
>> 
>> 
>> 
>> -- 
>> Nate Birkholz
>> _______________________________________________
>> swift-users mailing list
>> swift-users at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-users
> 
> 
> 
> -- 
> Nate Birkholz
> 
> 
> 
> -- 
> Nate Birkholz
> 
> 
> 
> 
> -- 
> Nate Birkholz
> _______________________________________________
> swift-users mailing list
> swift-users at swift.org
> https://lists.swift.org/mailman/listinfo/swift-users



More information about the swift-users mailing list