The Slow Shift to Swift: Order of Conversion

Jul 15, 2015

As I’ve said before, switching to Swift has been fraught with danger, false starts, and frustrations. I’ve lost countless hours and source control commits during the process. But I’m gonna keep on going because: (a) I think Swift is that much of a game-changer, (b) a lot of the problems, I think, stemmed from the order in which I was trying to convert the code, and (c) I was trying to avoid the interoperability bit—don’t be that person.

When Swift was first released, there were two major conversion tactics recommended:

  1. The language is too immature, just don’t do it; not even for new projects.
  2. Start with classes that inherit from NSObject.

Since WWDC another recommendation emerged: When you add to or tweak an existing project, see if you can do it in Swift.

I’m going to describe how I am doing it for Time Journal—this time.

Leave Core Data for Last

There are some issues with testing managed objects in Core Data; namely it revolves around retrieving the objects from result arrays.1, 2

Unfortunately, I learned this the hard way as I started bashing my head against the wall trying to figure out how to do it. Initially I did the wrong thing and just abandoned testing altogether. The second go ’round I tried a workaround I found online, which seemed less than ideal.1 After filing a bug report and being informed it was a duplicate, I stopped and took a break—deciding maybe the language really was too immature.

This time, I’m thinking maybe it is just the environment related specifically to Core Data; so, saving that for last.

Start With One Class Inheriting from NSObject

Time Journal depends a lot on data handlers. These are classes that inherit from NSObject and act in a similar capacity as NSFetchedResultsController; however, they are a cross-platform implementation (as OS X uses the NSArrayController class).

I chose the data handler with the fewest #import statements, and converted it.

Use the Bridging Header to Guide You

Interoperability between Objective-C and Swift is handled via two header files: Module_Name-Swift.h and Module_Name-Bridging-Header.h.

Module_Name-Swift.h is generated by Xcode and is what Objective-C uses to see your Swift code. Module Name-Bridging-Header.h can be generated by hand (just follow the naming convention, or Xcode can do it for you). Note: When importing the bridging header, replace spaces in your module name with underscores (#import “Module_Name-Bridging-Header.h”).

Objective-C generates two files for each class, a public header (.h) and a private implementation (.m). The header files are what you use to create public APIs and import into either the header or the implementation of other classes in your project. Note: I recommend importing most into your implementation files as this will reduce the installation footprint of your application in most cases—only import in the header if absolutely necessary.3

With the PSA out of the way.

The importing of Module Name-Bridging-Header.h is automatically handled for you by Xcode and exposes Objective-C APIs to Swift; however, you have to specify which public headers you want exposed to Swift. Some people might just throw all their headers in there with a, “I’ll need ’em all eventually anyway!” mentality. Unfortunately, that really shouldn’t be the case, and will probably cause your Swift app to become larger than it needs to be. Instead, let’s take a look at the data handler conversion; here’s what the data handler class kind of looked like for Time Journal:

// DataHandler.h
#import <Foundation/Foundation.h>

// DataHandler.m
#import “DataHandler.h”

#import “SharedSingleton.h”
#import “NSManagedObjectContext+Helpers.h”
#import “Clock.h”
#import “Entry.h”

Here’s what an empty Swift file looks like with the declaration of the DataHandler class:

// DataHandler.swift
import Foundation

class DataHandler {
}

So, we don’t have to add Foundation.h to our bridging header, because it’s already being imported. But, before we go importing all the headers listed in the Objective-C version into our bridging header, we should let the requirements of the development effort guide us. Let’s start by laying the foundation for the public API.

// Objective-C DataHandler.h
-(instancetype)initWithClock:(Clock*)clock;

// Swift
init(parentClock: Clock) {}

// Briding-Header.h
#import “Clock.h”

In both Objective-C and Swift, we need a designated initializer because we set a private variable to hold a read-only clock reference that cannot be changed. In other words, this class cannot be mutated in a fundamental way; instead, a new instance is required. Because we pass in the Clock class, which Swift has no knowledge of, we get a compiler error—which is easy to fix—import the Clock.h file into the bridging header.

On to the next line in the public API.

// Objective-C DataHandler.h
@property (weak) id<EntryListDataHandlerProtocol> delegate;

// Swift
var delegate: EntryListDataHandlerDelegate?

// Briding-Header.h
// no change

Looks like the data handler uses a protocol with a delegate. We could probably import the protocol’s .h file into the bridging header, but why not build it in Swift instead (it’s a one method protocol)? So, no changes to the bridging header.

On to the next.

// Objective-C DataHandler.h
@property (strong, nonatomic, readonly) Clock *clock;

// Swift
private(set) var parentClock: Clock?

// Briding-Header.h
// no change

There’s where we make the clock variable read-only. In Swift, we use private(set), while in Objective-C, there’s an explicit readonly property and pattern.4 Still no changes to the bridging header.

Keep going until hitting this one.

// Objective-C DataHandler.h
-(Entry*)entryAtIndexPath:(NSIndexPath*)indexPath;

// Swift
final func entryAt(indexPath: NSIndexPath) -> Entry {}

// Briding-Header.h
#import “Entry.h”

There’s a a public method; so, we create the same method in our Swift implementation.5 The public method uses a class unknown to Swift; so, we add it to the bridging header.

On to the implementation!

The only big shock for me here was that I needed to import UIKit into the Swift file, because the properties of row and section are specific to UIKit.6 If I had started with the AppDelegate, I would not have had this problem, because Swift compiles by taking a look at the entire project and the imports, not just one file.

Now we attempt to run our tests, which fail due to compiler errors. Most of these are due to rewriting method declarations in a more Swift-like fashion. But, one had me stumped; it dealt with the initializer; Objective-C doesn’t understand, because there is no need to call alloc on a Swift class. The quick and dirty approach would be to make the data handler a subclass of NSObject.

// Swift
@objc class DataHandler: NSObject {
    init(parentClock: Clock) {}
}

The @objc bit allows Objective-C to see this class via the Module_Name-Swift.h header. However, if we make our data handler a subclass of NSObject, then we are no longer dealing with a Swift class, we are essentially dealing with an Objective-C class. So, instead, we can do the following.7

// Swift
@objc class DataHandler {
    init(parentClock: Clock) {}

    final func newDataHandler(parentClock: Clock) -> DataHandler {
        return DataHandler(parentClock: parentClock)
    }
}

// Objective-C
DataHandler *dataHandler = [DataHandler newDataHandler:parentClock];

Comment out all the code related to the deprecated header and implementation files. Run the tests again. They pass.

Delete the header and implementation files. Try to run the tests again. Remove all references to deleted header and implementation files.

On to the next class.

It’s a Guide, Not a Train Track

A train on a tack should not, of its own accord, leave the track. When I said to use the bridging header as a guide, that does not mean we have to implement only the classes in the bridging header next. In fact, we probably shouldn’t because: (a) the data handler didn’t inherit from a lot of source code anyway, and (b) two of the classes in there are Core Data managed objects, which would cause us to break our first rule (train track) of doing Core Data last.

Instead, I found a class that inherited from the DataHandler and did it next.

// Swift
@objc class EntryDataHandler: DataHandler {}

Working on the EntryDataHandler caused the bridging header to get much bigger as it depended on a lot more classes. The steps for the DataHandler were used to duplicate the functionality and update the architecture.

After running the tests and manually verifying Time Journal still worked, I started working on the new header files included in the bridging header. With each conversion I made sure all the tests continued to pass.

And I will continue in this vein and see where it takes me.

Conclusion

What I’m noticing by working through the files this way is that the number of dependencies in the Objective-C classes are becoming fewer and fewer—making the only import the automatically generated Swift header.

From a productivity8 perspective this should prove worthwhile. The reason I say this is because as Objective-C classes become less dependent on each other and more dependent on Swift classes, it will be easier to migrate them—they’ll almost be there already. This way of working is similar to doing the simplest thing (small class, small dependency) with a high value (without the data handlers Time Journal does not work) first; thereby, creating a foundation to inform future decisions, all while maintaining a shippable product.


  1. Core Data and Testing return to text return to text

  2. Swift cannot test Core Data in Xcode Tests return to text

  3. #imports Gone Wild! How to Tame File Dependencies return to text

  4. In the header you mark the property as readonly in the implementation, within the private interface, you mark the same property name as readwritereturn to text

  5. The final keyword means this implementation is the last implementation of that method construction. Swift has three levels of access control: public (all files within a module), internal (within the same file, if you have multiple classes in a file maybe, I haven’t tried yet), and private (within the higher-level closure {}). Beyond the access control, Swift also grants the ability to limit what inheriting classes can do—using the final keyword says, “I know you’re my kid, but I’m the only one who can do this in this way.” Generally, I recommend going from private (to internal) to final to public. In other words, lock everything down from the start; otherwise, you end up with properties and methods at a level of accessibility that is unnecessary and can introduce defects that could have otherwise been avoided. return to text

  6. Xcode 6.3.2 Swift 1.2 NSIndexPath does not have member ‘row’ return to text

  7. Objective-C asking for alloc on swift class return to text

  8. At 8fold Productivity we define productivity as balancing efficiency (less waste), effectiveness (higher quality), and speed (faster or slower, depending on what is best). return to text

###