Full disclaimer: we have recently received a full access to the tool from ObjectiveC2Swift Team! Thanks!
Challenge Accepted!

We all have some big and small legacy projects written in Objective-C, who doesn’t right? That’s why we have felt obliged to try this tool out and share with community how it worked for us.
All that being said I will stay as objective as possible when reviewing the tool in this subjective post. All the good things mentioned in this article are an appraisal to the team for their great job. All bad things are tips to help developers and for the Swiftify Team to make their product even better!
Hang on! Let’s rock!
It’s an online tool. No need to install anything. Code is transmitted securely and is not stored anywhere. Free tier enables you to convert pieces of code up to 2KB. Then there are paid versions that allow you convert whole files and even entire projects. There is also an Xcode plugin. Everything is neat and self- explanatory. No need for any long tutorials.

To make a full use of the license we have received, we have converted an entire project. This is a simple demo app to display the Game of Thrones (referred as GoT) data from Wikia’s API. It has some networking, a model and Masonry (a layout DSL, link in references) as Pod. As an additional difficulty it was a piece of code that I have never seen before. The app is small but uses some very specific patterns like Configurator, Loaders and blocks. Seems just enough to test.
NOTE: Objective-C2Swift converter does not promise a perfect conversion. There is a list of supported Swift and Objective-C features on their page.
I have followed this process to test the tool:

As a result we have a GitHub repository with 3 folders. One with the original Objective-C project, another one with converted project and last one with fixes. You can find a link in references.
My first impression after unzipping converted project was:
Wow, it looks like a perfect Swift Code.
And it quite is one.
Let’s build it!
Ok… 12 errors, not bad. Let’s see what impressed me most and what could be done better.
The first file that looked into in the converted project was AppDelegate.swift. It was converted 100% correctly. We can probably expect that for simple and small files. You may notice now, that good architecture and KISS pays off.
Good start.
// AppDelegate.swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
self.window = UIWindow(frame: UIScreen.main.bounds)
...
The original GoT Objective-C code was using nullability to support Swift interoperability. It is good to know that our tool converts nullable attributed properties to Swift optionals.
// Article.h @property(nonatomic, strong, nullable) NSData *thumbnailData; - (nullable UIImage *)imageFromThumbnailData;
// Article.swift var thumbnailData: Data? func imageFromThumbnailData() -> UIImage?
I would give another plus for translating readonly property attribute into private (set) var.
In the example below, we get both optional and private setter right.
// AsyncLoadConfiguration.h @property(nonatomic, readonly, nullable) NSString *webserviceQuery;
// AsyncLoadConfiguration.swift private(set) var webserviceQuery: String?
I was wondering for a while if having let would be a better option here, but quickly reminded myself that there is no such thing as Objective-C const properties . Converter works well enough with Objective-C consts:
// Objective-C const NSString *const MyFirstConstant = @"FirstConstant"; // Swift const let MyFirstConstant: String = "FirstConstant"
As ugly using global vars are, they were converted properly!
// Article.m static NSString *kArticleIdentifier = @"privateIdentifier"; static NSString *kArticleTitle = @"privateTitle";
// Article.swift var kArticleIdentifier: String = "privateIdentifier" var kArticleTitle: String = "privateTitle"
It is good to see nice separation of parameters in init functions created from Objective-C initWithXYZ format.
// Article.m
- (nonnull instancetype)initWithArticle:(nonnull Article *)article
favourite:(BOOL)favourite {
// Article.swift init(article: Article, favourite: Bool)
Might seem pretty straightforward but it is nice to see attention to such details.
// DataSource.h
typedef void (^CellConfigureBlock)(UITableViewCell *_Nonnull cell,
NSIndexPath *_Nonnull indexPath,
id _Nonnull item);
// DataSource.swift typealias CellConfigureBlock = (_ cell: UITableViewCell, _ indexPath: IndexPath, _ item: Any) -> Void
Although GCD is a framework not a part of Swift language it was converted properly.
// DataSource.m
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
// DataSource.swift
DispatchQueue.main.async(execute: {() -> Void in
self.tableView.reloadData()
})
I have uploaded the zipped GoT project including fetched Pods directory. Luckily it was not touched at all and left in its Objective-C form.
The GoT project uses Masonry for autolayout purposes. Even in Objective-C Masonry calls felt weird. Having Masonry in Swift project (while SnapKit is available) is even weirder. To make fixes I had to take a look at sample repository Swift-Masonry to make it work.
Kudos for converter for taking it that far!
// DetailsViewController.m
[self.imageView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerX.equalTo(self.imageView.superview);
make.top.equalTo(self.imageView.superview).offset(10);
make.width.height.equalTo(@100);
}];
// DetailsViewController.swift
self.imageView?.mas_makeConstraints({(_ make: MASConstraintMaker) -> Void in
make.centerX.equalTo(self.imageView?.superview)
make.top.equalTo(self.imageView?.superview?)?.offset(10)
make.width.height.equalTo(100)
})
// DetailsViewController.swift - after fixes
_ = self.imageView?.mas_makeConstraints({(make: MASConstraintMaker?) -> Void in
_ = make?.centerX.equalTo()(self.imageView?.superview)
_ = make?.top.equalTo()(self.imageView?.superview)?.offset()(10)
_ = make?.width.height().equalTo()(100)
})
This one is to echo the AppDelegate one. Custom FavouriteTableViewCell was almost perfectly converted (apart from crazy Masonry stuff and dispatch_once). This file is larger (100 lines) than AppDelegate and still converted nicely.
Do-catch error handling is supported. Some APIs became throwing APIs in Swift and converter wraps it in do-catch, try statement. On the little con side it rendered one these for me in an incomplete state.
It’s probably too much to ask, so just note what may happen:
// Data+JSON.m
- (id)JSONObject {
return [NSJSONSerialization JSONObjectWithData:self
options:NSJSONReadingAllowFragments
error:nil];
}
In swift JSONSerialization is a throwing API. It was wrapped in this way.
func jsonObject() -> Any {
do {
return try JSONSerialization.jsonObject(withData: self, options: NSJSONReadingAllowFragments)!
}
catch {
}
}
All in all, this went into positive side.
Now let’s see what may go wrong. It is not written to condemn creators of this awesome tool. Treat it as tips.
Update 24/03/2017: Look into comments. Swiftify team was working hard to address issues mentioned below.
This goes as number one as I was not expecting that when converter trips over at one of early lines it may swallow the entire method. 41 lines of code were turned into merely 3 and the rest was thrown outside of class scope in broken pieces:
// MainViewController.m
- (void)loadTableView {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
self.tableView = [[UITableView alloc] initWithFrame:CGRectZero
style:UITableViewStylePlain];
self.tableView.dataSource = self.dataSource;
self.tableView.delegate = self;
[self.tableView registerClass:[FavouriteTableViewCell class]
forCellReuseIdentifier:self.dataSource.cellReuseIdentifier];
[self.view addSubview:self.tableView];
...
// 40 lines in total
// MainViewController.swift
func loadTableView() {
var onceToken: dispatch_once_t
dispatch_once
onceToken
}
Somehow the word ‘abstract’ that was used throughout the GoT project as a variable name (property in model) or an initializer parameter caused many troubles to the converter. Funny enough neither Objective-C nor Swift have ‘abstract’ keyword.
Could be some internal implementation detail.
// Article.h
@property(nonatomic, readonly, nonnull) NSString *abstract;
- (nonnull instancetype)initWithIdentifier:(nonnull NSString *)identifier
title:(nonnull NSString *)title
abstract:(nonnull NSString *)abstract
urlString:(nonnull NSString *)urlString
thumbnailURLString:(nonnull NSString *)thumbnailURLString;
// Article.swift
private(set) var: String = "" // this is in place of property
...
override init(identifier: String, title: String, urlString: String, urlString: String, thumbnailURLString: String) // look at doubled urlString param
...
override func abstract() -> String { // this is added somewhere in code
return self.privateAbstract
}
Another problem occurred with long init with block parameter. This piece of code gave a really hard time to the converter. Take a look at it:
// AsyncLoadConfiguration.m
- (nonnull instancetype)
initWithResponseParsingBlock:
(nonnull id _Nullable (^)(NSData *_Nonnull result))block
webserviceEndpoint:(nonnull NSString *)endpoint
webserviceQuery:(nullable NSString *)query {
self = [super init];
if (self) {
self.parsingBlock = block;
self.endpoint = endpoint;
self.query = query;
}
return self;
}
// AsyncLoadConfiguration.swift
override init(responseParsingBlock Nullable: Any) {
block
(endpoint as? String)
(query as? String)
do {
super.init()
self.parsingBlock = block
self.endpoint = endpoint
self.query = query
}
var: ((_ Nonnul: Data) -> Any)?
responseParsingBlock
do {
return self.parsingBlock
}
...
When trying to reproduce this in the Swiftify web tool I have found that there is a tiny console that shows all lexer, parser and converter messages. In the case of our unfortunate and weird initializer, it showed:
Lexer and Parser messages: (unknown): (3:12) Missing RP at '_Nullable' (unknown): (3:50) Mismatched input ')' (unknown): (13:1) Missing '}' at 'EOF' Converter messages: (unknown): (3:51) Unable to convert:(unknown): (13:0) Unable to convert:
This point is related to previous one. Trying to call such a complex initializer resulted in similarly broken code.
In many places throughout the code there were unnecessary override keywords.
// DataSource.m - (nonnull instancetype) initWithCellConfigureBlock:(nullable CellConfigureBlock)configureBlock cellReuseIdentifier:(nonnull NSString *)reuseIdentifier - (void)addItems:(nonnull NSArray *)items - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
// DataSource.swift override init(cellConfigureBlock configureBlock: CellConfigureBlock?, cellReuseIdentifier reuseIdentifier: String) override func addItems(_ items: [Any]) override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
While this is a safe option it is usually completely unnecessary and redundant piece. Still, better working code than optimized and broken.
// ArticleRepository.m
- (void)saveFavouriteArticle:(nonnull Article *)article {
[self.articles addObject:article];
[self saveFavouriteArticlesToDefaults];
}
// ArticleRepository.swift
func saveFavouriteArticle(_ article: Article) {
self.articles.append(article)
self.saveFavouriteArticlesToDefaults()
}
Some Objective-C patterns are sometimes not converted properly to Swift. That’s not a big deal though. Take a look at nil comparison:
// ArticlesRepository.m
if (!set) {
set = [[NSMutableSet alloc] init];
}
// ArticlesRepository.swift
if set.isEmpty {
set = Set()
}
// Objective-C
if (item) {
self.cellConfigureBlock(cell, indexPath, item);
}
// Swift
if item != nil {
self.cellConfigureBlock(cell, indexPath, item)
}
dispatch_once callsI do not know why, but I was expecting to have it converted when I saw GCD converted properly. The dispatch_once calls have no equivalent in Swift and should be rewritten into some form of lazy vars:
DISPATCH_SWIFT3_UNAVAILABLE("Use lazily initialized globals instead")
The converter took no attempt to translate this call:
+ (UIImage *)avatarImage {
static UIImage *avatarImage;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
avatarImage = [UIImage imageNamed:@"avatar"];
});
return avatarImage;
}
class func avatarImage() -> UIImage {
var avatarImage: UIImage?
var onceToken: dispatch_once_t
dispatch_once(onceToken, {() -> Void in
avatarImage = UIImage(named: "avatar")
})
return avatarImage!
}
Swift 3 imposes new style of writing. It affected many APIs and made them shorter. I have noticed that converter is not picking that up.
// DetailsViewController.m self.abstractTextView.editable = NO;
// DetailsViewController.swift // Should be 'isEditable' self.abstractTextView.editable = false
// Article.m [aCoder encodeObject:self.privateIdentifier forKey:kArticleIdentifier];
// Article.swift // Should be just 'encode' aCoder.encodeObject(self.privateIdentifier, forKey: kArticleIdentifier)
While looking into code we could probably find some more tiny wins and losses. But this trip was long already!
At this point of my process I was mostly working on strong typing of Set‘s and Array‘s so that everything was more type-safe and Swifty. Other tasks included handling optionals with guard let‘s and similar. It really didn’t take too much time until app was building and running correctly. Neat!
As you may notice project had properties and parameters marked with nullable and nonnull attributes that are there to help conversion to Swift optionals.
There is one more feature introduced in Xcode7 that I wanted to give a try and that was not in our sample project: Objective-C Lightweight Generics.
Let’s jump directly into Objective-C code and code converted using online tool:
@property NSArray*dates; @property NSCache > *cachedData; @property NSDictionary > *supportedLocales;
var dates = [Date]() var cachedData: NSCache! var supportedLocales = [String: [NSLocale]]()
Looks good to me. Good job Swiftify!
All in all I am very impressed with the results I was able to achieve using Swiftify to convert the entire project. There are few flaws but most of them are minor. They are picked by compiler and can be self-corrected quickly.
Clearly the fact that the project is building does not mean you are at home. There is still a high probability of runtime errors as the languages are very different. It is necessary to stay focused and analyze the results but all the tedious work and hours of typing are done for you! Now I just wonder whether unit tests could help in this converted project.
I have to admit that this project has a lot of unusual solutions. But only in this way we could get to the limits of the tool and give you some valuable tips and feedback.
It’s been more than a year since this review was created. If you are interested in converting Objective-C codebases into Swift you can follow up with this article from Swiftify.
.
Have you ever been tired of repeating if let or guard let statements, to perform certain operations, that should be performed only if a value is not nil? From time to time your code could become like this if you wanted to call a function with unwrapped value:
if let unwrapped1 = optional1 {
doTaskThatReturnsVoid(with: unwrapped1)
}
if let unwrapped2 = optional2 {
doTaskThatReturnsVoid(with: unwrapped2)
}
//...
if let unwrappedX = optionalX {
doTaskThatReturnsVoid(with: unwrappedX)
}
or like this:
struct Bar {
let name: String
let error: Error?
}
func foo(with bars: [Bar]) {
var errors: [Error] = []
bars.forEach {
guard let error = $0.error else { return }
errors.append(error)
}
process(errors)
}
Sometimes, it’s too much typing, isn’t it? But there’s an alternative …
Optionals are monads, on which you can call flatMap function, that unwraps an optional (of hypothetical type A, a.k.a Optional) if it contains a value, applies to it a transform, that converts a value from type A to type B and wraps it back into an optional of type B (a.k.a. Optional). PS. if you want to understand functors, applicatives and monads I strongly recommend this video.
So, if we have an optional, we can call flatMap on it. We have to pass a closure transforming from A→B to the function. What if we wanted to transform from T→Void?
It would work. According to Swift compiler, transformation from T to nothing (a.k.a. Void) is a valid transformation. So let’s use that fact!
let optional1 = getOptional1()
optional1.flatMap { self.doTaskThatReturnsVoid(with: $0) }
Note that we don’t have to use a capture list (see more on them here) since flatMap takes a non-escaping closure as an argument. In my opinion the real beauty of the solution meets the eye here:
func foo(with bars: [Bar]) {
var errors: [Error] = []
bars.forEach {
$0.error.flatMap { errors.append($0) }
}
process(errors)
}
Neat! We can append error to the array, only if it contains a value. We got rid of guard let similarly to if let in the previous example.
Seeing code with transforms from T→Void in flatMap might seem a bit odd at first glance. It was odd for me. But once I’ve seen it used multiple times in the code that would require a few if lets in a row I started appreciating the solution. Check it out, maybe you will start using it too! 🙂
Big thanks to people I work with for the things I’ve been learning from you ❤️!
We’ve received a tip and correction from Ole Begemann. Thank you Ole ❤️
!
There’s a mistake in flatMapping from T→Void paragraph. It actually describes map instead of flatMap function. The map function takes a transform closure from T→U, whereas flatMap from T→U?. You can see declarations below:
public func map(_ transform: (Wrapped) throws -> U) rethrows -> U? public func flatMap(_ transform: (Wrapped) throws -> U?) rethrows -> U?
So actually map and flatMap would have the same effect in our scenario. You can check this snippet in a Playground to see how it works!
import Foundation
enum Error: Swift.Error {
case small
case big
}
struct Bar {
let name: String
let error: Error?
}
let bars = [
Bar(name: "1", error: Error.small),
Bar(name: "2", error: nil),
Bar(name: "3", error: Error.big),
]
func fooFlatMap(with bars: [Bar]) {
var errors: [Error] = []
bars.forEach {
$0.error.flatMap { errors.append($0) }
}
print("\(errors)")
}
func fooMap(with bars: [Bar]) {
var errors: [Error] = []
bars.forEach {
$0.error.map { errors.append($0) }
}
print("\(errors)")
}
fooFlatMap(with: bars)
fooMap(with: bars)
@rosskimes had a good thought – forEach in the example can actually be replaced by let errors: [Error] = bars.flatMap { $0.error }. So, let us show you a more convincing example.
In snippet below CoreDataWorker mentioned in issue #28 is used to remove all entities from the database. A dispatchGroup synchronizes delete operations. All potential errors are appended to an array and processed when all operations are finished.
func removeData() {
let dispatchGroup = DispatchGroup()
var errors: [Error] = []
let completion = { (error: Error?) in
error.flatMap { errors.append($0) }
dispatchGroup.leave()
}
dispatchGroup.enter()
coreDataWorker.removeAllEntitiesOfType(Entity1.self, completion: completion)
dispatchGroup.enter()
coreDataWorker.removeAllEntitiesOfType(Entity2.self, completion: completion)
//...
dispatchGroup.notify(queue: DispatchQueue.main) {
guard errors.isEmpty else { process(errors); return }
//success, proceed with further operations
}
}
]]>Did you know that Swift 2.1 introduced new syntax for initialisation that works more or less like NSStringFromClass([Object class]) in Objective-C? From now on, you can initialise String in the following way:
let className = String(Object)
I dug the documentation and I found out that there is no initialiser that takes a class as an init argument. Because of that I’m not sure how it works under the hood but my guess would be that Swift compiler makes some magic tricks to make that work. If anyone has some thoughts – please, share them with us :)!
Some time ago I’ve seen a nice blog entry from NatashaTheRobot. It shows a clever way to create a cell reusable identifiers from class name.
let reuseIdentifier = String(MyTableViewCell)
Recently I had to create a dictionary that would return a specific string based on a class name it holds. It was used in UIViewController and its subclasses for configuration purposes in viewDidLoad method. It looked similarly to this snippet:
let dictionary = [
String(Class1) : "Custom string for class 1",
...
String(ClassN) : "Custom string for class n"
]
I thought it should be enough, however I got a fancy message from the compiler: Expression was too complex to be solved in reasonable time; consider breaking up the expression into distinct sub-expressions.

Resolution of a problem is pretty simple. From time to time one has to help compiler a bit by explicitly defining expression result type. By adding a ‘casting’ to String the problem was solved. Thanks to @higheror for helping out with this one! 
let dictionary = [
String(Class1) as String : "Custom string for class 1",
...
String(ClassN) as String : "Custom string for class n"
]
I had a good few entries in aforementioned dictionary, hence manual addition of as String would be painful. Thanks god we can use (masked) regular expressions in Xcode’s find & replace text option (CMD+ALT+F).
PS. You can also have a look at @krzyzanowskim‘s post about type ambiguity when overloading methods. Grab it here.
]]>