In this article written by Kyle Begeman author of the book Swift 2 Cookbook, we will cover the following recipes:
Swift 2 is out, and we can see that it is going to replace Objective-C on iOS development sooner or later, however how should you migrate your Objective-C app? Is it necessary to rewrite everything again?
Of course you don't have to rewrite a whole application in Swift from scratch, you can gradually migrate it. Imagine a four years app developed by 10 developers, it would take a long time to be rewritten.
Actually, you've already seen that some of the codes we've used in this book have some kind of "old Objective-C fashion". The reason is that not even Apple computers could migrate the whole Objective-C code into Swift.
(For more resources related to this topic, see here.)
In the previous recipe we learned how to add a new code into an existing Objective-C project, however you shouldn't only add new code but also, as far as possible, you should migrate your old code to the new Swift language.
If you would like to keep your application core on Objective-C that's ok, but remember that new features are going to be added on Swift and it will be difficult keeping two languages on the same project.
In this recipe we are going to port part of the code, which is written in Objective-C to Swift.
Make a copy of the previous recipe, if you are using any version control it's a good time for committing your changes.
class Setup {
class func generate() -> [Car]{
var result = [Car]()
for distance in [1.2, 0.5, 5.0] {
var car = Car()
car.distance = Float(distance)
result.append(car)
}
var car = Car()
car.distance = 4
var van = Van()
van.distance = 3.8
result += [car, van]
return result
}
}
- (void)viewDidLoad {
[super viewDidLoad];
vehicles = [Setup generate];
[self->tableView reloadData];
}
The reason we had to create a class instead of creating a function is that you can only export to Objective-C classes, protocols, properties, and subscripts. Bear that in mind in case of developing with the two languages.
If you would like to export a class to Objective-C you have two choices, the first one is inheriting from NSObject and the other one is adding the @objc attribute before your class, protocol, property, or subscript.
If you paid attention, our method returns a Swift array but it was converted to an NSArray, but as you might know, they are different kinds of array. Firstly, because Swift arrays are mutable and NSArray are not, and the other reason is that their methods are different.
Can we use NSArray in Swift? The answer is yes, but I would recommend avoiding it, imagine once finished migrating to Swift your code still follows the old way, it would be another migration.
Migrating from Objective-C is something that you should do with care, don't try to change the whole application at once, remember that some Swift objects behave differently from Objective-C, for example, dictionaries in Swift have the key and the value types specified but in Objective-C they can be of any type.
At this moment you know how to migrate the model part of an application, however in real life we also have to replace the graphical classes. Doing it is not complicated but it could be a bit full of details.
Continuing with the previous recipe, make a copy of it or just commit the changes you have and let's continue with our migration.
import UIKit
class
MainViewController:UIViewController,UITableViewDataSource,
UITableViewDelegate {
private var vehicles = [Car]()
@IBOutlet var tableView:UITableView!
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return vehicles.count
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) ->
UITableViewCell{
var cell:UITableViewCell? =
self.tableView.dequeueReusableCellWithIdentifier
("vehiclecell")
if cell == nil {
cell = UITableViewCell(style: .Subtitle,
reuseIdentifier: "vehiclecell")
}
var currentCar = self.vehicles[indexPath.row]
cell!.textLabel?.numberOfLines = 1
cell!.textLabel?.text = "Distance
(currentCar.distance * 1000) meters"
var detailText = "Pax: (currentCar.pax) Fare:
(currentCar.fare)"
if currentCar is Van{
detailText += ", Volume: ( (currentCar as
Van).capacity)"
}
cell!.detailTextLabel?.text = detailText
cell!.imageView?.image = currentCar.image
return cell!
}
Pay attention that this conversion is not 100% equivalent, the fare for example isn't going to be shown with two digits of precision, there is an explanation later of why we are not going to fix this now.
func tableView(tableView: UITableView,
willSelectRowAtIndexPath indexPath: NSIndexPath) ->
NSIndexPath? {
var currentCar = self.vehicles[indexPath.row]
var time = currentCar.distance / 50.0 * 60.0
UIAlertView(title: "Car booked", message: "The car
will arrive in (time) minutes", delegate: nil,
cancelButtonTitle: "OK").show()
return indexPath
}
As you can see, we need only do one more step to complete our code, in this case it's the view didLoad. Pay attention that another difference from Objective-C and Swift is that in Swift you have to specify that you are overloading an existing method:
override func viewDidLoad() {
super.viewDidLoad()
vehicles = Setup.generate()
self.tableView.reloadData()
}
} // end of class
As you can see, it's not so hard replacing an old view controller with a Swift one, the first thing you need to do is create a new view controller class with its protocols. Keep the same names you had on your old code for attributes and methods that are linked as IBActions, it will make the switch very straightforward otherwise you will have to link again.
Bear in mind that you need to be sure that your changes are applied and that they are working, but sometimes it is a good idea to have something different, otherwise your application can be using the old Objective-C and you didn't realize it.
Try to modernize our code using the Swift way instead of the old Objective-C style, for example, nowadays it's preferable using interpolation rather than using stringWithFormat.
We also learned that you don't need to relink any action or outlet if you keep the same name. If you want to change the name of anything you might first keep its original name, test your app, and after that you can refactor it following the traditional factoring steps.
Don't delete the original Objective-C files until you are sure that the equivalent Swift file is working on every functionality.
This application had only one view controller, however applications usually have more than one view controller. In this case, the best way you can update them is one by one instead of all of them at the same time.
As you know there is an object that controls the events of an application, which is called application delegate. Usually you shouldn't have much code here, but a few of them you might have. For example, you may deactivate the camera or the GPS requests when your application goes to the background and reactivate them when the app returns active.
Certainly it is a good idea to update this file even if you don't have any new code on it, so it won't be a problem in the future.
If you are using the version control system, commit your changes from the last recipe or if you prefer just copy your application.
class ApplicationDelegate: UIResponder,
UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject:
AnyObject]?) -> Bool {
print("didFinishLaunchingWithOptions")
return true
}
func applicationWillResignActive(application:
UIApplication) {
print("applicationWillResignActive")
}
func applicationDidEnterBackground(application:
UIApplication) {
print("applicationDidEnterBackground")
}
func applicationWillEnterForeground(application:
UIApplication) {
print("applicationWillEnterForeground")
}
func applicationDidBecomeActive(application:
UIApplication) {
print("applicationDidBecomeActive")
}
func applicationWillTerminate(application:
UIApplication) {
print("applicationWillTerminate")
}
}
#import "Chapter_8_Vehicles-Swift.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil,
NSStringFromClass([ApplicationDelegate class]));
}
}
The application delegate class has always been specified at the starting of an application. In Objective-C, it follows the C start point, which is a function called main. In iOS, you can specify the class that you want to use as an application delegate.
If you program for OS X the procedure is different, you have to go to your nib file and change its class name to the new one.
Why did we have to change the main function and then eliminate it? The reason is that you should avoid massive changes, if something goes wrong you won't know the step where you failed, so probably you will have to rollback everything again. If you do your migration step by step ensuring that it is still working, it means that in case of finding an error, it will be easier to solve it.
Avoid doing massive changes on your project, changing step by step will be easier to solve issues.
In this recipe, we learned the last steps of how to migrate an app from Objective-C to Swift code, however we have to remember that programming is not only about applications, you can also have a framework. In the next recipe, we are going to learn how to create your own framework compatible with Swift and Objective-C.
This article shows you how Swift and Objective-C can live together and give you a step-by-step guide on how to migrate your Objective-C app to Swift.
Further resources on this subject: