In Swift, how can you test if an object implements an optional protocol method which differs by signature without actually calling that method? - swift

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?

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+

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).

Related

How to define function using type that references type of itself in Swift?

I've read many articles about using Self inside protocol but I am still confused how to achieve some behaviour.
Code
protocol Bindable {
#discardableResult
func bind(_ target: inout Self) -> Self
}
extension Bindable {
#discardableResult
func bind(_ target: inout Self) -> Self {
target = self
return self
}
}
Attempt
extension NSView: Bindable {} // ERROR
Error
Complain I am using Self so it can be any subclass of NSView
Protocol 'Bindable' requirement 'bind' cannot be satisfied by a non-final class ('NSView') because it uses 'Self' in a non-parameter, non-result type position
Question:
How should I implement protocol Bindable so it uses current variable type?
or
How to apply protocol to any type to make function require type of variable itself?
Tests
Everything works ... except application to NSView
class MyView: NSView {}
let view: NSView
let myTarget: MyView
NSView().bind(&view) // OK
(MyView() as NSView).bind(&view) // OK
MyView().bind(&myTarget) // OK
NSView().bind(&myTarget) // Error = OK (expected)
MyView().bind(&view) // Error = OK (expected)
It seems compiler understands and does exactly what I want to achieve. Clearly recognises type of variable and allows only its type as a parameter. It is strange that auto-completion and semantic is correct but it's not compilable.

Strongly typing a Dictionary or NSDictionary to have keys and values as per a protocol

Here's my protocol:
#objc public protocol EventListenerOptions {
#objc optional var capture: Bool { get set }
}
I have this method signature:
func addEventListener(
_ type: NSString,
_ callback: ((_ event: UIEvent) -> Void)?,
_ options: EventListenerOptions?
)
How do I invoke it? I've tried using a statically declared dictionary and it's not accepting it. The suggested fix of inserting as! EventListenerOptions produces a compiler warning (and crashes at runtime, in any case).
view.addEventListener(
"tap",
{(event: UIEvent) -> Void in
print("Got a tap event.")
},
["capture": true] // Error: Argument type '[String : Bool]' does not conform to expected type 'EventListenerOptions'
)
Requirements: I want to expose the protocol to Obj-C, so what I'm looking for is some way to get type-safety in Swift while handling an object that is easily constructed in Obj-C (so I can't use structs, to my understanding). I was hoping I could just pass in an NSDictionary casted as EventListenerOptions, but it doesn't accept that.
Unlike in some languages, such as TypeScript, Swift has nominal typing rather than structural typing. What this means is that even if an object has the shape you want, unless its class explicitly adopts the protocol, you can't pass it in.
protocol EventListenerOptions {
var capture: Bool { get set }
}
class AnEventListenerOptionsType: EventListenerOptions {
var capture: Bool
}
class NotAnEventListenerOptionsType {
var capture: Bool
}
The type of your dictionary ["capture": true] is not a class which conforms to EventListenerOptions: it is the standard library type Dictionary<String, Bool>. Not only does this type not adopt the protocol, it doesn't even have the relevant property: you have to access it by dict["capture"] rather than dict.capture. This is an important difference: the former calls the subscript(_:) accessor, while the latter accesses the capture property. (If you are coming from TypeScript, as #Alexander suggests, you're probably used to these being equivalent, but in Swift they aren't.)
As far as I'm aware, Swift doesn't have anonymous object literals as in JS, C#, Kotlin etc.
TL;DR: the solution is to create a class which conforms to EventListenerOptions.
try to add
class Listener: EventListenerOptions {
var capture: Bool = true
}
...
let listener = Listener()
listener.capture = true
view.addEventListener(
"tap",
{(event: UIEvent) -> Void in
print("Got a tap event.")
},
listener
)

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.
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:
However, with the protocol extension, we no longer have to optionally unwrap. We can take the optional unwrapping out, and the extension is called:
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()
}
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.

Swift alternative to respondsToSelector:

I was trying to implement swift's alternative to the respondsToSelector: syntax that was also shown in the keynote.
I have the following:
protocol CustomItemTableViewCellDelegate {
func changeCount(sender: UITableViewCell, change: Int)
}
and then later in the code I call
class CustomItemTableViewCell: UITableViewCell {
var delegate: CustomItemTableViewCellDelegate
...
override func touchesEnded(touches: NSSet!, withEvent event: UIEvent!) {
...
delegate?.changeCount?(self, change: -1)
}
...
}
I get the following errors
Operand of postfix '?' should have optional type; type is
'(UITableViewCell, change:Int) -> ()'
Operand of postfix '?' should
have optional type; type is 'CustomItemTableViewCellDelegate'
Partial application of protocol method is not allowed
What I am doing wrong?
Thanks
You have two ? operators, and they're both causing problems.
First, the one after delegate indicates that you want to unwrap an optional value, but your delegate property isn't declared that way. It should be:
var delegate: CustomItemTableViewCellDelegate?
Second, it looks like you want your changeCount protocol method to be optional. If you do, you need to both mark the protocol with the #objc attribute and mark the function with the optional attribute:
#objc protocol CustomItemTableViewCellDelegate {
optional func changeCount(sender: UITableViewCell, change: Int)
}
(Note: Classes that conform to #objc protocols need to be #objc themselves. In this case you're subclassing an Objective-C class, so you're covered, but a new class would need to be marked with the #objc attribute.)
If you only want the delegate to be optional (that is, it's okay to not have a delegate, but all delegates need to implement changeCount), then leave your protocol as is and change that method call to:
delegate?.changeCount(self, change: -1)
The error says it all.
You use ? on an explicit type, it can't be nil, so simply don't use ? on that variable.
If you have a var like this one
var changeCount: Int
or this
var changeCount = 3
You have an explicit type. When an explicit type is requested, then you should give an explicit type, which is changeCount and not changeCount?.
If you want optional variable to begin with, declare it with an ?:
var changeCount: Int?
You can't use literal syntax with optional type if the type should be implicit. Because 3 is always explicit Int if not stated otherwise.

Check if a func exists in Swift

I wish to check if a func exists before I call it. For example:
if let touch: AnyObject = touches.anyObject() {
let location = touch.locationInView(self)
touchMoved(Int(location.x), Int(location.y))
}
I would like to call touchMoved(Int, Int) if it exists. Is it possible?
You can use the optional chaining operator:
This seems to only work with ObjC protocols that have #optional functions defined. Also seems to require a cast to AnyObject:
import Cocoa
#objc protocol SomeRandomProtocol {
#optional func aRandomFunction() -> String
#optional func anotherRandomFunction() -> String
}
class SomeRandomClass : NSObject {
func aRandomFunction() -> String {
return "aRandomFunc"
}
}
var instance = SomeRandomClass()
(instance as AnyObject).aRandomFunction?() //Returns "aRandomFunc"
(instance as AnyObject).anotherRandomFunction?() //Returns nil, as it is not implemented
Whats weird is that in the example above, the protocol "SomeRandomProtocol" is not even declared for "SomeRandomClass"... yet without the protocol definition, the chaining operator gives an error-- in the playground at least. Seems like the compiler needs a prototype of the function declared previously for the ?() operator to work.
Seems like maybe there's some bugs or work to do there.
See the "swift interoperability in depth" session for more info on the optional chaining operator and how it works in this case.