Reputation: 30368
Using Swift, is it possible to test if an object implements an optional protocol method without actually calling that method? This works except for cases where the optional methods differ only by their signature.
Consider this code...
@objc public protocol TestDelegate : AnyObject {
@objc optional func testx()
@objc optional func test(with string:String)
@objc optional func test(with2 int:Int)
}
let delegate:TestDelegate? = nil
if let _ = delegate?.test(with:) {
print("supports 'test(with:)'")
}
if let _ = delegate?.testx {
print("supports 'testx'")
}
If you paste the above in a playground, it works as expected.
However, if you change testx
to test
, it no longer works.
Likewise, if you change test(with2)
to test(with)
then that won't work either.
Is there any way to test for those methods that only differ by signature?
Upvotes: 4
Views: 1818
Reputation: 80781
As also shown in How do I resolve "ambiguous use of" compile error with Swift #selector syntax?, you can explicitly coerce a function reference to its expected type in order to resolve such ambiguities.
The only difference being, as such function references are to @optional
protocol requirements done through optional chaining, you need to coerce to the optional type of the function. From there, you can do a comparison with nil
in order to determine if both the delegate is non-nil, and it implements the given requirement.
For example:
import Foundation
@objc public protocol TestDelegate : AnyObject {
@objc optional func test()
// Need to ensure the requirements have different selectors.
@objc(testWithString:) optional func test(with string: String)
@objc(testWithInt:) optional func test(with int: Int)
}
class C : TestDelegate {
func test() {}
func test(with someString: String) {}
func test(with someInt: Int) {}
}
var delegate: TestDelegate? = C()
if delegate?.test as (() -> Void)? != nil {
print("supports 'test'")
}
if delegate?.test(with:) as ((String) -> Void)? != nil {
print("supports 'test w/ String'")
}
if delegate?.test(with:) as ((Int) -> Void)? != nil {
print("supports 'test w/ Int'")
}
// supports 'test'
// supports 'test w/ String'
// supports 'test w/ Int'
Note that I've given the test(with:)
requirements unique selectors in order to ensure they don't conflict (this doesn't affect the disambiguation, only allowing class C
to conform to TestDelegate
).
Upvotes: 1
Reputation: 541
Hey MarqueIV for checking the optional you can use inbuilt function
func responds(to aSelector: Selector!) -> Bool
Returns a Boolean value that indicates whether the receiver implements or inherits a method that can respond to a specified message.
The application is responsible for determining whether a false response should be considered an error.
You cannot test whether an object inherits a method from its superclass by sending responds(to:) to the object using the super keyword.
This method will still be testing the object as a whole, not just the superclass’s implementation.
Therefore, sending responds(to:) to super is equivalent to sending it to self.
Instead, you must invoke the NSObject class method instancesRespond(to:) directly on the object’s superclass, as illustrated in the following code fragment.
Listing 1
if( [MySuperclass instancesRespondToSelector:@selector(aMethod)] ) {
// invoke the inherited method
[super aMethod];
}
You cannot simply use [[self superclass] instancesRespondToSelector:@selector(aMethod)] since this may cause the method to fail if it is invoked by a subclass.
Note that if the receiver is able to forward aSelector messages to another object, it will be able to respond to the message, albeit indirectly, even though this method returns false.
Parameters
aSelector
A selector that identifies a message.
Returns
true if the receiver implements or inherits a method that can respond to aSelector, otherwise false.
SDKs iOS 2.0+, macOS 10.0+, tvOS 9.0+, watchOS 2.0+
Upvotes: 4