Default Implementations, Swift, and Objective-C
Since WWDC we've been made well aware that Swift wants you to protocol all of the things. This is extremely useful when using a Swift project. When incrementally converting a project that has both Swift and Objective-C, adopting these very Swifty conventions can be more difficult.
Protocol Extensions
Protocol extensions are, among other things, a way to provide a default implementation to a protocol method.
protocol MyProtocol {
func myMethod()
}
This defines a protocol (MyProtocol
) and defines a protocol method, myMethod()
. Classes that implement this protocol must provide an implementation of myMethod()
. That's essentially what protocols are.
A protocol extension allows you to define a default implementation of an protocol method.
extension MyProtocol {
func myMethod(){
print("Default implementation!")
}
}
Cool. Now classes (and structs) implementing this protocol can either implement their own implementation of myMethod()
, or they can rely on the default implementation. Protocol extensions are a main reason why protocols are so powerful in Swift.
The Objective-C Wet Blanket
Objective-C has protocols as well. It can even use Swift protocols (well, at least those that are marked with the objc
attribute). What it can't do, however, is use those default implementations declared in protocol extensions. To Objective-C, protocols are just a map to the messages an object can or cannot receive. It doesn't get the concept of default implementations. So what do you do if you need to work with Objective-C, but want default implementations?
In this case, we can adopt a pattern that Objective-C itself uses for NSObject
. It's not exactly pretty and requires class inheritence, which is something we were trying to get away from with protocols, but it works and can be used as a stop-gap measure until we've refactored out the last of the Objective-C from our apps.
Objective-C has an object called NSObject
. This is no surprise as almost every class in Cocoa and our own apps subclass NSObject
. However, you don't have to subclass NSObject
in order to to play nice with Cocoa which really, really likes NSObject
. What you have to do instead, is adhere to the NSObject
protocol. So, NSObject
is both a class and a protocol in Objective-C. There are even some classes in Cocoa, such as NSProxy
that take this approach.
The NSObject
protocol defines methods like isEqual:
, hash
, isKindOfClass
and many others.. Ninety-nine percent of the time, you'll simply want to subclass NSObject
so you won't have to implement all of these classes yourself; you can just rely on the, wait for it, default implementation.
You can probably see where I am going with this.
A Protocol By Any Other Name
So, let's say with the protocol we defined above, MyProtocol
, we want to use default implementations in Swift and Objective-C. In order to accomplish this, we'll need to define our protocol in Objective-C, not Swift. Let's also change the name to MyObject
while we're at it, since it will make more sense in this situation.
@protocol MyObject
- (void)myMethod;
@end
Now, in order to define our default implementation, we'll create a class with the same name.
// MyObject.h
@interface MyObject: NSObject
- (void)myMethod;
@end
// MyObject.m
@implementation MyObject
- (void)myMethod
{
NSLog(@"Default implementation!");
}
@end
Now we have a protocol and a default implementation. To use this in an Objective-C class, we now do this:
@interface CoolObject: MyObject
@end
CoolObject
both subclasses MyObject
and conforms to the MyObject
protocol. It can rely on myMethod
defined in the class or it can define its own.
So what is the advantage of this over straight subclassing? Let's see how we implement this in Swift to see.
MyObject and MyObjectProtocol
Swift won't let you have a protocol and a class with the same name. This isn't a problem, it'll just just tack on "Protocol" to the name of the protocol in question in order to distinguish the two. (It imports NSObject
as NSObject
and NSObjectProtocol
.)
Since we have MyObject
and MyObjectProtocol
to work with, we can either conform to MyObjectProtocol
and implement our own myMethod
or we can additionally subclass MyObject
in order to get the default implementation. Our app passes around the MyObjectProtocol
definition, which is super Swifty and our classes can either be MyObject
subclasses or not.
No, it isn't as good as protocol extensions as it still requires subclassing which is oh-so-not-Swifty, but when playing in the sandbox with Objective-C and Swift, we sometimes have to make some compromises in order to have Objective-C know what's going on and keeping the compiler happy. It's not perfect, but it gives us optional default implementations in both Objective-C and Swift until the day we can sweep Objective-C out the door of our app entirely.