We were pleased to welcome Jonathan Blocksom at our SLUG meetup. A seasoned industry veteran, Jonathan has the real world experience of adding Swift to a large consumer-facing app with millions of users. Swift was not designed in a vacuum; working with the rich history of Objective-C frameworks was a critical part of the design. His talk covered how one can work with both languages in one project: what works well and what pitfalls there are.
As usual, video & slides are synchronized. The video is also subtitled. You can find a blog version of the talk (with code samples) below.
Swift - Obj-C Bridge Basics (0:00)
Swift and Objective-C work together fairly well, so there are many reasons why you would want to include both in a single project. There may be a handy Swift library that you want to use in Objective-C, or vice versa. If you have a big Obj-C codebase and you want to learn Swift, now would be a good time to do that as well. Swift was built for Objective-C compatibility and also works with C, but not with C++. Many types in Objective-C translate over to Swift fairly well: BOOL to Bool, NSInteger to Int, SEL to Selector, and so on.
Adding Swift to Objective-C
? and ! (7:59)
In Objective-C, pointers to objects can be nil. However, Swift doesn’t allow nil objects. A question mark is used to declare a parameter as optional (which may be nil), and test for nil. An exclamation mark implicitly unwraps optionals (which won’t be nil).
Subclassing (9:16)
You can subclass an NSObject in Swift, but it’s mostly a one way thing. You can’t subclass a Swift class in Objective-C, so if you make a Swift subclass in your existing project, it may be difficult to use that Swift later in the project.
Features in Swift Only (9:53)
Swift has many features that aren’t available in Objective-C, like generics, tuples, or top-level Swift functions. There are no Swift enums or structs, global variables, type aliases, or nested types. You can do a lot of things with these concepts in Swift. Enums and structs are not passed around by reference, so it’s kind of understandable that it would be a lot of work for the Objective-C compiler.
Demo: Adding Swift to Existing Obj-C (10:46)
If you have Objective-C in Swift files, those files have to know about the classes defined in the other Swift and/or Obj-C files. If there’s a Swift file that needs to not about an Obj-C class you’ve created, XCode will create a bridging header for you. There are no imports in Swift, besides modules, so code will just be in the Swift name space. Going the other way, XCode creates a file called [ProductName]-Swift.h and puts Obj-C versions of the Swift classes in that file.
Adding a Swift file (11:47)
Create the Swift file you want to add to your Obj-C. The public keyword is used to add permissions. The function must be in a class, and there is no @objc keyword because the class is of type NSObject.
import Foundation
class Foo : NSObject {
func bar() {
println("Hi from Swift")
}
}
In your Objective-C code, you would then have the following. Including the [ProductName]-Swift.h before you create the object and send a message as usual.
#import "DemoApp-Swift.h"
- (void)viewDidLoad {
[super viewDidLoad];
Foo *aFoo = [[Foo alloc] init];
[aFoo bar];
}
Bridging Classes in Action
Bringing Swift to Obj-C (22:03)
The following code is a Swift class with some members. It has an enum and an init method. You can use it in Objective-C because it inherits from NSObject, so you can allocate and initialize it.
public class CuriousWidget: NSObject {
enum WidgetType {
case FlobGangle
case HuffTanker
case GorbNorgle
}
var name: String
var weight: Int
var type = WidgetType.FlobGangle
override init() {
name = "Widget"
weight = 1
}
}
Basic Usage (22:55)
You can use this class in Objective-C: you can set the name and the weight. What you can’t do is set the type, which will give a compiler error, because once again, Swift enums aren’t supported in Obj-C. To access it, you either have to wrap it or do a little hackery to access it.
self.widget = [[CuriousWidget alloc] init];
self.widget.name = @"thingybob";
NSLog(@"%@", self.widget);
self.widget.weight = 2;
NSLog(@"%ld", self.widget.weight);
self.widget.type = 2; // Property 'type' not found on object of type 'CuriousWidget *'
Obj-C Class Definition (23:36)
The following code is the generated header for the Swift class. If you actually run this and ask what the name of the class is, it will print out the module name “BFFDemo.curiouswidget”, the actual running code. We don’t know yet what the exact rules for name mangling are or how it works because we don’t have access to the Swift compiler yet. More on the name mangling can be found in this blogpost.
__attribute__((objc_runtime_name("_TtC7BFFDemo13CuriousWidget")))
// Actual Class name is BFFDemo.CuriousWidget (Check via NSStringFromClass or class_getName)
__attribute__((objc_subclassing_restricted)) // Can't subclass
@interface CuriousWidget : NSObject
@property (nonatomic, copy) NSString * name;
@property (nonatomic) NSInteger weight; // Missing type enum property
- (instancetype)init __attribute__((objc_designated_initializer));
@end
Subclassing Swift @objc Classes (24:48)
Again, if you try to subclass a Swift class, you’ll get a compiler error. However, you can add category methods, as they’re called in Objective-C, or extensions, as they’re called in Swift. In Swift, you would have to create a duplicating method to access the properties. This also exposes only the public properties.
@interface CuriousWidget(DupeWithName)
- (CuriousWidget *)dupeWithName:(NSString *)newName;
@end
@implementation CuriousWidget(DupeWithName)
- (CuriousWidget *)dupeWithName:(NSString *)newName
{
CuriousWidget *newWidget = [[CuriousWidget alloc] init];
newWidget.name = newName;
newWidget.weight = self.weight;
return newWidget;
}
@end
KVO & Method Swizzling (25:54)
Another thing you can do is key value observing. You can actually make a property and use it as if it’s an Obj-C property - it can be key value observed.
[self.widget addObserver:self
forKeyPath:@"name"
options:NSKeyValueObservingOptionNew
context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
NSLog(@"Observed change, widget is %@", self.widget);
}
You can also do method swizzling. Declare your function, or your method in your Swift class with the dynamic keyword. This way, the compiler won’t optimize it away because the compiler could actually remove or in-line the method call and not do an Objective-C dynamic dispatch.
Calling Objective-C from Swift
General Rules (28:21)
If you’re programming in Swift and want to access Objective-C, there are some rules. In general, most of the name parameters pass over in a good way. The only real gotcha is the init method, where if you have something like initWithFoo:(MyFoo *)foo, it just becomes init(foo: MyFoo).
Product-Bridging-Header.h (29:44)
If you have an Objective-C class that you’ve defined, you have to add it into a bridging header. XCode creates it, but you fill it in. It’s just a list of import statements, so it’s not hard at all. However, you do have to be careful of circular imports. If your bridging header imports some classes and those classes also import, you can get a circular reference. In your .h files, just use the @ class for declaration so you don’t have to deal with that.
Options & Implicitly Unwrapped Optionals (31:25)
When you’re working with Objective-C objects in Swift, you need to be careful about how the optionals are implicitly unwrapped. Apple has done a fairly good job going through their code and annotating in their methods whether cases are questions marks or exclamation points. The standard for checking is using an if let statement.
Alamofire in Obj-C Case Study (34:50)
This case study is meant to examine a complex Swift library as it can be used in Objective-C. AlamoFire is by the same person who wrote AFNetworking. AlamoFire is a Swift library that gives you asynchronous handlers for things.
AlamoFire (36:52)
The following code is what we want to access from Objective-C, but we have to make changes.
Alamofire.request(.GET, "http://httpbin.org/get", parameters: ["foo": "bar"])
.response { (request, response, data, error) in
println(request)
println(response)
println(error)
}
Request is a top level function, which can’t be called from Obj-C, but it returns Manager.sharedInstance. The function is just a convenience function for calling a method on an object. We can bring Swift objects into Objective-C, so it compiles in Objective-C. We wrap the request method and make an Obj-C version of it. We also have to add in the @objc to set the response handler.
Misc
Testing Classes in Swift & Swift + C (48:31)
To see what classes things are in Swift, you can use the is and as? keywords. These replace isMemberOfClass and isKindOfClass in Objective-C, respectively. This is definitely more idiomatic Swift than those methods. There was a really neat talk by Mike Ash at NSSpain on using C in Swift. His talk also covers pointers. There are now types for many different pointers, and you actually run a map function on them. If you have say, a void * in Swift, that’s a C mutable pointer. You can do free and malloc, and you have access to all of the C functions in Swift.
Gotchas & Lessons Learned (50:59)
Never import a .swift file, because that will throw compiler errors. Import instead “
Swift Resources & Q&A (57:47)
- WWDC Sessions on Swift & Obj-C: Integrating Swift with Obj-C, Swift Interoperability in Depth
- Mike Ash NSBlog
- Erica Sadun Blog
- Daniel Steinberg Swift Book
- objc.io Issue #16
- Apple’s Swift iBook
Q: Did your team establish any policies for what the public interface of your Swift code would look like so that it can be accessible from Objective-C?
Jonathan: We haven’t done anything like that on purpose. We’ve also been treading fairly carefully like the enums we’ve used so far. Our enums are only used in that Swift file, none have been used in multiple files yet. I think a bigger thing has been stylistic things because it’s frustrating that Apple doesn’t have an official style guideline. Now that objects are starting to talk to each other, we are seeing more of that. The trailing closure syntax in Swift is affecting the way from of our Obj-C APIs are being written. We’ve switched from a pattern with a success and a failure block to one where we’ll combine them. The Objective-C is influencing how we’re writing the Swift.
Q: What made you choose not to go strictly to Swift in your redo of your app?
Jonathan: We were already well underway with the new version, but not so well underway that we were not going to release before iOS8.
Q: Over time, are you going to try to use only Swift in the app?
Jonathan: I would be surprised if we do because there’s a lot of existing stuff that we’d have to rewrite, a lot of which works just fine. We also have other code that we’d rather refactor first. I don’t see the advantage in rewriting everything in Swift. Other teams have a prototype all in Swift because it’s brand new, only supported by iOS8, but that has caused some interesting wrinkles with the TDD we’re trying to do. Personally, I do a lot with 3D graphics and SceneKit, but trying to use SceneKit and Swift was too much so I went back to Obj-C.
Q: Now that Swift is in production, have you done any performance testing in Swift, both on code size and execution speeds?
Jonathan: We haven’t done any yet. We will probably do some, but not until we’re closer to release.
Q: One problem I saw building an app in Swift was the compile time, which was really slow. More than 40 Swift files took 9 or 10 seconds to compile. Is this something you see as well?
Jonathan: Yes, the compile time is slow, which can be a pain. We also depend on what we’re doing on a fairly slow test cycle because we have to authenticate every time we log into our app. It’s not actually the compiler that’s the bottleneck for us so much as rerunning the testing. What I often to is do my development in a test harness project where I bring the classes I need into a new project and compile there.
Q: In the readme, I noticed Swift doesn’t map perfectly to IBOutlets and there was a workaround. Is there something that’s missing in Swift?
Jonathan: We’ve tried to use some IBDesignable things and haven’t had any luck with that either. I think it’s still a work in progress.
Q: Why does it get harder and harder to convert between CGFloat and Float?
Jonathan: I think it comes down to the fact that CGFloat isn’t a float or a double, and because Swift is so strongly typed, the compiler has a hard time dealing with that.
Q: Has your team thought about trying to publish some of your stylistic ideals for Swift?
Jonathan: I think we’ve been waiting for Apple to publish something. When I have a style question, I’ll go look at Apple’s documentation and see what they do. I don’t think we’ll necessarily publish one, but I wouldn’t be surprised if someone does.
Q: Do you or your team have any regrest about writing so much in Swift this summer or going forward?
Jonathan: The beta churn was very hard because we didn’t expect betas every two week. We had some of the betas we skipped, like beta 3. We had one person who tried out the project and figured out what needed to be changed every beta. We also worked in two week sprints as well, and it was hard to work with different betas. I’m much happier knowing what I know about Swift now than not knowing about it. Doing code reviews also helped us to get up to speed on it. We also only tried to do little pieces in Swift, so not big regrets.
Q: Can you talk about the switch from HTML5 to native code? Do they have to do native for other platforms as well?
Jonathan: We are doing native for other platforms, but we’re still looking for people to help us with that. We rewrite in native because you can get a much better experience thatn with HTML. People are doing more and more on their devices and we want it to be a seamless experience.
Receive news and updates from Realm straight to your inbox