Swift’s design promotes language features like generics and first-class protocols as key architectural components in application development. However, many of the logical patterns that arise, including ones imported from Objective-C, don’t work as we expect them to. In many cases, Swift’s type system resists certain straightforward patterns, such as constraining a property to both a class and a protocol. This talk will highlight several of these challenges, discuss the underlying causes, and evaluate workarounds.
Introduction (0:00)
My name is Michele Titolo, and I’m a Lead Software Engineer at Capital One. I’ve been doing iOS development for something like six years now.
In this post, I will talk about protocols and the promised land. I’ll address some pain points, some thoughts about language design, and I’ll show you how to make working in Swift (especially within mixed code bases) a lot easier. However, before I get into the details, I want to remind everyone of the aspects of Swift that affect our day-to-day development practices.
Truths (1:10)
First of all, Swift needs types at compile time. Those who work in Swift every day know that the compiler will start complaining as soon as it can’t find something. Of course, Swift and Objective-C have different language features, and as much as we like to try to treat them the same, they are indeed extremely different.
Protocols are first-class citizens, which means protocols can be singularly typed, unlike in Objective-C.
However, the thing I want to emphasize the most is that Swift is still very new. It was released with iOS 7 at WWDC 2013. A lot of the things in today’s post have to do with the fact that this is a really new language, especially when compared to Objective-C, which has been around for 20+ years.
Interop (2:30)
When Swift was announced, Apple was really excited and said that Swift would work great with Objective-C, and that we would be able to do everything in Swift that we could in Objective-C. However, there are some downsides.
'as?'
lies (2:48)
Are you tired of seeing as
everywhere? It happens all over the place because the type bridging between Swift and Objective-C requires you to check everything. This is mostly because Swift nil
is not valid for just anything; only some things can be nil
, and as a result, you end up having to do a lot of these as
checks which litter your code.
Apple has introduced ways of making this nicer, with if-lets and guards, but you still have as
everywhere. Then there are the times when as
lies. I had this problem last week, coming from Core Data, because lovely Core Data is in Objective-C. To be blunt, as
doesn’t work when you’re bridging from Objective-C to a Swift protocol.
Imagine that I really want to do this:
let vendor = ObjcObject()
let items = vendor.giveMeItems() as? [Fruit]
I will have an Objective-C object. I wanted to give you some fruit, but Fruit
is a Swift protocol. Objective-C doesn’t know about it, so the compiler is going to complain.
let vendor = ObjcObject()
let items = vendor.giveMeItems().map { $0 as Fruit }
Instead, you have to do this weird .map
thing, which turns out to be okay. There are other times, especially when you’re coming out of an Objective-C class, where the return type is just a generic NSArray. Sometimes you’ll put an as
in, and it turns out the array you get is not actually that type, and it doesn’t fail until a lot later.
'@objc'
spreads like a virus (4:32)
All of a sudden, you’ll get a compiler error saying “this class needs to be Objective-C because it implements this protocol,” or “this needs to be Objective-C because of some other weird reason.”
@objc protocol SomeProtocol {
func someFunc()
}
class MyClass: SomeProtocol {}
When you have a protocol that’s Objective-C, you can’t just create a Swift class that’s not Objective-C, when it implements that protocol. Swift says no.
@objc protocol SomeProtocol {
func someFunc()
}
@objc class MyClass: NSObject, SomeProtocol {}
Instead, you have to go through and write your Objective-C class. This is kind of okay, but when you start getting into nested protocol hierarchies where the base protocol is @objc
, so the sub-protocol’s @objc
, so that means any objects that inherit from either of those also need to be @objc
, you start peppering your code with @objc
all over the place, and then you can’t use any of the nice, amazing new Swift features.
There’s the also caveat that some things can’t be bridged. If you ever have a generic function inside of an @objc
class, that function won’t exist in the header that’s automatically generated for Objective-C. Function pointers have the same problem. If you have a variable that is a function pointer, it will not show up in that header file which leads to a lot of heartache.
Swift will leave out methods and variables that don’t bridge properly. I personally wish there could be a warning for this, but I haven’t been able to find anything yet.
XCTest is buggy (6:25)
I’m hoping with the new open source version of XCTest, we’ll be able to find some of these bugs. This is summed up in a radar title: “rdar://24200114: Bi-directional interop between Objective-C and Swift does not work in XCTest targets.”
This means that if you have a test target, and you write @testable import MyApp
and you want to import the bridging header for your app in an Objective-C file, your test target won’t compile. It adds something to the Swift header, and it’s just bad. Writing tests turns out to be really difficult when you’re dealing with a mixed code base, especially because you can really only test Swift with Swift. I suggest getting rid of your Objective-C tests as soon as possible, even though you do lose out on a lot of the really great dynamic language features.
Things You Expect to Work, But Don’t (7:23)
I don’t have a computer science background, being entirely self-taught. A lot of the things I’ve learned, especially terminology, have come from being on the job. As someone who has worked primarily in Objective-C for many years, coming into Swift was disorienting. I’d think, “I can do this in Objective-C, I can do this in other languages like Ruby or Python… why isn’t this there in Swift?”
Protocols (8:07)
Firstly, our love, and one of the things that Swift really hammers into our heads that we should use: protocols. Protocols are fantastic.
class Fruit {}
class Peach: Fruit {}
protocol FruitHolder {
var fruits: [Fruit] { get }
}
class PeachBasket: FruitHolder {
var fruits: [Peach] = []
}
It lets us do stuff like this. We can have really simple and obvious names for things. For instance, anything can be a “fruit holder”. One in the example above happens to be a “peach basket”. However, one thing that protocols can’t do is type overriding.
Something I really miss from Objective-C is being able to implement a protocol and then have a property or function use a sub-type of the class in the protocol. Unfortunately, this doesn’t work, but you can always go back to Objective-C and this works just fine (though the syntax is a little weird):
@interface PeachBasket : NSObject <FruitHolder>
@property(nonatomic, strong) NSArray<peach*>* fruits;
@end
Generics (9:01)
One of my biggest pet peeves with protocols is that while some aspects of generics are really powerful, they may not always be the best. We know that generic types were supposed to be stand-ins for things we want to use.
protocol Holder {
typealias Item
var items: [Item] { get }
}
class PeachBasket: Holder {
var items: [Peach] = []
}
Here, we have a holder class that has a random collection of items that doesn’t know what those items are. We just know that there will be items. From there, I can implement something very simple. This protocol is now generic, though, and that has a lot of implications.
protocol Holder {
typealias Item
var items: [Item] { get }
}
class Basket<Thing>: Holder{
var items: [Thing] = []
func add(item: Thing) {
items.append(item)
}
}
class PeachBasket: Basket<Peach> {}
What are the problems that you kind of run into, occasionally, with generic protocols? Here is slightly longer example of the different kinds of things. I’ll reference these below.
var someHolder: Holder
var someBasket: Basket
I want to reference a holder, and I want to reference a basket. I can’t do this though, since I’ll get errors on each. I was hoping to find ways to work around this, because I often found myself doing work that would benefit from having a generic type correspond to a concrete type. It’d be great and fancy, but I couldn’t do it.
var someHolder: Holder<Peach>
var someBasket: Basket<Peach>
What you can do, of course, is have a generic class. This is where generic classes and generic protocols become very different from each other, because you can reference a generic class. This is how you’re supposed to use generic classes, with the type. This is the only way to use generic protocols for variables, which is really annoying. You can do much more with function declarations, but with variables, it always has to be a super concrete type.
Covariance only works with generic classes (11:16)
For those of you who, like me, may not have known what covariance was six months ago, a simple example is the following: if a cat is an animal, a function that takes an animal can also take a cat. It is essentially subtyping. Inheritance has been a part of object-oriented programming for many years, but unfortunately, Swift generics don’t do them.
typealias Object
typealias Object: String
typealias RoundObject: Object
You can’t have a generic type alias inherit from an existing concrete type. You also can’t have it inherit from a generic type. There’s currently no way to specify any sort of relationship between generics with type aliases, which is kind of disappointing.
class Basket<Thing: Fruit>: Holder{
var items: [Thing] = []
func add(item: Thing) {
items.append(item)
}
}
On the other hand, instead of using a generic protocol, you can use inheritance. In this case, I have a basket that holds anything, but that thing has to be a fruit, essentially, so this is really a fruit basket.
class FruitBasket: Basket<Fruit> {}
class PeachBasket: FruitBasket<Peach> {}
Then you can go on to say: I have a fruit basket that generically holds fruit. I’d also like to specify my peach basket, which is a fruit basket that holds peaches. Except… you can’t do that!
FruitBasket
is no longer a generic class. It becomes a concrete class once it inherits from a generic class. People like saying “it’s generics all the way down,” but it’s really not. Generics are really only at one level, unless you have generics inherit from generics, which is not something I recommend doing.
class Bag<Stuff> {
class Basket<Thing: Stuff>: Holder{
var items: [Thing] = []
func add(item: Thing) {
items.append(item)
}
}
}
What about nested types with generics? It seems like a great thing to do, but you can’t nest generic types. You can’t nest generic classes inside of generic classes. Basically, any kind of generic class has to be a top-level class, which is disappointing.
New Language, New Patterns (13:48)
It’s not all bad, though. There are some really fantastic things about Swift. I’ll discuss some new language features and patterns, and hopefully these will be a little more fun.
Properties can have 1 type (14:04)
Swift is a statically-typed language, meaning that you have one of things. I was doing a refactoring recently where I was making our ViewControllers all conform to really awesome protocols, because protocols are powerful things in Swift.
protocol Themeable {}
class ListViewController: UIViewController, Themeable {}
var themedViewController: // UIViewController, Themeable ????
For instance, I wanted to create themeable ViewControllers so that I could automatically style the navigation bar and back buttons without having to write a ton of extra code. However, because of Swift’s type limitations, I had to have a reference to my ViewController. I wanted it to be a reference to the theme ViewController, because I wanted to present it and style it.
Two solutions (14:52)
I came up with two possible solutions, but it took me a couple days to figure out the right way to do it. Take a look for yourself, and guess which one I went with!
Solution 1: Swift-y but hack-y
public protocol ViewControllerProtocol {
var view: UIView! { get }
var storyboard: UIStoryboard? { get }
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
func viewDidLoad()
// etc
}
protocol ThemeableViewController: Themeable, ViewControllerProtocol {}
var themedViewController: ThemeableViewController
One way to do it is by creating a ViewControllerProtocol
. You put any sort of method you want to call on a ViewController
, such as viewDidLoad
, viewWillAppear
, present
, push
, or pop
, into a protocol. That is why it’s Swift-y, but hack-y.
Then your ViewController
, which is a UIViewController
subclass, inherits from a protocol that implements both your theme and your ViewControllerProtocol
. If you’ve ever watched the WWDC videos that Apple has, this is a protocol composition. They really, really want you to do protocol composition. However, the fact that Apple doesn’t have any protocols for us to compose ViewControllers or any UIKit classes with makes me sad. File lots of radars, please!
This was one of the ways I found, but you’re relying on the fact that UIKit’s not going to change. UIKit does change (because APIs change), and every time you want to add or use a method that’s in UIViewController
header that’s not in your protocol, you have to go through and add it. It’s a lot of manual work. ViewControllers are the powerhorses of the application, and even in Swift, we need to be able to have full use of all of the different things that ViewControllers do. I didn’t go with this one, because I wanted to use UIViewController
in case it was easier to refactor down the line.
Solution 2: Also swift-y, also hack-y
var themed: Themeable {
get {
return self.viewController as! Themeable
}
set(newThemeable) {
if let themedViewController = newThemeable as? UIViewController{
viewController = themedViewController
}
}
}
var viewController: UIViewController
init<T where T: Themeable, T: UIViewController>(viewController: T) {
self.viewController = viewController
}
This ended up using a computed property, which I’ve found to be extremely useful for things like returning different values based on state. Here, I have two variables: one that’s my actual ViewController
, and then one that is of the Themeable
protocol type. I just override the computed property so that it returns or sets the ViewController
.
If anyone has a better solution to this, please tell me. It’s one thing when you’re doing this once or twice, but if you’re doing an entirely Swift application and you really want to use protocols, this is a severe limitation on the kind of architecture that you can choose. You can’t reference, unless you create base classes. If you’re okay with base classes, then you’re fine, but we really wanted to avoid the whole base class thing because you quickly get seven layers deep.
Can’t override type in a subclass (even if it’s a subtype) (18:19)
I really want to be able to do this!
class FruitBasket: Basket<Fruit> {
var ribbonColor: UIColor
}
class PeachBasket: FruitBasket {
var ribbonColor: Pattern
}
There’s simply not a lot of support for deep object hierarchies in Swift. It’s not something that the language designers thought was worth the time and effort to make work, so there are a lot of these weird little situations where you think you’ll be able to insert a subtype, and then it won’t work. Generics help with that a little bit, but then you have the generic syntax all over the place. For people who are new to Swift, the generic syntax is like an alien language, and their eyes go kind of blank. You’ll see them stare at it for a few minutes, then they’ll Google something, and then they’ll go back and stare at it for a few more minutes. It’s not a fantastic experience for a lot of developers, especially ones who work in Objective-C, to have code littered with generics everywhere. It’s kind of a bummer, especially for a lot of Objective-C paradigms that we’re used to.
Generic non-NSObjects have issues. When they were creating generic classes, they only wrote tests for NSObject. If you’re ever making a generic class that’s not NSObject, like UIViewController
, UIView
, UIButton
, UILabel
, or NS-anything that’s not NSObject, it’s just not going to work well right now.
So, this was a rant the limitations in Swift. That being said, I work in Swift every single day, and I’m simply talking about my pet peeves. There are not nearly as many as I thought there would be. Swift is really fantastic. I talked a lot about the problems and pain points, and some weird things you have to do to work around them sometimes, but Swift is definitely a really interesting and useful language. I enjoy working in it every day, even when I have to bang my head against these bugs every once in a while.
Q&A (20:49)
Q: I noticed you did the as!
when returning the ViewController that was Themeable. Is that something that you would say is okay? I see people do that, but I try to always avoid that.
Michele: There are a couple situations where I find the as!
unavoidable. Usually, it comes from bridging. I had a Core Data problem last week, for instance, where I had a NSManagedObject
subclass that I had conform to a Swift-only protocol. I knew that the object conformed to the protocol. It was there, I could see it, and I knew that that’s the object I was getting back. I could type check it and everything. However, I had to use the as!
because there are some weird things where it doesn’t necessarily see it all the time, especially in bridging. It’s something I do like to avoid, although I have been using property!
a lot recently, but that’s for other unrelated reasons. Use it very sparingly, and as much as you can, use guards and if-lets to wrap all those cases. Sometimes the type checker will lie if you’re just doing an as?
, so you need to make sure that things are the right type, especially when coming from Objective-C.
Q: What are your favorite things about Swift that you’ve found so far?
Michele: I really do love generics. As much trouble as they’ve been to get compiling sometimes, and they’re definitely buggy with things like UIViewController
subclasses, I created this beautiful module where there are a couple spots in our app that show very similar screens, but they’re not quite the same. There was already a base class that handled a lot of that logic, so I turned it into an abstract base class. No one could ever use the base class ever again, because they weren’t; they were already trying to do this, but they didn’t have generics. I turned the base class into an abstract base class, and then passed in the types, and it worked beautifully.
Generics do help a lot with with the single-typed-properties thing too. If you have an initializer and you want to make sure you have a ViewController
that’s also themable, you can actually add that annotation to your initializer functio so that you can be sure at compile time that whatever’s getting passed in there conforms to both protocols. Little things that are really fantastic. Plus, I love function pointers. I stick functions in arrays all the time, it’s so great!
Q: If you are used to using delegation a lot in Objective-C, and you’re moving over to Swift with all the different new protocol types, what are some of the warnings that you’d need to watch out for, or what are different design changes that you’d need to think about?
Michele: Delegation hasn’t really changed much. In fact, it’s probably even more popular because protocols are so much more important in Swift. As always, you need to worry about your retain cycles, because Swift is still memory managed under ARC.
Also, be careful about composition, especially if you have a delegate that is a composition of other things. You first want to make sure that it doesn’t have any conflicts, especially when you’re working with really large projects. I’d more be careful about creating deep delegate compositional hierarchies, because you’re going to run into similar problems that really deep object hierarchies have where you want to use a protocol because it has the one function, in an extension that you want to use, but it means inheriting all of this other cruft. Keep things small and specific to what you’re doing.
Protocol extensions are also really great with delegates, because for simple things you can have it return the same value, or only override it when you need to. Make sure to take advantage of those.
Q: You didn’t mention specifically how you’re doing your interop. You can add Swift to your existing legacy project, or you can create a framework that’s nicely isolated. Is there a benefit to do it one way or the other, or what are the gotchas that you deal with?
Michele: Capital One started adding Swift to their project before I started. When I joined, they were still supporting iOS 7, and the current app still supports iOS 7, so frameworks are out. You have to consider what version of iOS you’re supporting, because iOS 7 is drastically different than 8 or 9. Otherwise, it really depends on how much you want to deal with bundles. A lot of the time, for assets, fonts, and storyboards, people just tend to use NSBundle mainBundle
, and as soon as you move things into a framework for your UI code, it starts getting messy. Application logic, requests, etc., there’s no point to having that in your main application if you don’t need to, especially if you can have a separate test suite set up to just test those things, so that when people make changes they don’t break. Personally, I like refactoring out that application logic in general, because it really separates your UI code from a lot of the underlying causes. You can refactor your UI and not have to worry about mucking about with all of this other code. Also, please don’t make APR requests in ViewControllers. We all do, but don’t do it!
Receive news and updates from Realm straight to your inbox