Swift optimiser causing a crash in SpriteKit app

Development of my upcoming (Swift & SpriteKit) game had reached a point where I was happy to let other people loose on it, so  I decided to submit it to Apple’s TestFlight service. Boom, rejection! The app crashes when touching the screen. This post is a record of my investigations into what turned out to be one of the most annoying issues I’ve encountered.

You can follow along with a sample project I have prepared: https://github.com/NxSoftware/Swift-SpriteKit-Crash.

A crash you say? Nay!

 

903

OK, what? That never happened during development. I quickly identified that the crash only occurs under the Release configuration. In addition, I discovered the app also crashes when attempting to pause the game (again, under the Release configuration).

Screen Shot 2015-05-25 at 11.06.06
Crash when attempting to pause the game
Screen Shot 2015-05-25 at 10.57.54
Crash when tapping the screen

There’s definitely issues leading to crashes, but not a great deal of information I can extract from the stack traces (please let me know if I’ve missed something obvious).

The hunt begins…

At this point the game has enough code to make me suspect that it’s something I’m doing. I fire off the static analyser in the hope that it will spot something I haven’t. Nothing. Nothing?!? I don’t think I’ve ever had 0 issues on the first pass.

It became obvious that this is going to be something that needs to be tracked down manually. Let’s check what is different between a Debug and Release build. Scrolling through the target’s Build Settings tab there isn’t actually that many debug/release specific settings, but optimisation stands out as a likely culprit.

Swift Compiler - Code Generation settings

Optimisation is disabled (-Onone) for debug, and set to Fastest (-O) for release. I enable optimisation on the debug build and hit run, touch the screen and there it is, my old friend EXC_BAD_ACCESS.

Now I know the crash(es) are caused by “something” the optimiser is doing, but still no clue as to why it’s happening or how to go about fixing it. And no, disabling optimisation for release isn’t really an option.

Is it just me?

I take to Google to see if there are others that have had similar SpriteKit + Swift optimiser issues such as this but unfortunately the outlook is bleak. This reinforces my belief that there is something lurking in my code.

As I’m still learning Swift, the code isn’t the easiest to follow and it makes for a bit of a tricky exercise trying to spot potential issues that I would find much easier in Objective-C, though of course this will improve over time. Nevertheless I stare at the project for a few days, getting nowhere. Even resorting to a very quickly aborted attempt to compare the debug vs release assembler using Hopper, in the process finding an insightful article on the Secret of Swift Performance.

Apple to the rescue

After spending a couple of weeks (this is a project I’m working on in my spare time) to no avail, I decide to take advantage of one of the Technical Support Incidents (TSI) included in my annual developer subscription.

In preparation I attempt to isolate circumstances where the crashes occur/do not occur. Here is my initial TSI request:-


 

PLATFORM AND VERSION
iOS
Build environment:
MacBook Pro (Retina, 15-inch, Late 2013)
OS X 10.10.3 (14D136)
Xcode 6.3.1 (6D1002)
iOS Simulator 8.3

Devices:
iPhone 5, iOS 8.3 (12F70)
iPad Air, iOS 8.3 (12F69)

DESCRIPTION OF PROBLEM
I have created a Swift based SpriteKit project and I am experiencing several crashes under varying circumstances that only occur when compiling with “Fastest (-O)” optimization. As documented:-

Simulator (iPhone 5/5s), Debug, Swift Optimization Level None (-Onone). No crash.

Device (iPhone 5), Debug, Swift Optimization Level None (-Onone). No crash.

Simulator (iPhone 5), Debug, Swift Optimization Level Fastest (-O).
The following crashes occur:-
* objc_retain when touching the left-most brick.
* objc_msgSend when touching anywhere else in the left 25% of the screen.
* objc_msgSend when attempting to pause the game.

Simulator (iPhone 5s), Debug, Swift Optimization Level Fastest (-O).
The following crashes occur:-
* objc_retain when touching the left-most brick.
* objc_msgSend when touching anywhere else in the left 25% of the screen.
* objc_retain when attempting to pause the game.

Device (iPhone 5), Debug, Swift Optimization Level Fastest (-O).
The following crashes occur only when NOT connected to the debugger:-
* Touching anywhere in the left 25% of the screen (including the brick).
* Attempting to pause the game.

Device (iPad Air), Debug, Swift Optimization Level Fastest (-O). No crashes occur regardless of whether or not the debugger is connected.

The stack trace includes internal SpriteKit method calls that indicate the crash occurs when traversing the node hierarchy (such as SKNode nodesAtPoint: and children).

STEPS TO REPRODUCE
The steps vary slightly based on the exact crash but generally follow the same pattern:-

1) Run the project in the simulator with -O Swift optimization.
2) Start a new game and wait for the countdown to complete.
3) Tap anywhere in the left 25% of the screen or attempt to pause the game.


At this point I’m starting to believe that this is a bug generated by the Swift optimiser. I patiently wait for a response, what I received was a bit disappointing.


Thank you for contacting Apple Developer Technical Support (DTS).

After reviewing your case, Apple Developer Technical Support recommends that you use the Zombies Instrument to investigate the problem:

https://developer.apple.com/library/ios/recipes/Instruments_help_articles/FindingMessagesSenttoDeallocatedObjects/FindingMessagesSenttoDeallocatedObjects.html

Crashes with the symptoms you are describing are usually caused by attempting to use an object, after it has been deallocated. After triggering the problem while running the Zombies instrument, you can use the extra information that instrument provides to track down the problem.

[more information on how to use the Zombies instrument]


Admittedly I hadn’t tried the Zombies Instrument but I was naively expecting a bit more of a personal response than this. Had they even opened the attached project? However I appreciate they must get a lot of support requests and likely don’t have the time to do developer’s debugging for them.

Time to run the app with Instruments, oh wait, no crash, no message about accessing a deallocated object, nothing.

fuuuuuuu

I send a message back to Apple to let them know that the Zombies Instrument hasn’t helped and again wait for a response.


Thank you for contacting Apple Developer Technical Support (DTS). Our engineers have reviewed your request and have determined that this would be best handled as a bug report.

Please submit a complete bug report regarding this issue using the Bug Reporting tool at <https://developer.apple.com/bug-reporting/>.


 

noooooo

It’s well known in the Apple developer community that bug reports (better known as Radars), rarely reach a timely/satisfactory conclusion. Not only that, but submitting a bug report won’t magically make my game playable. Anyway I want to be a good developer and set about preparing a small sample project that can be used to reproduce the issue. Maybe in doing so I will discover a workaround.

Pulling it all apart

I start by deleting screens and chunks of code that are completely irrelevant, followed by smaller bits and pieces. Testing to make sure the crash is still present as I go. I delete a call to a setter on a SKSpriteNode subclass, the crash goes away, ooh now we’re getting somewhere.

I can’t see anything particularly special about this line, other than the property’s type is a typealias. Just for my own sanity I change it to it’s underlying type, UInt8. No luck, still crashes, good(?).

So far at least it would appear that it is this line that causes the crash, I leave it in place and proceed to delete other bits of code. Next up is an innocuous looking for loop.

Before
Before
After
After

The crash goes away. OK this is really getting weird now and I’m (still) struggling to get my head around what is going on. But the madness continues…

Refactoring the contents of the scene’s spawnBrick function into the loop also fixes the crash.

spawnBrick function
spawnBrick function…
… refactored into loop body

I thought I had an epiphany when this refactor proved to be successful, maybe adding the sprite to the scene prior to returning the sprite is giving the optimiser a headache. Let’s try adding the sprite to the scene later on.

Defer adding the sprite to the scene
Defer adding the sprite to the scene

This doesn’t work either, not particularly surprising. Considering how confused I am, I also try adding the sprite to the scene after setting it’s strength. Just to add insult to injury this causes a crash at the point where addChild is called!

Add to the scene last
Add to the scene last
That's a new one
That’s a new one

The final straw

After finding all of the issues in what I would otherwise believe was perfectly standard code the only other thing that stood out was the SKSpriteNode subclass.

It’s a final class, but this is perfectly acceptable right? Wrong, removing the final keyword will fix all of the crashes mentioned in this post.

Wrap it up

Now that I have a concise project that can consistently reproduce these issues, I have submitted radar 21108886 (OpenRadar) at Apple’s request. If you have any information as to why these crashes might be occurring please let me know as it’s been eating away at my soul for a couple of weeks now!

As for how I will proceed to get my game onto the App Store, I have decided that removing the final status of my SKSpriteNode subclass will be a sufficient compromise at least until I can get a better understanding of why I’ve ended up in this mess in the first place.

Leave a Reply

Your email address will not be published. Required fields are marked *