Image from http://blog.teamtreehouse.com/an-absolute-beginners-guide-to-swift

Obj-C to Swift: A Transition

pancy
Code Zen
Published in
8 min readJan 12, 2015

--

I’ve been working on an outdated iOS Objective-C code with tons of deprecated methods which isn’t working on the latest version of iOS, so I might as well rewrite most of it in Swift for the sake of learning both languages, and here’s the summary of what I’ve learned.

If any of you are excited about Apple’s new language Swift as I was and thinking it’s your new savior from Obj-C, I’ll have to say it is not that simple. Swift does make sense on its own and is a very neat and concise language which reminds me of Go, a concise, script-like statically-typed language from Google. However, developing on iOS in it without a slight knowledge of Obj-C does not necessary. Cocoa frameworks are still in Obj-C, and to develop in Swift you will need to understand the quirks and nuisances coming from Obj-C to a certain point.

Swift is a new language on the horizon, and I haven’t had much luck searching for an elaborate migration guide online except from two excellent iBooks from Apple, which you can check out on iTune. So here I am, learning Obj-C as I convert its verbose APIs to Swift’s. And what I’ve discovered is that learning Obj-C had made learning Swift much more enjoyable.

Variables and Types

This is how we would print a string in Obj-C compared to Swift.

// Obj-C
NSLog(@"Odelay!");
// Swift
print("Odelay!")

Obj-C seems like a prehistoric attempt to simplify C and make it more “objective,” sacrificing conciseness for verbosity and clarity in words. For instance, you will find methods like stringByAppendingString: which you can make sense right away upon reading and other higher-level object types such as strings, arrays, dictionaries are always appended with a * and the values prepended with a @ to explicitly tell you that they are pointers and addresses. Also, most types are prepended by NS which stands for NeXTSTEP, the OS predating OS X and iOS. It is there for homagesake.

Swift has no * and @ to indicate objects and pointers. Since most of Obj-C objects are treated as raw pointers, Swift handle pointers in the background so users won’t have to think about them.

Compare defining variables in two languages:

// Obj-C
NSString *aString = @"Odelay!";
NSInteger aInt = 20;
BOOL *aBool = YES;
NSNumber *aNum = @3.14;
NSArray *aArray = @[@"Howdy", @"Doodle", @"Deedee"];
NSDictionary *aDict = @{@"name": @"Jane", @"age": @31};
// Swift
let aString: String = "Odelay!"
let aInt: Int = 20
let aBool: Bool = true
let aNum: NSNumber = 3.14
let aArray: [String] = ["Howdy", "Doodle", "Deedee"]
let aDict: [String: String] = ["name": "Jane", "age": "31"]
// or do type inference
let aString = "Odelay!"

Obj-C has a convention of treating (almost) everything as raw pointer to object, (thus the * and @) and only primitive types like integers and characters are treated as real values. Swift is more strict about types than Obj-C. Therefore, arrays and dictionaries are strongly-typed. Also, there isn’t a NSNumber to wrap around all types of numbers or a NSDictionary to handle dictionaries with arbitrary types in Swift, but we can borrow NS- types from Obj-C since they both are interoperable.

Unlike Obj-C, arrays in Swift (as well as dictionaries) are mutable.

// Obj-C
NSArray *arr = @[@"first", @"second", @"third"];
arr[2] = @"last"; // This gives compile error
// Swift
let arr1 = ["first", "second", "third"]
arr[2] = "last" // This gives error because arr is constant.
var arr2 = ["first", "second", "third"]
arr2[2] = "last" // The last member of arr2 is changed to "last"

Swift’s let keyword is used to define a constant (immutable) and var to define regular variables. Using let should be less expensive than var, and thus should be used whenever it’s viable.

Sending Messages (or Calling Methods)

I like how Obj-C send messages. However, in Swift you do it just like in many other languages with dot notation. Be clear that “sending a message to” and “calling a method on” mean the same thing. However, in Obj-C-speak, it’s sending messages. And this analogy is also very helpful when developing iOS (i.e. sending a message to a delegate makes more sense than calling a method on a delegate to do something).

// Obj-C
[obj message]
[[obj message1] message2]
// Swift
obj.method()
obj.method1().method2()

Obj-C’s methods are imported to Swift this way:

// Obj-C
// Method signature
— (UIImage *)resizedImageWithContentMode:(UIViewContentMode)contentMode
bounds: (CGSize)bounds
interpolationQuality: (CGInterpolationQuality)quality
{ ... }
// Method call
[UIImage resizedImageWithContentMode: UIViewContentModeAspectFill
bounds: CGSizeMake(size1, size2)
interpolationQuality: quality];
// Swift
// Method signature
func resizedImage(contentMode: UIViewContentMode,
bounds: CGSize,
interpolationQuality quality: CGInterpolationQuality
) -> UIImage { ... }
// Method call
UIImage.resizedImage(.AspectFill,
bounds: CGSizeMake(size1, size2),
interpolationQuality: quality
)

For factory methods, Swift resort to using a more familiar constructor syntax:

// Obj-C
NSString *aString = [NSString stringWithString: @"Hello"];
// or
NSString *aString = [[NSString alloc] initWithString: @"Hello"];
// Swift
let aString = NSString(string: "Hello")

In general, the name after ‘With’ in Obj-C methods are omitted and automatically become the internal name of the first argument.
Also note that enum value conventions are different. While Obj-C has verbose value name like UIViewWithContentModeAspectFill or DayofWeekMonday, Swift use dot notation to access enum value so they become .AspectFill or .Monday (since the method already know what type of argument it is expecting).

Strings

We know how complex strings and characters are in C. Obj-C doesn’t allow you to append an NSString to another using ‘+’ operator overload, because NSString is a special object. A stringByAppendingString: message is sent to the first string instead. In Swift, you can expect ‘+’ to work on strings like in most languages.

// Obj-C
NSString *str = @"Hello" + @" world."; // Can't do this
NSString *str = [@"Hello" stringByAppendingString: @" world."];
// In Swift, it's a breeze
var str = "Hello" + " world."

However, NSString in Obj-C is more powerful than Swift’s String type because it has more useful methods attached to it and is more flexible. Swift can use NSString as its type alright:

let str1: NSString = "Hello, world."
// or type-casting with "as"
let str1 = "Hello, world." as NSString
let str2 = str1.stringByAppendingString(" I'm Joe.")
// or just
let str3 = str1 + " I'm Joe."

Surprisingly, NSString type in Swift can use overloaded ‘+’ operator for concatenation. Also, String type in Swift is mutable, unless assigned with let keyword. (In Obj-C, NSMutableString class must be chosen)

String formatting is easy in Swift:

// Obj-C
NSString *str1 = @"Hello";
NSString *str2 = @"world!";
NSString *str3 = [NSString stringWithFormat: @"%@ %@", str1, str2];

// Swift
let str1 = "Hello", str2 = "world!"
let str3 = "\(str1) \(str2)"

Fast Enumeration

Fast enumeration or looping through collections are quite similar in Obj-C and Swift, except for latter you can provide additional arguments to get key-value in a dictionary or index-value in an array.

// Obj-C
NSDictionary *persons = @{@"James": 25, @"Anna": 18, @"Pete": 40};
for (NSString *person in persons) {
NSUInteger age = partyList[p];
NSLog(@"%@ is %d years old.", person, age);
}
// Swift
let persons = ["James": 25, "Anna": 18, "Pete": 40]
for (person, age) in persons {
print("\(person) is \(age) years old.")
}

Block is Closure

Obj-C’s blocks have the weirdest syntax. They are imported as closures in Swift.

// Obj-C
void (^myBlock)(NSString *) = ^(NSString *name, NSNumber *age){
NSLog(@"%@ is %@ years old.", name, age);
}
// Call the block
myBlock(@"Joe", @30);
// Swift
let myClosure: (NSString, NSNumber) -> Void = {
name, age in print("\(name) is \(age) years old.")
}
// Call the block
myClosure("Joe", 30)

Classes and Objects

Class is where Obj-C gets really interesting. An Obj-C class is divided into an interface (in an .h file) and implementation (in an .m file).

// person.h
@interface Person : NSObject
@property NSString *name;
- (void) speak;
@end
// person.m
#import "Person.h"
@implementation Person
- (void) speak:(NSString *greeting);
{
NSLog(@"%@ from %@", greeting, self.name);
}
@end
// use
Person *person1 = [[Person alloc] init];
person1.name = @"Joe";
[person1 speak: @"Hello!"]; // Print "Hello! from Joe"

In Swift, a class declaration stays in one file:

// person.swift
class
Person: NSObject {
var name: NSString;

init(aName: NSString) {
name = aName
}
func speak(greeting: NSString) {
print("\(greeting) from \(name)")
}
}
// use
let person1 = Person("Joe")
person1.speak("Hello!")

In Swift, self keyword is not necessary, unless there is some conflict in the name (that’s why an argument to the constructor above was named aName and not just name). A Swift instance knows how to get its own “self” properties without an explicit self keyword. Only in closures that self is used when capturing values from outer scopes.

Read/Write and Read-only

In Swift, there’s no such things as read, write or readonly. Just use let keyword to make a stored property read-only, and var to make it read/writable.

// Obj-C
@property (readonly) NSString *name;
// Swift
let name: NSString

To set read/write accessibility in a computed property, set the appropriate getter and setter.

// Swift
var ageInSeconds: NSNumber {
// readable
get {
return (age * 365 * 24 * 60 * 60)
}
// writable
set {
age = ((((newValue / 365) / 24) / 60) / 60)
}
}

Assigning Default Property

Default properties could be initialized within the constructor init method. In Obj-C, since most of our classes inherit from NSObject base class, we are most likely overriding its constructor method.

// person.m
#import "person.h"
@implementation Person;
{
// Implicitly override NSObject's init
- (Person *) init;
self.name = @"Henry";
// or for read-only properties
// _name = @"Henry";

// This is so that the NSObject's init method can be
// implemented and return a (Person *) instance

return [super init];
}
@end

Swift has an explicit override keyword, and never explicitly return anything from init(). To override and initialize default properties:

// person.swift
class
Person: NSObject {
var name: NSString
override init() {
name = "Henry"
super.init()
}
}

In fact, super.init() can be omitted and the constructor still works fine.

Instance Variables

Obj-C instance variables are hidden from anything outside of its own class. And declared without the @property keyword:

// Obj-C
// person.h

@interface Person : NSObject {
NSString *_bankAccountNumber
}
@property NSString *name;
@property (readonly) NSNumber *age;
@end

Note here that we can read the value of read-only age with _age. Obj-C automatically generate an instance variable for that property.

Swift has no instance variables, only properties. Access control is used on properties to control accessibility:

// Swift
// person.swift

class Person: NSObject {
private var _bankAccountNumber: NSString
public var name: NSString
internal var age: NSNumber
// Can be accessed only within this class
func getAccountNumber() -> NSString {
return _bankAccountNumber
}
}

id and AnyObject

Obj-C id type is imported as AnyObject in Swift. Basically, id is used to describe any general object of any type. (It’s an NSObject * alias, I think).

// Obj-C
id
myObj = @"Hello world";
myObj = @[@32, @"twenty-one", YES, 3.14];
// Swift
var myObj: AnyObject = "Hello world"
myObj.count? // {Some false}
myObj = ["34", "twenty", "plusthree"]
myObj.count? // {Some 3}

Swift make use of its Optional type for type safety, as seen above.

Extensions and Categories

Obj-C uses categories as a mean to extend a class’s functionalities. Swift instead uses extensions, which are more straightforward and unnamed.

// Obj-C
@interface ClassName (Categoryname);
- (NSString *)newMethod: (NSString *)arg;
@end
// Swift
extension ClassName {
func newMethod(arg: NSString) -> NSString {
// method implementation
}
}

Phew! That was enough for me as a starter. In any case, two-way interoperability allows Swift and Obj-C code to live in harmony within the same project with mix and match style, thus there’s no need to rewrite everything in Swift. However, if newer iOS versions are your target, starting new code in Swift is never a bad idea considering in a few years most of iOS code should have migrated to Swift anyway.

--

--

pancy
Code Zen

I’m interested in Web3 and machine learning, and helping ambitious people. I like programming in Ocaml and Rust. I angel invest sometimes.