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.

class ViewController : UIViewController {
override func decodeRestorableState(with coder: NSCoder) {
super.decodeRestorableState(with: coder)
// force class initialization for the sake of state restoration
ViewModel.classInit
viewModel = coder.decodeObject(of: ViewModel.self, forKey: "viewModel")
}
}
@objc(ViewModel)
class ViewModel : NSObject,NSCoding {
static let classInit : () = {
// force global initialization for state restoration purposes
Entry.classInit
}()
@objc(ViewModel_Entry)
class Entry : NSObject,NSCoding {
static let classInit : () = {
// force global initialization for state restoration purposes
_ = Entry.self
}()
}
}
view raw ViewModel.swift hosted with ❤ by GitHub

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!



Avoid App Review rules by distributing outside the Mac App Store!


Get my FREE cheat sheets to help you distribute real macOS applications directly to power users.

* indicates required

When you subscribe you’ll also get programming tips, business advices, and career rants from the trenches about twice a month. I respect your e-mail privacy.

Avoid Delays and Rejections when Submitting Your App to The Store!


Follow my FREE cheat sheets to design, develop, or even amend your app to deserve its virtual shelf space in the App Store.

* indicates required

When you subscribe you’ll also get programming tips, business advices, and career rants from the trenches about twice a month. I respect your e-mail privacy.

0 thoughts on “State Restoration Hiccups with Swift

Leave a Reply