//
you're reading...

Programming

State Restoration Hiccups with Swift

Not too long ago, my Swift-based tvOS app was crashing during state restoration. This is the Wake-on-LAN app for tvOS. The crash log said that a class was missing when decoding state restoration data:

*** Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*** -[UIStateRestorationKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (…) for key (NS.objects); the class may be defined in source code or a library that is not linked' ***

Missing Piece

It was pretty puzzling at first. Having the class undefined is pretty much impossible because this is a state restoration case — all de-serialized objects were created by the exact same code. Moreover, that particular missing class (name replaced with ellipsis for brevity) is linked to the main executable — not some dynamic library that possibly not yet loaded at the time of app restoration.

Then I thought, perhaps it’s because of Swift’s name mangling. The allegedly “missing” class is an inner class contained within a view-model class. To Objective-C, it has a pretty cryptic name which showed up in the crash report. I’ve tried creating a “proper” Objective-C alias using the @objc(…) directive. But still the same crash happened again, but this time it shows the Objective-C alias of the inner class in the crash log.

Finally I’ve solved the problem by forcibly referring to that inner class just prior to decoding state restoration data that may contain its objects. Surprisingly it works. That is in the decodeRestorableState function, I make a reference to the view-model class to ensure it gets loaded and in turn, it also pre-emptively loads any dependent classes the same way.

The following is a greatly-simplified diagram of how my view model class gets set up:

Keeping in mind of the relationships above, the following is one way to initialize the view-model classes and ensure they are loaded prior to restoring user interface state data.

For the purpose of initializing the class and ensuring that the Objective-C half gets setup, I’ve created a series of classInit no-value static member that’s sole purpose is just to be referred. At the end of the chain, the Entry class just calls self that should instantiate the Objective-C metaclass instance for it.

I found this solution partially inspired by Swift’s preference of lazy initialization. Notably, static members are computed only when it is first referred. I thought to myself, “Maybe that class wasn’t initialized during state restoration? At least the Objective-C portion of it — probably the Class instance isn’t yet created and registered to the runtime.” Which apparently this is true.

However strangely view controllers and table view cells that are instantiated from storyboards works fine out-of-the-box. They can be instantiated by storyboards without referring to the class beforehand. Then again, maybe Apple made a hack inside the UIKit framework just to support Swift — since this is a common case after all. But my view-model class descend directly from NSObject, which may not be included as part of this hack.

For the record, this issue occurred in  tvOS 10.2.1 / Swift 3.1.

That’s all for now. Take care!



Do you enjoy this post? Enter your e-mail address below to receive articles like this one in your mailbox.
* indicates required

Discussion

No comments yet.

Leave a Reply

Free Updates!

Learn how to grow your indie business while keeping your day job.

Categories

Archives

Keep updated!

Don't miss out on new articles!