How to Use @Objc Protocol with Optional and Extensions at the Same Time

How to use @objc protocol with optional and extensions at the same time?

I think this is not possible in swift (because of the way it bridges to @objc protocols). But this is a work around(using Obj-c associated objects) to solve the unrecognized selector sent to instance... problem.

fileprivate class AssociatedObject: NSObject {
var closure: (() -> ())? = nil

func trigger() {
closure?()
}
}

// Keys should be global variables, do not use, static variables inside classes or structs.
private var associatedObjectKey = "storedObject"

protocol CustomProtocol: class {
func setup()
}

extension CustomProtocol where Self: NSObject {

fileprivate var associatedObject: AssociatedObject? {
get {
return objc_getAssociatedObject(self, &associatedObjectKey) as? AssociatedObject
}

set {
objc_setAssociatedObject(self, &associatedObjectKey, newValue, .OBJC_ASSOCIATION_RETAIN)
}
}

func setup() {
let object = AssociatedObject()
object.closure = { [weak self] in // Do not forget to use weak in order to avoid retain-cycle
self?.functionToCallIndirectlyWithSelector()
}

let selector = #selector(object.trigger)
// Uncomment next line to test it's functionality
object.perform(selector)

// Here, you must add selector to the target which needs to call the selector, for example:
// refreshControl.addTarget(object, action: selector, forControlEvents: .valueChanged)

self.associatedObject = object
}

func functionToCallIndirectlyWithSelector() {
print("Function got called indirectly.")
}
}

class CustomClass: NSObject, CustomProtocol {}

let instance = CustomClass()

instance.setup()

I added Self: NSObject constraint to be able to test it's functionality in playground, I'm not sure if it's necessary or not.

Default Implementation of Objective-C Optional Protocol Methods

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:

Sample Image

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

Sample Image

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()
}

Sample Image

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.

How to expose existing property on Obj-C class using an extension protocol in Swift

The compiler error message


note: Objective-C method 'isEnabled' provided by getter for 'enabled' does not match the requirement's selector ('enabled')

gives a hint about the problem. The enabled property of UIButton is inherited from UIControl and in Objective-C declared as

@property(nonatomic, getter=isEnabled) BOOL enabled

Therefore the protocol method has to be

@objc protocol Enableable: class {
var enabled: Bool { @objc(isEnabled) get set }
}

and the implementation (similarly as in Swift 1.2 error on Objective-C protocol using getter):

extension UIImageView: Enableable {
var enabled: Bool {
@objc(isEnabled) get {
return alpha > DisabledAlpha
}
set(enabled) {
alpha = enabled ? EnabledAlpha : DisabledAlpha
}
}
}

Non-'@objc' method does not satisfy optional requirement of '@objc' protocol

While I think I can answer your question, it's not an answer you will like.

TL;DR: @objc functions may not currently be in protocol extensions. You could create a base class instead, though that's not an ideal solution.

Protocol Extensions and Objective-C

First, this question/answer (Can Swift Method Defined on Extensions on Protocols Accessed in Objective-c) seems to suggest that because of the way protocol extensions are dispatched under the hood, methods declared in protocol extensions are not visible to the objc_msgSend() function, and therefore are not visible to Objective-C code. Since the method you are trying to define in your extension needs to be visible to Objective-C (so UIKit can use it), it yells at you for not including @objc, but once you do include it, it yells at you because @objc is not allowed in protocol extensions. This is probably because protocol extensions are not currently able to be visible to Objective-C.

We can also see that the error message once we add @objc states "@objc can only be used with members of classes, @objc protocols, and concrete extensions of classes." This is not a class; an extension to an @objc protocol is not the same as being in the protocol definition itself (i.e. in requirements), and the word "concrete" would suggest that a protocol extension does not count as a concrete class extension.

Workaround

Unfortunately, this pretty much completely prevents you from using protocol extensions when the default implementations must be visible to Objective-C frameworks. At first, I thought perhaps @objc was not allowed in your protocol extension because the Swift Compiler could not guarantee that conforming types would be classes (even though you have specifically specified UIViewController). So I put a class requirement on P1. This did not work.

Perhaps the only workaround is to simply use a base class instead of a protocol here, but this is obviously not completely ideal because a class may only have a single base class but conform to multiple protocols.

If you choose to go this route, please take this question (Swift 3 ObjC Optional Protocol Method Not Called in Subclass) into account. It appears that another current issue in Swift 3 is that subclasses do not automatically inherit the optional protocol requirement implementations of their superclass. The answer to that questions uses a special adaption of @objc to get around it.

Reporting the Issue

I think this is being discussed already among those working on the Swift open source projects, but you could be sure they are aware by either using Apple's Bug Reporter, which would likely eventually make its way to the Swift Core Team, or Swift's bug reporter. Either of these may find your bug too broad or already known, however. The Swift team may also consider what you are looking for to be a new language feature, in which case you should first check out the mailing lists.

Update

In December 2016, this issue was reported to the Swift community. The issue is still marked as open with a medium priority, but the following comment was added:

This is intended. There is no way to add the implementation of the method to every adopter, since the extension could be added after the conformance to the protocol. I suppose we could allow it if the extension is in the same module as the protocol, though.

Since your protocol is in the same module as your extension, however, you may be able to do this in a future version of Swift.

Update 2

In February 2017, this issue was officially closed as "Won't Do" by one of the Swift Core Team members with the following message:

This is intentional: protocol extensions cannot introduce @objc entry points due to limitations of the Objective-C runtime. If you want to add @objc entry points to NSObject, extend NSObject.

Extending NSObject or even UIViewController will not accomplish exactly what you want, but it unfortunately does not look like it will become possible.

In the (very) long-term future, we may be able to eliminate reliance on @objc methods entirely, but that time will likely not come anytime soon since Cocoa frameworks are not currently written in Swift (and cannot be until it has a stable ABI).

Update 3

As of Fall 2019, this is becoming less of a problem because more and more Apple frameworks are being written in Swift. For example, if you use SwiftUI instead of UIKit, you sidestep the problem entirely because @objc would never be necessary when referring to a SwiftUI method.

Apple frameworks written in Swift include:

  • SwiftUI
  • RealityKit
  • Combine
  • CryptoKit

One would expect this pattern to continue over time now that Swift is officially ABI and module stable as of Swift 5.0 and 5.1, respectively.

How does one declare optional methods in a Swift protocol?

1. Using default implementations (preferred).

protocol MyProtocol {
func doSomething()
}

extension MyProtocol {
func doSomething() {
/* return a default value or just leave empty */
}
}

struct MyStruct: MyProtocol {
/* no compile error */
}

Advantages

  • No Objective-C runtime is involved (well, no explicitly at least). This means you can conform structs, enums and non-NSObject classes to it. Also, this means you can take advantage of powerful generics system.

  • You can always be sure that all requirements are met when encountering types that conform to such protocol. It's always either concrete implementation or default one. This is how "interfaces" or "contracts" behave in other languages.

Disadvantages

  • For non-Void requirements, you need to have a reasonable default value, which is not always possible. However, when you encounter this problem, it means that either such requirement should really have no default implementation, or that your you made a mistake during API design.

  • You can't distinguish between a default implementation and no implementation at all, at least without addressing that problem with special return values. Consider the following example:

    protocol SomeParserDelegate {
    func validate(value: Any) -> Bool
    }

    If you provide a default implementation which just returns true — it's fine at the first glance. Now, consider the following pseudo code:

    final class SomeParser {
    func parse(data: Data) -> [Any] {
    if /* delegate.validate(value:) is not implemented */ {
    /* parse very fast without validating */
    } else {
    /* parse and validate every value */
    }
    }
    }

    There's no way to implement such an optimization — you can't know if your delegate implements a method or not.

    Although there's a number of different ways to overcome this problem (using optional closures, different delegate objects for different operations to name a few), that example presents the problem clearly.


2. Using @objc optional.

@objc protocol MyProtocol {
@objc optional func doSomething()
}

class MyClass: NSObject, MyProtocol {
/* no compile error */
}

Advantages

  • No default implementation is needed. You just declare an optional method or a variable and you're ready to go.

Disadvantages

  • It severely limits your protocol's capabilities by requiring all conforming types to be Objective-C compatible. This means, only classes that inherit from NSObject can conform to such protocol. No structs, no enums, no associated types.

  • You must always check if an optional method is implemented by either optionally calling or checking if the conforming type implements it. This might introduce a lot of boilerplate if you're calling optional methods often.

Implement protocol partially in Objective C and partially in Swift

When building a project that has mixed Swift+Objective-C code, this is a brief summary of what happens:

  1. the bridging header is processed
  2. the Swift files are compiled
  3. the Objective-C files are compiled

The error you see occurs at step #2, at this point the compiler sees that the Objective-C class conforms to UITableViewDataSource, thus it assumes that the Objective-C code implements the tableView(_:numberOfRowsInSection:) method, thus it sees a conflict since the Swift code also implements the method.

The source of the problem is the fact that you are trying to implement some of the protocol methods in Objective-C, and some in Swift, and this is not possible, you need to either implement all the non-optional methods of the protocol either in Swift, or Objective-C.

If you really want to go this road, a possible solution would be to move the protocol conformance on the Swift side, and declare all the other methods in the Objective-C header as public methods of that class. But this might cause other problems along the way.

protocols: better to implement optional methods as stubs, or use reflection?

This is a very subjective question, so depending on your specific use-case, I think a test is in order. But I do recall seeing some posts on the use of KVO as being more expensive that specific subclassing and this question appears to be along a similar vein. I would think reflection would be more expensive than the specific delegate protocol. Keep in mind you can mark delegate methods as @optional/@required. Delegation is also a well established design pattern used extensively throughout. Once again I don't think it's possible to give a specific answer that holds up in all cases.

Accessing optional method in protocol returns scope error

class MyClass: NSObject, MyProtocol { ... }

This says that MyClass implements MyProtocol, which allows, but does not require myFunction. The compiler can clearly see that MyClass does not implement myFunction, so self.myFunction() isn't a thing. It could be a thing, but it's clearly not a thing. The protocol doesn't play into this.

You could "work around" this by saying "I don't mean MyClass; I mean MyProtocol":

func doSomething() {
(self as MyProtocol).myFunction?()
}

But this is kind of silly IMO. self doesn't implement myFunction and this is known at compile time.

If the point is to implement this via subclassing, the correct way to do that is to just implement it as empty in the superclass:

class MyClass: NSObject, MyProtocol {
func myFunction() {}

func doSomething() {
myFunction()
}
}

This is precisely equivalent to "do nothing" while allowing overrides in subclasses.



Related Topics



Leave a reply



Submit