Thoughts on Swift, From an Idiot

Swift (-taylor) is the exciting new language Apple debuted at WWDC in 2014. It took pretty much all of us in the developer community by surprise. We’re now in August, 5 beta builds into Xcode and I feel like I’ve started to form an opinion about the language itself.

First the good parts:

  • It looks a million times cleaner than Objective-C code
  • I won’t miss the [square brackets]
  • It’s much nicer to read
  • It has a lot of modern ideas and influences that make it a joy to write
  • It’s apparently fast, and only getting faster

And that’s where, for me, it ends. You see Apple sold me on the promise that this would solve my number one gripe about Objective-C: memory management issues. I had assumed my number two gripe, proper stack traces on crashes, was joining us for the ride. If you want to feel some of the pain that comes with Objective-C development, feel free to peruse this crash, from an older version of Pocket Casts:

Thread 0 Crashed:
0   libsystem_kernel.dylib               0x000000019a38e58c __pthread_kill + 8
1   libsystem_pthread.dylib              0x000000019a41116c pthread_kill + 100
2   libsystem_c.dylib                    0x000000019a322808 abort + 108
3   libsystem_malloc.dylib               0x000000019a3c85c4 nanozone_error + 292
4   libsystem_malloc.dylib               0x000000019a3c8778 _nano_malloc_check_clear + 432
5   libsystem_malloc.dylib               0x000000019a3c725c nano_malloc + 40
6   libsystem_malloc.dylib               0x000000019a3b8368 malloc_zone_malloc + 92
7   CoreFoundation                       0x000000018d68fd70 _CFRuntimeCreateInstance + 268
8   CoreGraphics                         0x000000018d81c9dc CGTypeCreateInstance + 56
9   CoreGraphics                         0x000000018d8262ec create_provider + 52
10  CoreGraphics                         0x000000018d826284 CGDataProviderCreateDirect + 52
11  CoreGraphics                         0x000000018d826228 CGDataProviderCreateWithCFData + 88
12  CoreGraphics                         0x000000018d8261a4 CGDataProviderCreateWithCopyOfData + 364
13  CoreGraphics                         0x000000018d825ee8 CGBitmapContextCreateImage + 84
14  UIKit                                0x000000019076d038 _UIGraphicsGetImageFromCurrentImageContext + 196
15  UIKit                                0x000000019076f830 -[UIImageView _setImageViewContents:] + 1244
16  UIKit                                0x000000019076ede0 +[UIView(Animation) performWithoutAnimation:] + 84
17  UIKit                                0x000000019076e748 -[UIImageView setImage:] + 836
18  UIKit                                0x0000000190b128b8 -[UIButton _updateImageView] + 128
19  UIKit                                0x00000001907b7e2c -[UIButton layoutSubviews] + 100
20  UIKit                                0x000000019075afe0 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 344
21  QuartzCore                           0x000000019034c258 -[CALayer layoutSublayers] + 180
22  QuartzCore                           0x0000000190346e20 CA::Layer::layout_if_needed(CA::Transaction*) + 296
23  QuartzCore                           0x0000000190346cd8 CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 28
24  QuartzCore                           0x0000000190346560 CA::Context::commit_transaction(CA::Transaction*) + 276
25  QuartzCore                           0x0000000190346304 CA::Transaction::commit() + 420
26  QuartzCore                           0x000000019033fc38 CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 76
27  CoreFoundation                       0x000000018d757858 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 28
28  CoreFoundation                       0x000000018d754ae0 __CFRunLoopDoObservers + 368
29  CoreFoundation                       0x000000018d754e6c __CFRunLoopRun + 760
30  CoreFoundation                       0x000000018d695dd0 CFRunLoopRunSpecific + 448
31  GraphicsServices                     0x000000019337dc0c GSEventRunModal + 164
32  UIKit                                0x00000001907c6fc4 UIApplicationMain + 1152
33  podcasts                             0x000000010004e9b4 _mh_execute_header (main.m:14)
34  libdyld.dylib                        0x000000019a293aa0 start + 0

Looks very detailed right? So as an app developer you get that back, and now it’s your job to figure out why. The first thing you look for is your app’s line numbers, so you can follow the execution path. Our app executable is called ‘podcasts’ and we can see it’s in main.m, line 14. Now most Objective-C developers have already skipped ahead and know where this is going, but indulge me. Here’s line 14:

return UIApplicationMain(argc, argv, nil, NSStringFromClass([SJAppDelegate class]));

Oh…it’s the line our app was launched from, and doesn’t really bare any resemblance to where the crash actually started from. What button did the user press? Which screen where they on? There’s no way to know. Now before you email me, I know exactly why it’s like that. I’ve put up with it for many years. Currently in Swift, those stack traces are even worse. I won’t post one until they actually release it to be fair to Apple…but why oh why when they were making a ‘modern’ programming language could they not solve this? I know, the Objective-C runtime is hailed by many the world over as being fast, and awesome. But it’s 2014, the things I actually care about are the problems Microsoft and Sun Microsystems solved, memory management and reliability. If it comes at the expense of a tiny amount of speed, I’ll happily take it. I can count the amount of times on no hands that my code was running slow, and it was the languages fault. That goes for Java, Objective-C, C#, C++, Ruby and so many other languages. Don’t get me wrong, I love more speed, but give me more reliability first.

Update: Someone on Twitter wanted to be clever and pick apart the actual stack trace I posted. It was an example kids. But here’s a more specific one:

Thread 0 Crashed:
0   libobjc.A.dylib                      0x3a6f7626 objc_msgSend + 6
1   CoreFoundation                       0x2ff14f01 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 10
2   CoreFoundation                       0x2fe88d69 _CFXNotificationPost + 1718
3   Foundation                           0x30874cc5 -[NSNotificationCenter postNotificationName:object:userInfo:] + 70
4   UIKit                                0x329e545f -[UIApplication _sendWillEnterForegroundCallbacks] + 152
5   UIKit                                0x3298a949 -[UIApplication _handleApplicationResumeEvent:] + 1146
6   UIKit                                0x327895f3 -[UIApplication handleEvent:withNewEvent:] + 1880
7   UIKit                                0x32788dd9 -[UIApplication sendEvent:] + 70
8   UIKit                                0x327ed3e5 _UIApplicationHandleEvent + 614
9   GraphicsServices                     0x34df6b55 _PurpleEventCallback + 606
10  GraphicsServices                     0x34df673f PurpleEventCallback + 32
11  CoreFoundation                       0x2ff1d807 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32
12  CoreFoundation                       0x2ff1d7a3 __CFRunLoopDoSource1 + 344
13  CoreFoundation                       0x2ff1bf6f __CFRunLoopRun + 1404
14  CoreFoundation                       0x2fe86729 CFRunLoopRunSpecific + 522
15  CoreFoundation                       0x2fe8650b CFRunLoopRunInMode + 104
16  GraphicsServices                     0x34df56d3 GSEventRunModal + 136
17  UIKit                                0x327e7871 UIApplicationMain + 1134
18  podcasts                             0x000851cf main (main.m:14)
19  libdyld.dylib                        0x3abebab7 start + 0

I know the cause of this. I forgot to call removeObserver somewhere. But where? Damned if I know unless I give every single one of my methods unique names. Pointers are fun. My point is, a language/framework/runtime combination that is modern, should remove that guesswork.

As much as people hate Java, and to some extent I’m in that camp too, here’s an equivalent crash from our Android app:

java.lang.NullPointerException
	at au.com.shiftyjelly.pocketcasts.PodcastDialog.addToPodcastLibrary(PodcastDialog.java:465)
	at au.com.shiftyjelly.pocketcasts.PodcastDialog.subscribeToPodcast(PodcastDialog.java:257)
	at au.com.shiftyjelly.pocketcasts.PodcastDialog.access$400(PodcastDialog.java:48)
	at au.com.shiftyjelly.pocketcasts.PodcastDialog$2.onClick(PodcastDialog.java:201)
	at android.view.View.performClick(View.java:4438)
	at android.view.View$PerformClick.run(View.java:18422)
	at android.os.Handler.handleCallback(Handler.java:733)
	at android.os.Handler.dispatchMessage(Handler.java:95)
	at android.os.Looper.loop(Looper.java:136)
	at android.app.ActivityThread.main(ActivityThread.java:5001)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:515)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
	at dalvik.system.NativeStart.main(Native Method)

Yes I know, ha ha Null Pointer, Java, LOL. But that’s an exact line number friends. What did the user do? They tapped the subscribe button. Which page where they on? The Podcast Dialog. Zero ambiguity. Guess how many of our Android crashes we get that for? 100%. In iOS we’d be lucky if even 30% of our crashes had stack traces we can line up to actual things we can then reproduce. So most iOS crashes today involve me becoming House MD and poking the code for hours, only to figure out that like always, it’s never Lupus.

I haven’t even covered the crashes I’ve seen where Objective-C was sending a message to the wrong object, a classic sign that the object you thought you had has been released and you’re now talking to something completely different. I’m assuming that Swift, running on the same runtime, with the same compiler, is going to have that exact same issue. I also haven’t gone into how the entire Cocoa/Cocoa Touch API is built for Objective-C and is still at times awkward to interact with in Swift. That one at least feels like mostly a transitional issue. So count me disappointed until the day Apple announce a new runtime that actually gets rid of memory related crashes, and gives us accurate stack traces. Until that day, old man Rusty is just going to keep yelling “get off my lawn, kiddo’s”.