As a refresher from Part 1 and Part 2, we’re trying to make the process of porting Objective-C to Swift code more gradual by porting classes method by method using Swift extensions. This is very helpful in keeping with the idea of “When it comes to porting, smaller steps are better.” In this part, we’ll cover something I discovered when porting my custom UIKit code to Swift.
Porting UIViews and UIViewControllers is relatively straightforward using the techniques we’ve already discussed. But delegates, your most common extension point for UIViews and UIViewControllers, can be a different story.
You do the easy thing: take that first method from the delegate protocol and move it to your Swift extension. You will probably get away with this for a while, until you move a method, and Xcode suddenly expresses its unhappiness:
Method 'tableView(_:numberOfRowsInSection:)' with Objective-C selector 'tableView:numberOfRowsInSection:'
conflicts with previous declaration with the same Objective-C selector
In this case, long ago, Apple decided to “help” you by providing a default implementation for certain methods of UIKit delegates. Whether this was a good idea in the first place is a topic for another article, but when porting the method to Swift, this causes nothing but problems.
The particular example I gave was from
UITableViewDataSource but it can occur with any protocol which provides default implementations for some of its methods. The reason behind this is: when you define a new method implementation in Objective-C, it happily replaces the default implementation provided by the SDK. In our Swift extension, this does not happen, causing the conflict build error above. The only way to not get the Objective-C default implementation is to make the Swift extension extend the UIKit delegate protocol, which means Objective-C methods cannot implement the protocol methods.
The bad news is this does break our strategy to port method by method. There is no way to make the Swift extension implementation replace the default Objective-C implementation. The even worse news is that it isn’t obvious which methods the iOS SDK has provided with default implementations, until you actually port a method — or at least remove your custom Objective-C implementation and fill in a stub Swift version — and get the conflict error. But we’re not going to take no for an answer: what can we do about it?
The good news, is that the number of instances of these methods is relatively rare, it only happens with UIKit protocols, and you can still port everything else method by method. The strategy I have employed successfully is to continue porting method by method as described in previous posts. If I complete a port and start getting the conflict build error, I comment out the Swift implementation, uncomment the Objective-C implementation (which you should still have commented out if you followed the process in Part 1), and put a comment above the Swift and Objective-C implementations that the port should be finished when no other methods in the class need to be ported.
If you’re worried about wasting porting effort you won’t get to use — until later, at least — do what I described earlier: comment out the Objective-C method and make just the stub Swift method. That will be enough to trigger the build error; if that happens, I would leave the same comments that this method must be ported last, and continue porting other methods as needed.
The general process in Part 1 and the tips in Part 2 and this article have helped me take what looked like a massive effort to port to Swift and allow me to do it as needed, on demand. Suddenly making my app all Swift doesn’t look so bad after all.