Swift vs Objective-С: There Will Be Blood

After Swift was released, lots of people immediately started saying how it would simplify the iOS application development, make the creation of end-product much faster, etc. As lots of our clients are asking about which technology to use, I decided to create a small comparison of pros and cons of Swift versus Objective-C.

First of all, let's define the key points in these two languages.

Objective-C

Objective-C is a dynamic object-oriented language with duck typing. It has a syntax that needs some time to get used to, but overall results in high code readability if you atone to the Apple naming conventions and guides. It implements runtime class creation, messaging instead of method calls and other stuff that really simplifies and speeds up the development process. But, as any dynamic language, you should be careful with what you use and how you do it, as the compiler won't be able to check it for you, which results in runtime testing and takes additional time.

It has well-defined ABI and API, which don't really change and are backwards compatible. The real deal here are messages which are Objective-C method call look-alike and nil as a valid object.

For messaging, the concept is straightforward: instead of calling a method, you instruct the object to call a method with specified names and parameters. In most programming languages, including Swift, you simply instruct the method to be called directly, and the object has no influence on the calling process. However, in Objective-C, you could intercept a method call and perform any action you desire. This provides both possibilities and responsibilities. You are accountable for properly handling the result and parameters expected by the compiler, but it enables you to write straightforward, easy-to-use code without extensive duplication. Duplication is the greatest sin in coding, resulting in less maintainable and less user-friendly code.

For nil, the idea is even simpler: it is a valid object. If you want to send a message to nil, it is permissible, but it will not do anything and will return an empty value, such as 0 or nil, as a result. However, you must exercise caution since receiving the result of a message call to nil, which returns a structure, can have repercussions. Nevertheless, if used appropriately, nil can help avoid many checks and other complicated processes, resulting in less boilerplate code throughout your program.

Swift

Swift is a statically typed language with a wild mix of functional and object-oriented approach. Nil is not a valid object in it, it doesn't have messages either (only in Objective-C bridged code, but this ruins the idea of using Swift). Its syntax is similar to other modern-day languages with named parameters, which results in a much better readability compared to Java, C++ and the likes. It also has the benefit of generics, which are its own way of implementing the dynamism. As the language is statically typed and is not dynamic, a compiler will be able to find most of the mistakes and fail the compilation process. Its ABI is changing from version to version and they are not backwards compatible.

The real deal here is the functional paradigm implementations (e.g. SwiftZ), which yield benefits of writing a decomposable and easy to grasp code with little to no duplication.

Swift vs Objective-С: let the battle begin

And now moving on to the fun part - comparison of Swift and Objective-C, which is the sole purpose of the article.

Coding

Let's just take a simple example of parsing the JSON. For the sake of simplicity, we will consider that there are no NSNull-related problems (this can be achieved by writing a simple category, which removes NSNull from JSON, and then reusing it for life).

In Objective-C you would do it like that:

// ANDUser.h

@interface ANDUser : NSObject
@property (nonatomic, assign, readonly) NSUInteger  ID;
@property (nonatomic, copy, readonly)   NSString    *name;
@property (nonatomic, copy, readonly)   NSString    *email;

- (instancetype)initWithDictionary:(NSDictionary *)dictionary;

@end

// ANDUser.m

@implementation ANDUser

- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
    self = [self init];
    if (self) {
        dictionary = [dictionary JSONRepresentationWithMap:[self JSONMap]];
        _ID = [dictionary[@"id"] unsignedIntegerValue];
        _name = dictionary[@"name"];
        _email = dictionary[@"email"];
    }
    
    return self;
}

- (NSDictionary *)JSONMap {
    return @{ @"id" : [NSNumber class],
              @"name" : [NSString class],
              @"email" : [NSString class] };
}

@end

If you wrote the parsing code in Swift like in Objective-C, it would look like that (the example is taken from the well-known article on how to properly write in swift:

struct User {
    let id: Int
    let name: String
    let email: String

    static func decode(json: AnyObject) -> User? {
        if let jsonObject = json as? [String:AnyObject] {
            if let id = jsonObject["id"] as AnyObject? as? Int {
                if let name = jsonObject["name"] as AnyObject? as? String {
                    if let email = jsonObject["email"] as AnyObject? {
                        return User(id: id, name: name, email: email as? String)
                    }
                }
            }
        }
        
      return .None
    }
}

You can see for yourself that such a code, while readable, has a lot of boilerplate and doesn't use Swift features. And that's exactly what you would get from an average dev. This surely is an unwelcome approach and the winner here is Objective-C. Of course, you could say that I cheated here, as we could also implement the JSONMap approach in Swift. But this actually wouldn't cut down the amount of code, as you would still have to make the optional unwrapping. And nope, guard statements won't simplify the code in any way either.

But writing code in Swift, like it was supposed to, would look plain awesome. Let's take a peek:

struct User {
    let id: Int
    let name: String
    let email: String
    
    static func create(id: Int)(name: String)(email: String?) -> User {
        return User(id: id, name: name, email: email)
    }

    static func decode(json: JSON) -> User? {
        return _JSONParse(json) >>> { d in
            User.create
                <^> d <|  "id"
                <*> d <|  "name"
                <*> d <|? "email"
        }
    }
}

It looks as magnificent, as Objective-C code. It also relies on several third-party functions which we don't consider, as they would be reusable (the same applies to Objective-C). But the main drawback here is that the developer, who would be working with the code, should know the concepts behind such an implementation. It's a functional implementation for you and it's quite tricky to say the least.

The proper way to write in Swift is fundamentally different from the object-oriented approach in Objective-C. Functional paradigm and functional-reactive paradigm for interoperability with iOS libraries is the definitive approach. Yep, you can write reactively in Objective-C as well, but the syntax would just stand in your way.

So, the point goes to Objective-C, in my opinion, for simplicity, as it's a simpler readable solution with no time to get into the code and start using/changing it.

How far could errors go

On the other hand, if we made any mistakes in Objective-C with types and things like that, we are definitely screwed and will only find out about the issue after we launch the app (or if we wrote proper tests). Swift, on the other hand, wouldn't even compile if there were type errors.

So the point for error-proofness of the code goes to Swift. Ladies and gentlemen, we have a draw here for you. But that won't be for long.

Coding the project in the long run

As I previously mentioned, Swift ABI changes from time to time, expect to refactor your code each time the new Swift version is released (at least twice a year) in order to even compile it on a newer Swift version. And this requires additional expenses for nothing. Your customers don't want version changes without new functionality, but that's what Swift would require you to do from time to time. Gross.

Another problem is that you would have to update the xCode to a newer version as well. Why? Imagine the situation: A bug comes up (and this happens from time to time to even most awesome teams, like ours - bug-fixing process is a normal part of development). We try to fix it but in vain, as the cause is unclear. We had a long-running project and switched several Swift versions. One of the last resorts in that case is to go down into older development versions in order to find where the bug was. And then voila, it stops compiling because at some point in time we come around a version that's using older Swift. xCode is complaining as well, so we would have to install older xCode version and hold several of them on a local machine. All of this results in a slower flow.

And this is the most illustrative case. Another case would be: you partially implemented the feature and postponed it till better days, then you want to restore it into the major version after some time and you can't do that. You'll have to do some coding just in order to port in to the newer version.

So, Swift, as a young programming language, won't earn that point unlike Objective-C whose ABI is stable and is backwards compatible.

Developers as resources

Suppose, you need devs for your project. How easy would it be to find them? How much would they cost? How good are they?

In Swift, most of developers write in Objective-C using Swift syntax (which sucks, as I showed above). This results in a bloated code with a lot of boilerplate, which is hard to maintain. But the worst thing is that functional and functional-reactive paradigm, which Swift begs for, is not that wide-spread among devs. So expect major headaches when trying to a find a good dev. And a good dev would obviously cost more than an average one. Objective-C, on the other hand, proposes loads of developers who use it with loads of different approaches and open-source libraries. The paradigm it uses is pretty simple so most of the tasks are well-defined in terms of implementation and could be easily handled by an ordinary dev.

Resource-wise Swift is just a no-go as of now. And it will be like that for quite some time. So, Objective-C is the winner in this battle and that's for sure.

Minor rant in Swift's defense serving as an epilogue

Are you still pondering over the question "Should I use Swift or Objective-C to build my iOS app?" Here are some final thoughts to help you make the right decision.

We really like Swift. Yep, we do. We use it in our internal projects, we have a lot of fun with it. It's awesome and allows you to write some beautiful and reusable code. You could do loads of tricks that are simply not possible in Objective-C without tons of boilerplate code. The problem is that it's too young and if you want a solid product, it's just not the right time despite all the marketing hype. Yep, if you want your devs to use the cool stuff, you could take Swift, but if you want the product to be written faster with less costs, run like hell from Swift. At least for now.