iosswiftcodablestate-restoration

Swift Codable: marking an object to work with state restoration


I have a Swift class which stores different types of filters currently applied to my view controller. I want to save this using state restoration, so that if the user leaves the app and comes back, the filters are restored even if the app was terminated by the system.

I'm not sure what the best way to do this is. I believe it's something to do with Codable, but how to make that work with the UIViewController encoding/decoding.

First, the class:

@objcMembers class CJDataViewControllerFilters: NSObject {
    
    var selectedDateFilters = [String: Array<Date>]()
    var selectedTitleFilters = [NSManagedObject]()
    var customFilterPredicate: NSPredicate?
    var customFilterName: String?
    let filterStyle: SectionStyle
    
    init(style: SectionStyle) {
        self.filterStyle = style
        super.init()
    }
}

The UIViewController (which is still in Objective-C):

@interface DiaryViewController : UIViewController {

  @property (nonatomic, strong) CJDataViewControllerFilters *dataFilters;
}

and calling state restoration:

- (void)encodeRestorableStateWithCoder:(NSCoder *)coder {

    [super encodeRestorableStateWithCoder:coder];
        
    [coder encodeObject:self.dataFilters forKey: @"DataFiltersDiary"];
}

Just doing this will crash when the app goes to background:

2020-08-15 11:37:55.470081-0700 CJournal[11367:6382501] -[XYZApp.CJDataViewControllerFilters encodeWithCoder:]: unrecognized selector sent to instance 0x6000014cb640

So I added Codable as a protocol to the CJDataViewControllerFilters class, and added basic conformance:

@objcMembers class CJDataViewControllerFilters: NSObject, Codable {

    func encode(to encoder: Encoder) throws {
            print("CJDataViewControllerFilters: encode")
    }
    required init(from decoder: Decoder) throws{
        print("init(from decoder)")
    }
}

It still crashes for the same reason.

How do I best resolve this? Is there a different method to implement? Also, how do I manually encode/decode things like NSPreciate, and the array of NSManagedObjects?

Most of the examples on Codable refer to JSON coding/encoding, which I don't need here (I think), so would appreciate an answer.


Solution

  • The way to encode and decode an NSObject is to make it conform to NSCoding (nowadays, NSSecureCoding). Now you can use a keyed archiver to get it into the coder at saving time, and use a keyed unarchiver to get it out of the coder at restoration time.

    Most built-in types such as NSPredicate already conform to NSSecureCoding, so the problem on the whole is solved before you arrive.

    If your type has Codable properties that are not toll-free bridged to NSCoding types (like String to NSString), you can encode them using the NSCoder subclass methods encodeEncodable(_:forKey:) and decodeDecodable(_:forKey:).