I'm a software developer based in the UK. I am blogging regularly about software development & Apple

Objective-C protocols in Swift

Optional objective-c protocols in Swift

Any new swift developer has to deal with Swift protocols early on and any veteran iOS-developer knows protocols already from objective-c. So I don't want talk about how to define protocols or when to use them. Instead I'd like to talk about some peculiarities I encountered when using objective-c protocols in swift.

The other day I wanted to write a test for my swift class that was using an objective-c protocol, which looked similar to this one here

@objc protocol NumberServerProtocol : class {
func produceNumber() -> UInt
optional func produceEvenNumber() -> UInt
optional func produceOddNumber() -> UInt
}

To illustrate the problem let's just quickly define a class which conforms to that protocol:

class NumberServer : NSObject,NumberServerProtocol {
    func produceNumber() -> UInt {
        let number = arc4random() % 1000;
        return UInt(number);
    }
    
    func produceEvenNumber() -> UInt {
        let number = produceNumber()
        let evenNumber = (number % 2) == 0 ? (number == 0) ? number+2 : number : number+1;
        return evenNumber;
    }
    
    func produceOddNumber() -> UInt {
        return produceEvenNumber()+1;
    }
}

In my Swift test I wanted to verify the output of all three methods to make sure they work correctly. So let's just look at each method call and ignore the actual test for the time being. The required protocol method gets called like this:

let server = NumberServer()
let number = server.produceNumber()

Well, nothing wrong with that. Let's move on and call the optional methods:

let evenNumber = server.produceEvenNumber()
let oddNumber = server.produceOddNumber()

Wait, you say. That's not what you would write to call those optional methods. Well, I agree. I didn't originally try to call them this way either. Well, I tried but the compiler insisted and I had to give in. As you might have guessed, originally I tried to call them like this:

let evenNumber = server.produceEvenNumber?()
let oddNumber = server.produceOddNumber?()

But what I got from the compiler was:

'cannot use optional chaining on non-optional value of type '() -> UInt'.

So what? Both methods are optional methods. The compiler should know this. It turns out to get the proper compiler support we have to cast the class to the delegate type like this:

let server = NumberServer()
let numberProtocol = server as NumberServerProtocol
let number = numberProtocol.produceNumber()
let evenNumber = numberProtocol.produceEvenNumber?()
let oddNumber = numberProtocol.produceOddNumber?()

Only then do we get the swift compiler support we love and appreciate. If you ask me why the cast is necessary,  I honestly don't know. The compiler should have all the necessary information

  1. a method gets called
  2. the method is part of a protocol the class declares
  3. the method is declared as optional in that protocol

Please let me know if you know the answer. I would like to know why.

 

 

 

 

 

 

 

 

 

 

 

 

 

Writing tests with Quick & Nimble