chrisamanse
chrisamanse

Reputation: 4319

Default Implementation of Objective-C Optional Protocol Methods

How can I provide default implementations for Objective-C optional protocol methods?

Ex.

extension AVSpeechSynthesizerDelegate {
  func speechSynthesizer(synthesizer: AVSpeechSynthesizer, didFinishSpeechUtterance utterance: AVSpeechUtterance) {
    print(">>> did finish")
  }
}

Expectation: Whatever class that conforms to AVSpeechSynthesizerDelegate should run the above function whenever a speech utterance finishes.

Upvotes: 2

Views: 3036

Answers (1)

nhgrif
nhgrif

Reputation: 62062

You do it just exactly as you've implemented it. The difference ends up being in how the method is actually called.

Let's take this very simplified example:

@objc protocol FooProtocol {
    optional func bar() -> Int
}

class Omitted: NSObject, FooProtocol {}
class Implemented: NSObject, FooProtocol {
    func bar() -> Int {
        print("did custom bar")
        return 1
    }
}

By adding no other code, I'd expect to have to use this code as such:

let o: FooProtocol = Omitted()
let oN = o.bar?()

let i: FooProtocol = Implemented()
let iN = i.bar?()

Where oN and iN both end up having type Int?, oN is nil, iN is 1 and we see the text "did custom bar" print.

Importantly, not the optionally chained method call: bar?(), that question mark between the method name in the parenthesis. This is how we must call optional protocol methods from Swift.

Now let's add an extension for our protocol:

extension FooProtocol {
    func bar() -> Int {
        print("did bar")
        return 0
    }
}

If we stick to our original code, where we optionally chain the method calls, there is no change in behavior:

enter image description here

However, with the protocol extension, we no longer have to optionally unwrap. We can take the optional unwrapping out, and the extension is called:

enter image description here

The unfortunate problem here is that this isn't necessarily particularly useful, is it? Now we're just calling the method implemented in the extension every time.

So there's one slightly better option if you're in control of the class making use of the protocol and calling the methods. You can check whether or not the class responds to the selector:

let i: FooProtocol = Implemented()

if i.respondsToSelector("bar") {
    i.bar?()
}
else {
    i.bar()
}

enter image description here

This also means you have to modify your protocol declaration:

@objc protocol FooProtocol: NSObjectProtocol

Adding NSObjectProtocol allows us to call respondsToSelector, and doesn't really change our protocol at all. We'd already have to be inheriting from NSObject in order to implement a protocol marked as @objc.

Of course, with all this said, any Objective-C code isn't going to be able to perform this logic on your Swift types and presumably won't be able to actually call methods implemented in these protocol extensions it seems. So if you're trying to get something out of Apple's frameworks to call the extension method, it seems you're out of luck. It also seems that even if you're trying to call one or the other in Swift, if it's a protocol method mark as optional, there's not a very great solution.

Upvotes: 4

Related Questions