Hiya 👋! While loading some data, I'm having issues decoding arrays of basic types, specifically Int and Bool – they return nil. My app has existing data (live on the App Store for years) that is saved and loaded using NSKeyedArchiver. I'm updating to support a newer iOS version, which requires me now to adhere to NSSecureCoding. As I understand, NSSecureCoding needs strict type definitions, and it will return nil if things are ambiguous (for security reasons).
Essentially as I load data, it all works when I use strict types, like Int and Bool, because the methods themselves are strict: decodeInteger(forKey:) and decodeBool(forKey:). However, if I want to decode something more complex, like NSDate or basic type arrays (in my case [Int], [[[Int]]], [Bool] and [[Bool]]), I assume I have to decode an object: decodeObject(of:forKey:). While I was able to make NSDate work by forcing the type (decodeObject(of: NSDate.self, forKey: "modifyDate")! as Date
), getting arrays to decode is proving difficult. They always return nil.
I've now tried forcing the type to different arrays, including NSArray, and listing array types. I also tried decodeArrayOfObjects(ofClass:forKey:), but no luck.
Example:
Here are some data items I might have:
var id: Int?
var isModified: Bool?
var modifyDate: Date?
var myIntegers: [Int]?
var myEmbedIntegers: [[[Int]]]?
var myBooleans: [Bool]?
var myEmbedBooleans: [[Bool]]?
Here's how I encode them:
encode(id!, forKey: "id")
encode(isModified!, forKey: "isModified")
encode(modifyDate!, forKey: "modifyDate")
encode(myIntegers!, forKey: "myIntegers")
encode(myEmbedIntegers!, forKey: "myEmbedIntegers")
encode(myBooleans!, forKey: "myBooleans")
encode(myEmbedBooleans!, forKey: "myEmbedBooleans")
This is how Int, Bool and Date are apparently successfully decoded (where Date is forced to search for NSDate type):
decodeInteger(forKey: "id")
decodeBool(forKey: "isModified")
decodeObject(of: NSDate.self, forKey: "modifyDate")! as Date
Here are attempts with Int (same with Bool) arrays, which are erroneously decoded (these all either get compiler errors or return nil):
decodeObject(forKey: "myIntegers") as! [Int] // This used to work before NSSecureCoding, but now returns nil.
decodeObject(of: [NSArray.self], forKey: "myIntegers") as! [Int] // Returns nil.
decodeObject(of: [Int.self], forKey: "myIntegers") // Compiler error about value type conversion.
decodeArrayOfObjects(ofClass: Int.self, forKey: "myIntegers") // Compiler error complaining that Int doesn't conform to NSSecureCoding.
decodeArrayOfObjects(ofClass: NSArray.self, forKey: "myIntegers") as! [Int] // Returns nil.
The funny thing is I don't even remotely need security. My data is for song compositions in an entertainment app, so it's strictly loaded and saved to device by my own code without networking, hashing or anything else being involved. Nevertheless I'm now stuck on this 😥.
How do I decode arrays (without returning nil)?
Keyed archiving is very Objective-C centric. So, when you encode a Swift Array
, it’s actually stored as an NSArray
. NSArray
can only hold objects. The Swift Bool
and Int
types are not objects. When you put them in a keyed archive, they get stored as NSNumber
objects.
Normally this isn’t a big deal because the Swift compiler goes out of its way to help you convert the types. However, when working with a secure keyed archive you have to list the element type, and that has to be an Objective-C object type. So, to decode an array of Bool
values, you have to tell the keyed archiving to decode an array of NSNumber
objects.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"