Swift alternative to respondsToSelector: - swift

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.

Related

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
)

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

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

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.

How to represent an optional Bool (Bool?) in Objective-C?

I am trying to write a protocol in swift
#objc protocol RestAPIManagerDelegate {
optional func credentialValidated(isValid: Bool?)
}
But I am getting following error:
'Method cannot be marked #objc because the type of the parameter cannot be represented in Objective-C'
Any suggestion?
The problem is this type declaration:
`isValid: Bool?`
That is perfectly fine in Swift. But you cannot expose it to Objective-C, because Objective-C does not have any notion of an Optional BOOL - a BOOL in Objective-C is basically just a number, a primitive C datatype (what we call a scalar).
Here's another way of looking at it. In an interchange with Objective-C, you can use a Swift Optional anywhere that Objective-C can say nil - indeed, to a large extent Swift Optional exists exactly in order to deal with the possibility that Objective-C will say nil or that you might need to say nil to Objective-C. But a BOOL in Objective-C can never be nil - it can only be YES or NO (Swift true or false).
So you have three choices:
Take away the #objc that exposes all this to Objective-C
Remove the Optional and just declare that type a Bool
Use an object type. For example, declare the type as AnyObject? (or NSNumber?). This will work because Swift will bridge a Bool to an NSNumber (including as it passes into an AnyObject), and Objective-C will deal just fine with an Optional AnyObject or Optional NSNumber because those are object types, not scalars.
Object-C does not have the concept of optionals, try to remove the "?" from your declaration
It's OK to use NSNumber? instead, but there is another way.
You can create OptionalBool enum and use it instead of Bool? for compatibility with Objective-C.
Creating OptionalBool enum in Swift code:
#objc enum OptionalBool: Int {
case none
case yes
case no
}
#objc protocol RestAPIManagerDelegate {
#objc optional func credentialValidated(isValid: OptionalBool)
}
Using OptionalBool in Objective-C code:
#interface RestAPIManagerHandler () <RestAPIManagerDelegate>
#end
#implementation RestAPIManagerHandler
- (void)credentialValidatedWithIsValid:(enum OptionalBool)isValid {
switch (isValid) {
case OptionalBoolYes:
NSLog(#"TRUE");
break;
case OptionalBoolNo:
NSLog(#"FALSE");
break;
case OptionalBoolNone:
NSLog(#"NULL");
break;
}
}
#end
Swift Bool are converted in NSNumber so for example if a Swift method returns [Bool] and if you receive this array in Objective-C code, it become a NSArray <NSNumber *>. And if you give this array to a Swift method which receives a [Bool] as parameter, the conversion will be done automatically.
If you are wanting to just do a protocol in Swift, for use within Swift, without optionals you can do this:
public protocol Note {
var content: [ContentType] {get}
func insertNote(note: Note) -> Bool
}
That will force a class to implement a method that returns a Bool and have an array of some class called ContentType.
If you want to make the method optional, you must specify the objc keyword as you have done, regardless if you are interoperating with Objective-C or not. At that point, everything else should work in your example.
#objc protocol RestAPIManagerDelegate {
optional func credentialValidated(isValid: Bool?)
}
From Apples documentation:
Optional protocol requirements can only be specified if your protocol is marked with the #objc attribute.
This attribute indicates that the protocol should be exposed to Objective-C code and is described in Using Swift with Cocoa and Objective-C. Even if you are not interoperating with Objective-C, you need to mark your protocols with the #objc attribute if you want to specify optional requirements.
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.
The example given by Apple:
#objc protocol CounterDataSource {
optional func incrementForCount(count: Int) -> Int
optional var fixedIncrement: Int { get }
}

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.