Porting an Objective-C project to Swift - Part I

If you started writing code for Apple's mobile devices, you definitely would have definitely come across the wonderful language called Objective-C. This was an amazing piece of technology that stemmed from SmallTalk in late 70's to the early 80's. It also gave a lot of developers headaches, heartaches and whatnots - till in 2014, at Apple's WWDC conference, Chris Lattner introduced Swift. A new language that was offered as an alternative to Objective-C. From there, it has evoked practically every emotion possible, right from pure Love to pure Hate.
The biggest issue with Swift is that it is growing organically. It has taken the best bits from the modern languages and has put them together for use. However there are a couple of caveats that make it difficult to work with.

In this article, We shall look at some basics of migrating a project over from Objective-C to Swift and the pitfalls to avoid or interesting outcomes that arise from this.

Structure of an Objective-C file/module

Every object definition in Objective-C has two files, the header '.h' and the implementation '.m'.
In the header file, you declare the interface which has a syntax such as

@interface MyClassName: NSObject
@property (nonatomic, strong) NSString *name;
@end


and in the implementation file '.m' you write the code or further extensions to the interface

@interface MyClassName()
@property (weak, nonatomic) IBOutlet UILabel *theText;
@end


then you follow that with the implementation (i.e. the code)


@implementation MyClassName

-(id) initWithName:(NSString*) name {
self = [super init];
if (self) {
self.theText.text = name;
}
return self;
}
@end


What would this look like in Swift?
Firstly, we do not need two files to separate the Header and the Implementations. Secondly, you do not need a series of '#import' statements for Swift classes as they are accessible to the entire project unless you specifically restrict access.
So the same code in Swift would feature in a single file called MyClassName.swift instead of the MyClassName.h and the MyClassName.m
and the code would look something like


class MyClassName: NSObject {
var name: String

@IBOutlet weak var theText: UILabel!
}


In Objective-C all objects are inherited from NSObject, however in Swift you can have it by itself, but since we want to integrate and *maybe* use this class from Objective-C, we need to inherit the classes from NSObject. In a bit we shall also add the @objc prefix to the class definition to be able to use this class from Objective-C.

Note that if you do not make the variable optional, you cannot assign it as weak. In addition since this is an outlet there should be a property connected to the variable and therefore we can use the unwraped variant "!" instead of the wrapped variant "?"

We did not declare any init functions as yet, and therefore if you typed the code above in Playgrounds or a Swift class, the compiler would complain about the fact that the class does not have an initialiser. We can rectify that by adding an init function like,

init() {
self.name = ""
}

In our Objective-C code, we have another init function that we have not translated as yet, it is initWithName. We could simply create a function called the same and return self as the return value, something like

func initWithName(_ name: String) -> MyClassName {
self.name = name
super.init()
return self
}


NOTE: this is not OK, this is not the way it works. While the function names are verbose, Swift functions are setup in a special way

Let's look at the way the functions are mapped

let's take the example of our first function


func firstFunction(class: String, marks: Int) {}



We can see that even if we were to call this function from Swift, we would call it as

firstFunction(class:"Server Side Swift", marks: 95)


This also generates an Objective-C interface for this function that looks something like

-(void) firstFunctionWithClass:(NSString *)class marks:(NSNumber)marks {
}


Note that the interface generates a function by adding the word 'With' between the function name and the first parameter. If you do not want to have this interface generated, then you can use an underscore to change this. The function if written as

func firstFunction(_ class: String, andMarks marks: Int) {}


this would translate to


-(void) firstFunction:(NSString *)class andMarks:(NSNumber)marks {
}



So going back to the initWithName function, you can write an init function as


init(name: String) {
self.name = name
}

this will generate the Objective-C interface as we expected.

Aliasing the function as something different

There are times when you want the interface to be different than what you have defined. The Objective-C generated interface might require a specific function but the Swift function cannot be declared that way. Here's an example, say we define a swift function with the swift guidelines of defining a function as

func displaySprite(for named: String, atPos point: CGPoint) {}

However the Objective-C interface generated is as follows

-(void) displaySpriteWithFor:(NSString*)named atPos:(CGPoint) point {}


NOT quite what we want, what we want it to look like is

-(void) DisplaySpriteNamed:(NSString *)named atPosition:(CGPoint)point {}


this is easy to specify a custom Objective-C signature to the function by adding the "@objc(signature)"

@objc(displaySpriteNamed:atPosition)
func displaySprite(for named: String, atPos point: CGPoint) {}


This is just one of the few quirks of working between Swift and Objective-C, in the next few articles we shall see how to share the Swift functions to your Objective-C code and use Objective-C functions in swift. Also how you can replace Objective-C classes / code with Swift equivalents.

Comments

Popular Posts