Swift, Meet Objective-C

Swift is here to stay, but Objective-C is not going to fade away quickly. Many developers have existing codebases, but they also have deadlines, and business decisions. There is no fast way to rewrite an app in Swift. So let’s face it, many of you will probably be writing in Objective-C for a while yet.

But that’s okay! You don’t need to start from scratch; there are many ways to mix a little Swift into your Objective-C. In this talk, Daniel Tomlinson shows us how to do just that. We learn how to bridge into Objective-C from Swift, making the best use of your existing code’s functionality. Then we reverse the process, and look at which Swift concepts survive the call from Objective-C. There are limitations to both, but if you want to get started, it’s a whole lot easier than you might think.


On the Way Out (0:00)

Most Swift demos start with a new project or Playground, both of which are great for exploring the language. But you don’t need to start from scratch. Many of you already have apps in the store, or in development, and many of you will have spent years on debugging, polishing, and optimization. You don’t want to throw all of that away, only to start over again. Let’s face it, you’re probably still going to be using Objective-C for a while. You might want to rewrite your app in Swift, but that won’t happen: you have deadlines, and you have business decisions.

Objective-C is not going away quickly, but it is definitely on the way out. Most of the Objective-C you write today is going to be technical debt. Applications are getting significantly more complex, and I don’t just mean the yearly UI changes. Business logic and other internals are doing more and more, as mobile performance approaches that of our laptops. It’s clear from Apple’s marketing push, and communities like this, that Swift is definitely the future of Cocoa development. We need to look towards that future, whether by improving our existing API designs with Swift in mind, wrapping libraries and frameworks, building small new features in Swift, or, in general, just writing more where we can.

Swift is a rapidly evolving language, both in its own implementation, and in our perceived best practices. Some people are really pushing functional programming, while others abide by classes and inheritance. Until this settles down, you definitely need to define a style that works for you and your team. I’d like us to reach a point where we have a comfortable middle-ground between functional, reactive, imperative, and declarative styles, and we take advantage of each of them when they most suit.

As the Swift compiler changes, parts of your codebase will need adapting before it compiles again. You need to be aware of that, because it will cost you time to adapt. Fortunately, both languages are great tools for Cocoa development. You don’t need to feel like you have to stick to one or the other. If something is better suited to Objective-C’s dynamicism, then write in Objective-C! If Swift’s type safety and better closures help, then use that!

Starting Out (4:41)

Let’s face it, Swift is far more approachable than Objective-C. The talent pool is already rapidly expanding with developers who don’t want to use Objective-C. People coming from JavaScript, Rust, Haskell, and the like are all going to see points of familiarity in Swift. If you don’t support Swift, or hire for Swift, you’ll be locking yourself out from a fantastic group of talented, enthusiastic, self-motivated developers — the kinds of people you probably want to work with. So how do you get started?

Adding Swift to your existing project can be as simple as creating a .swift file, and a bridging header. Your first simple Swift integration might be a simple button or a table for your data source.

To start, create a new Swift file, then ask to generate a bridging header.

//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

#import "Person.h"
#import "PersonController.h"
#import "CoreDataController.h"

Objective-C -> Swift (5:02)

Much of the bridging you’ll want to do lightly uses Objective-C from within Swift. This layer works really well, especially in Swift 1.2, with additions like Objective-C bridging for some enums. A bridging header exposes your Objective-C classes to Swift. Any Objective-C headers imported in the bridging header file will be visible to Swift. The Objective-C functionality will be available in any Swift file within that target, automatically, without any import statements. You can use your custom Objective-C code in the same Swift syntax you use with the system frameworks. But how do these Objective-C APIs translate into Swift?

Method Names (5:49)

Objective-C methods bridge pretty well to Swift. Without the nullability annotations introduced in Swift 1.2, everything bridges as an implicitly unwrapped optional. When bridging in simple cases, we get a nice and clean interface. However, in cases where the method name differs from the variable, you see weird, and potentially confusing, Swift syntax.

// simple case
// Objective-C
- (instancetype)initWithArray:(NSArray *)array title:(NSString *)title;
// Swift
init!(array: [AnyObject]!, title: String!)

// potentially confusing
// Objective-C
- (BOOL)application:(UIApplication *)application
            didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
// Swift
func application(application: UIApplication,
            didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool

Nullability (6:25)

When Swift first came out, the Objective-C interfaces didn’t bridge quite as nicely. There was a mass of implicitly unwrapped optionals that would often cause apps to crash, due to unhandled nils or verbose chains of things that were never nil. Swift 1.2 ships with an updated version of Clang, which brings new property attributes and pointer annotations that allow you to indicate whether a pointer — be it an Objective-C property, method parameter, or return value — can, or won’t ever be nil.

  • __nonnull indicates that the pointer will never be nil. Pointers annotated with this are imported into Swift as their non-optional base value, for example, NSData.
  • __nullable indicates that the pointer can be nil, in general practice, and that comes in as optional.
  • __null_resettable indicates that, while a property will always have a value, it can be reset by assigning nil. These are imported as a relatively safe, implicitly-unwrapped optional.
  • null_unspecified continues the current functionality of importing into Swift as an implicitly-unwrapped optional. You should never really do this, as it’s just the default.
// BEFORE:
// Objective-C
@interface XYZList : NSObject <NSCoding, NSCopying>
- (XYZListItem *)itemWithName:(NSString *)name;
@property (copy, readonly) NSArray *allItems;
@end

// Swift
class XYZList : NSObject, NSCoding, NSCopying {
    func itemWithName(name: String!) -> XYZListItem!
    @NSCopying var allItems: [AnyObject]! { get }
}

Before we had nullability, code would look like the above. We didn’t really know what to expect from our Objective-C API. If I pass nil, will my app die? Am I going to get nil back from allItems, or will I get an empty array? When we add new annotations, the interface becomes much cleaner and easier to use. Previously, you had to rely on documentation existing for the method if you wanted to see how it behaved, or, failing that, you had to hope that the method was clear enough to determine exactly what would be returned. By moving that information to the interface, it’s much clearer, and the compiler can better reason about what we will get back.

// AFTER:
// Objective-C
@interface XYZList : NSObject <NSCoding, NSCopying>
- (XYZListItem * __nullable)itemWithName:(NSString * __nonnull)name;
@property (copy, readonly) NSArray * __nonnull allItems;
@end

// Swift
class XYZList : NSObject, NSCoding, NSCopying {
    func itemWithName(name: String) -> XYZListItem?
    @NSCopying var allItems: [AnyObject] { get }
}

Swift -> Objective-C (8:20)

What about those cases where you want to call a Swift feature from Objective-C? This layer is not quite as forthcoming. When bridging Swift to Objective-C, we’re limited to things that can be represented in the Objective-C runtime. This can be pretty painful. For example, you cannot represent a Swift struct in Objective-C, or associated types of enums. So what can we represent?

Classes (8:45)

We can represent classes that are marked with @objc or descend from NSObject, but not pure Swift classes. Although generic classes will appear in the -Swift header, but any method or property that uses generics won’t be visible. When a class is bridged to Objective-C, it is given the correct nullability specifiers. It bridges collections to their Cocoa equivalent. Variable Swift collections will be changed to their immutable counterparts. Unfortunately, an Objective-C class cannot subclass a Swift class.

// Swift
@objc class MySwiftNSObject {
    var array: [String]
    init(array: [String]) {
        self.array = array;
    }
}

// Objective-C
@interface MySwiftNSObject
@property (nonatomic, copy) NSArray * __nonnull array;
- (instancetype)initWithArray:(NSArray * __nonnull)array OBJC_DESIGNATED_INITIALIZER;
@end

Protocols (9:25)

We can also represent non-generic @objc protocols in Objective-C, but not Swift-only or generic protocols. They didn’t change that much, so they translate fairly directly. A struct cannot conform to an @objc or an NSObject dependent protocol.

@objc protocol Edible {
    func eat()
}

@protocol Edible {
    - (void)eat;
}

Methods (9:51)

We can represent non-generic methods, but generic methods and methods with default parameters are not bridged. Here, our genericFunction and defaultParameterFunction are not bridged to Objective-C. This is because they can’t be represented in the runtime.

func genericFunction<T>(param: T) // Not bridged
func defaultParameterFunction(param: String = "Hi") // Not bridged

func function(param: String)
- (void)function:(NSString * __nonnull)param;

Properties (10:07)

We can represent properties on bridge classes. Collections will be bridged to their Objective-C counterparts. However, this is limited because you lose the generic information. For example, an array of strings is just an NSArray. Your Swift class still gets the same property callbacks as it was if it was Swift only.

// Swift
let forname: String
var surname: String

// Objective-C
@property (nonatomic, readonly, copy) NSString * __nonnull forname;
@property (nonatomic, copy) NSString * __nonnull surname;

It’s worth noting that these will not work with KVO, and they can’t be swizzled. How do you make them KVO compliant? Use the keyword dynamic. Marking a Swift property with that keyword opts it into the Objective-C runtime, and allows for things like method swizzling and KVO. But, a word of caution: it should only really be used when absolutely required for compatibility reasons; currently, it is kind of a mess.

dynamic var name: String

object.addObserver(self,
        forKeyPath: "name",
        options: .New,
        context: &myContext)

Enums (11:02)

As of Xcode 6.3 and Swift 1.2, we can bridge @objc enums that have integer values. These are translated directly into the equivalent NS_ENUM. Being limited to simple enums can be annoying when trying to make heavy use of things like an Either or Result type.

@objc enum MyEnum : Int {
case SomeValue = 0
case AnotherValue = 1
}

typedef NS_ENUM(NSInteger, MyEnum) {
    MyEnumSomeValue = 0,
    MyEnumAnotherValue = 1
};

Bridging Limitations (11:30)

The main limitations of bridging are that we do not get structs, functions, operators, or generics. These are considerably large limitations when we build Swift frameworks. Developers can choose to support only Swift, with the consequence of losing users. Or, they can offer an Objective-C API.

Resources + Q&A (12:09)

Q: In JP Simard’s talk at NSLondon, his approach was to create an API in Objective-C first before bridging it to Swift. Any thoughts?

Daniel: I’m doing the same approach. I think it works pretty well, and is far nicer than retrofitting Objective-C support. It is more work, but I tend to write my apps as a separate section of libraries. I just wrap them in Swift so they can be used either way.

Q: This is more of a comment, but I’ve found that using @objc at the beginning of a class will strip that class of its namespace. So, it loses the Swift namespace. If you expect that, for testing, you can get tripped up?

Daniel: I haven’t run into that issue, but that is good to know.

Brian: When you use the @objc keyword, you can also specify in parentheses the name that you want to use when you bridge it. This way, you can add some sort of namespace to it.

Thank you!



Danielle Tomlinson

Danielle Tomlinson

Danielle hails from England, but is currently embracing jet lag as a way of life. They co-organize NSLondon and ran Fruitconf. They have been building things for Apple platforms for 8 years, but now work at CircleCI and on open source libraries and tools such as CocoaPods.