Swift - A first deeper look
tl;dr; Swift is awesome, but there are still many bugs for Apple to fix.
What is Swift?
Swift is the new programming language Apple introduced at the last WWDC in 2014 alongside iOS 8. When I first heard about it a the keynote I was quite sceptic. Why? Because I just thought that I understood Objective-C. A new language means getting used to new features of the language. But that feeling was gone right after I played around with Swift for the first time. Despite the ton of bugs it had during the beta period (I'm getting back to that later) I really liked Swift. Many things I didn't like with Objective-C (and I already really liked Objective-C compared to other languages) were changed to the better. Like for example initializers. I've always hated it to write the same boilerplate code in Objective-C to create an initializer (or override one):
- (instancetype)init {
self = [super init];
if (self) {
// Do your stuff
}
return self;
}
In Swift it's a lot easier:
init() {
// Maybe call super.init()
// Do your stuff
}
They saved a lot of overhead by removing repeating code.
But that's only a part of it. Generics, more powerful structs and enums, (pseudo-)namespaces, frameworks for iOS, etc... All these things make Swift great. But is it ready to use in a customer project yet?
Swift or Objective-C?
A client of mine was planning a new version of an app I wrote three years ago. It was my second app and the code is horrible. So for me it was clear that although it was "only an update" for the client, I would completely rewrite it. Now the question was, in which language am I going to rewrite it, Swift or Objective-C?
At first I thought that Objective-C would be the better choice at it's a stable language and I wouldn't have to deal with some strange bugs. But in the end I thought that in the future Apple will certainly push Swift and I don't know if I'm going to get the opportunity to rewrite the app again. So I decided to go with Swift.
The adventure begins
The choice was made and thus I started with a blank Swift project. Now, I've already had a lot of code I reused in almost any project, but all of it was Objective-C. Also, the code was spread over many repositories on GitHub. I used to add submodules for all of them. But since the new version of the app will be iOS 8+ only, I thought that this would be the perfect time to use frameworks. Hence I created FFFoundation and FFUIKit. For another app I had already created FFCoreData. I copied Objective-C code I didn't want to rewrite in Swift just yet into these frameworks. Some classes I rewrote in Swift as they were the perfect possibility to use some great features of Swift. And that's what I did the first two weeks, getting together a solid foundation for Swift apps.
First difficulties
Once the three frameworks were ready enough to work with I continued working on the actual app. I created the CoreData model and let Xcode generate the corresponding NSManagedObject
subclasses. But what's that? The app crashes when I tried to create a new instance of one of the NSManagedObject
subclasses. I've done that a thousand times and I was 100% sure I didn't do anything different this time. The only thing different was Swift. Now, Google is your friend and so I found that due to the "pseudo-namespaces" Swift has, you need to either add @objc($CLASSNAME)
before every NSManagedObject
subclass or to prefix the class names with $PROJECTNAME.
in the CoreData model. First I tried the latter, but that didn't really work out as Xcode then also added it to the filename and the class name the next time I generated the model classes. So I went with adding @objc($CLASSNAME)
before the class definition which would basically be Xcode's job. Also, due to Swifts much better support of primitive types (and String
being one of them) I chose to use primitive types for CoreData, too. And I was surprised that it worked without any problems.
Optionals - A love-hate relationship
The first big change you're going to have to deal with in Swift (aside from the general language differences) are optionals. In Objective-C there was nil
and in Swift there's still nil
, but they're used quite differently. While in Objective-C every pointer could be nil
, in Swift nil
is wrapped into an optional (which is actually a protocol). If a variable/constant isn't an optional it's gotta have a value when you first try to access it. And the compiler makes sure that it does by not allowing you to access the value before there is one. In Objective-C it also wouldn't matter if you send a message to nil
. It actually was kind of a feature and I've used it thousand of times. I used to laugh when my workmate was doing Android development and ran into a NullPointerException
. But then I suddenly stopped laughing when I ran into fatal error: unexpectedly found nil while unwrapping an Optional value
, which is basically the NullPointerException
of Swift. At first I hated Swift for that. Did I really have to make sure that every value has something in it now? I've already hated that in Java. But then I realized that it wasn't like that. In fact, my coding style changed. Instead of not caring about nil
, I tried to get rid of it as soon as possible. So instead of passing optionals all the way through my app, I began to handle them as soon as possible. For example when decoding JSON I tried not to use optionals for my properties wherever possible (except of course if null
is a valid value for a JSON key).
Swift also has "implicitly unwrapped optionals". If you define a variable or property with an !
, Swift will automatically try to unwrap any optionals you assigned when you access it (and fail if it contains nil
). But automatically in this case mostly means dangerous. Because you have no compile-time control over it. You can assign anything to it. You can even assign nil
and it'll work perfectly fine as long as you don't try to access it again. Hence I tried to use !
as little as possible (or only after checking that the optional doesn't contain nil
). Instead I used the if let
feature to safely unwrap optionals.
By now, there are still cases where I hate optionals. But mostly only because they come from "old" Objective-C frameworks from Apple which aren't really made for Swift (more about that in the next paragraph). In most cases I love being sure that there are actual values in my variables / properties.
Apple's Frameworks
While Swift is a great languages, it is currently held back from becoming even better by Apple's own frameworks. Sure, Apple is doing their best to make their frameworks compatible with Swift, but they're still in Objective-C. With Swift 1.2 they also released an update for Objective-C containg features to help developers making their Objective-C code integrate better with Swift. But it's still Objective-C code and you can't get all the cool features of Swift with Objective-C. If we could, Swift would be useless. What we need are Swift versions of Apple's frameworks. For example you can't have lightweight view controllers today because UIViewController inherits indirectly from NSObject
. So every view controller you have will still use the old msg_send
overhead for example. Also, the prefixes should be removed. UILabel
should become Label
, NSFetchedResultsController
-> FetchedResultsController
and so on.
Apple's Tools
Xcode is buggy as hell when it comes to Swift. I'd even go as far and say that Swift is way better than Xcode. Dear Apple, if you want us developers to use Swift, then please release an IDE with which we can actuall use Swift.
What's missing:
- Refactoring: Seriously Apple? Every developer refactors his code. Even if it's something simple as renaming a class or a property. That shouldn't be so hard.
- Interface Builder: Okay, you can create outlets to Swift classes and they'll be properly assigned at runtime. But why do we still have to use
!
for outlets? Xcode knows if an outlet is connected and the compiler could just fail if there's no connection. Also, I want "Generic ViewControllers". For example being able to create aSearchViewController<T: Equatable>: TableViewController
would be nice. Of course you can create such a view controller in code, but you can't use outlets. Why can't you just specify the type in the interface builder? - SourceKitService: I don't care what it's used for. I don't even really care why it crashes. Just make it work again, Apple. Having it crash all the time and then having to wait until it's back is just nuts! (Update: With the latest versions of Xcode this seems to have improved a lot)
I'm sure there's more to add to this list...
Strange bugs, even stranger fixes
When you work with Swift you won't get around strange bugs, yet. You might just end up being unable to compile the project but not getting any help from Xcode why exactly it can't build. You will then go back step by step commenting out what you've done until you're back at a stage where your project compiles again. Then you reverse the process until you find the responsible line of code. Sometimes it's a reasonable error, which is just not reported back to the developer correctly. But most of the time you have such bugs, you'll most likely never understand why exactly it doesn't work. Let me give you an example.
This works:
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return UIStatusBarStyle.Default
}
But this does not (notice the missing enum name in the return):
override func preferredStatusBarStyle() -> UIStatusBarStyle {
return .Default
}
Only in one particular view controller of course.
Another example:
This compiles:
import FFFoundation
import MapKit
class MapViewController: ViewController {
}
This does not:
import FFFoundation
import FFUIKit
import MapKit
class MapViewController: ViewController {
}
First you'll say: There's an error in FFUIKit
! Sorry to disappoint you, but FFUIKit
builds just fine and is used in many different places in the app. The truth is I haven't used anything from FFUIKit
in that class hence it didn't compile. When I removed the import it compiled just fine. Once I used a function from FFUIKit
and imported it again, it also compiled just fine.
If there was a rule that you shouldn't import frameworks you don't use, I'd be okay with that (only if Xcode gives an appropriate error message of course). But just having the compiler crashing around doesn't help much.
So Swift is crap?
Not at all! Although I wrote a lot about Swift's weaknesses, I still love Swift. And it's totally possible to write apps, even customer apps, in Swift. You'll have to deal with some difficulties, but it's nothing that would stop you completely. Also, all of the issues mentioned above aren't permanent. They're bugs and bugs can and most likely will be fixed by Apple. With Swift 1.2 they've already fixed many bugs. I haven't written any code at the time but I'm sure Objective-C wasn't perfect in the beginning, either.
The customer app I wrote is now finished and I'm still happy with my choice to write it in Swift. I've even updated everything to Swift 1.2 in the meantime and it's really another great update for the language as well as Xcode.