Non-'@Objc' Method Does Not Satisfy Optional Requirement of '@Objc' Protocol

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.

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

The problem is that you’re defining these methods in a protocol extension. This is used to define a “default implementation” for a protocol (i.e. if a type doesn’t implement the method, the protocol’s implementation will be called).

But Objective-C doesn’t have the concept of default implementations for protocols. So it doesn’t makes sense to declare the protocol as @objc and have default implementations within the Swift protocol extension. An Objective-C class conforming to this protocol would never be able to enjoy these Swift default implementations.

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

I've downloaded YPSignatureView, and did similar to what you have. All I implemented was:

func didStart(_ view : YPDrawSignatureView) {}
func didFinish(_ view : YPDrawSignatureView) {}

and of-coarse assigning a delegate to the view, with those methods implemented by the delegate. And I don't get any errors. Make sure you haven't accidentally changed the YPSignatureView.swift file by clicking on the red button that says Fix as a suggestion. Before I implemented didStart and didFinish, I did get the same error as you have, with a button that says Fix in YPSignatureView. Clicking on that silently changes code within YPSignatureView.swift. Make sure YPSignatureView.swift is pristine and implement the above two functions and you should be fine. Re-download the file to be safe, implement those two methods as above, and thats it.

My didStart() and didFinish() functions are called when touches begins and finishes respectively. Let me know how you go.

Update: as mentioned above, you probably clicked on these two error messages here:

Swift Sample Image 4

Do not do that. If you did then get a fresh copy of that file then just implement the protocol methods without any @objc. Hit Command + Shift + k to clean build folder and then build again. I did not get any errors after that, runs fine, and yours should too.

Swift protocol implementing another @objc protocol

This is due to the fact that Obj-C runtime uses Dynamic dispatch (dynamic lookup/message passing) to conforming object to know if it confirms the required method and invoke it.

Swift protocol extension by default use static dispatch. In a sense, when UISearchBarDelegate passes a message to your class that conforms to this protocol it can't find the extension method as this is static to the protocol. And hence your implementation is neither detected by the runtime nor does it work.

This is a very deep topic. I don't expect you to be clear with my simple paragraph. However, I tried this when Swift Protocol Extension was new (trying to generalize UITableViewDelegate and DataSource) but all in vain. I had to research into why and I can tell you from my experience and research this has to do with Objc messaging passing relying on dynamic dispatch and protocol extension being statically dispatched.

Candidate is not '@objc' but protocol requires it

From what I can tell, marking your protocol as @objc means that any classes implementing it also have to be exposed to Objective-C. This can be done either by making Vicki a subclass of NSObject:

class Vicki: NSObject, Speaker {

Or by marking each implemented method as @objc:

class Vicki: Speaker {
@objc func Speak() {
print("Hello, I am Vicki!")
}
@objc func TellJoke() {
print("Q: What did Sushi A say to Sushi B?")
}
}

Update: From Apple's Swift Language Documentation

Optional protocol requirements can only be specified if your protocol is marked with the @objc attribute.

...

Note also that @objc protocols can be adopted only by classes, and not
by structures or enumerations. If you mark your protocol as @objc in
order to specify optional requirements, you will only be able to apply
that protocol to class types.

Optional can only be applied to members of an @objc protocol

Swift doesn't allow protocols to have optional requirements-- if the protocol declares something, it's required. Objective-C has long had the idea of optional requirements, and Swift recognizes this when you use @objc when declaring the protocol. So using @objc is the easy way to get what you're after.

If you want a pure Swift solution, you need to add a protocol extension that includes default implementations of the methods. This doesn't make those methods optional, instead it says that any class that doesn't implement the method itself will use the default implementation instead. They're sort of optional in that classes don't have to implement them, but not really optional since that's only because a default implementation is available.

That would look something like this:

protocol DrawViewProtocol : class{
func drawViewDidEndEditing()
}

extension DrawViewProtocol {
func drawViewDidEndEditing() {}
}

class MyClass : DrawViewProtocol {

}

class MyOtherClass : DrawViewProtocol {
func drawViewDidEndEditing() {
print("Foo")
}
}

Now if I create an instance of MyClass and call drawViewDidEndEditing(), it uses the default implementation, which does nothing. If I create an instance of MyOtherClass, the same method call prints "Foo".

Swift 3 ObjC Optional Protocol Method Not Called in Subclass

tl;dr you need to prefix the function declaration with its Objective-C declaration, e.g.

@objc(tableView:heightForRowAtIndexPath:)
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// return stuff
}

I was tipped off to this being a solution thanks to the Swift 3 Migration Guide which states:

If you implement an optional Objective-C protocol requirement in a subclass of a class that declares conformance, you’ll see a warning, “Instance method ‘…’ nearly matches optional requirement ‘…’ of protocol ‘…’”

• Workaround: Add an @objc(objectiveC:name:) attribute before the implementation of the optional requirement with the original Objective-C selector inside.

I'm fairly certain this is a bug: it appears that the the runtime dynamism that allows checking for selector capability does not get properly bridged in the Grand Swift Renaming when the protocol method is in the subclass. Prefixing the function declaration with the Objective-C name properly bridges the Swift to Objective-C and allows Swift 3 renamed methods to be queried with canPerformAction:withSender: from within Objective-C

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.



Related Topics



Leave a reply



Submit