Search code examples
iosswiftsecure-coding

How to convert NSCoding to NSSecureCoding?


I got a message in log 'NSKeyedUnarchiveFromData' should not be used to for un-archiving and will be removed in a future release. I am not sure how to convert this class to use NSSecureCoding. I am not sure which class is throwing this error. Could you please help me on how to convert this?

I am using requiringSecureCoding: true. Doesn't this make this secure?

class ZoneInfo: NSObject, NSCoding {
    var zoneID: CKRecordZone.ID
    var serverChangeToken: CKServerChangeToken? = nil
    
    enum CodingKeys: String, CodingKey {
        case zoneID
        case serverChangeToken
    }
    
    func encode(with coder: NSCoder) {
        coder.encode(zoneID.encode(), forKey: CodingKeys.zoneID.rawValue)
        coder.encode(serverChangeToken?.encode(), forKey: CodingKeys.serverChangeToken.rawValue)
    }
    
    required init?(coder: NSCoder) {
        self.zoneID = CKRecordZone.ID.decode(coder.decodeObject(forKey: CodingKeys.zoneID.rawValue) as! Data)!
        if let tokenData = coder.decodeObject(forKey: CodingKeys.serverChangeToken.rawValue) as? Data {
            self.serverChangeToken = CKServerChangeToken.decode(tokenData)
        }
    }
    
    init(zone: CKRecordZone, serverChangeToken: CKServerChangeToken? = nil) {
        self.zoneID = zone.zoneID
        self.serverChangeToken = serverChangeToken
    }
    
    init(zoneID: CKRecordZone.ID, serverChangeToken: CKServerChangeToken? = nil) {
        self.zoneID = zoneID
        self.serverChangeToken = serverChangeToken
    }

    static func decode(_ data: Data) -> ZoneInfo? {
        return try? NSKeyedUnarchiver.unarchivedObject(ofClass: ZoneInfo.self, from: data)
    }
    
    func encode() -> Data {
        return (try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: true)) ?? Data()
    }
}

Thanks.


Solution

  • Your ZoneInfo class should conform to NSSecureCoding, not NSCoding.

    Your encode(with:) is needlessly double-encoding the properties and your init?(coder:) is needlessly double-decoding the properties.

    Here's a corrected version of your class:

    class ZoneInfo: NSObject, NSSecureCoding {
        // Added to support NSSecureCoding
        static var supportsSecureCoding: Bool { return true }
    
        var zoneID: CKRecordZone.ID
        var serverChangeToken: CKServerChangeToken? = nil
    
        enum CodingKeys: String, CodingKey {
            case zoneID
            case serverChangeToken
        }
    
        // Updated to avoid the double-encoding
        func encode(with coder: NSCoder) {
            coder.encode(zoneID, forKey: CodingKeys.zoneID.rawValue)
            coder.encode(serverChangeToken, forKey: CodingKeys.serverChangeToken.rawValue)
        }
    
        // Updated to avoid the double-decoding and to properly handle an invalid zoneID
        required init?(coder: NSCoder) {
            if let zoneID = coder.decodeObject(of: CKRecordZone.ID.self, forKey: CodingKeys.zoneID.rawValue) {
                self.zoneID = zoneID
            } else {
                return nil
            }
            self.serverChangeToken = coder.decodeObject(of: CKServerChangeToken.self, forKey: CodingKeys.serverChangeToken.rawValue)
        }
    
        init(zone: CKRecordZone, serverChangeToken: CKServerChangeToken? = nil) {
            self.zoneID = zone.zoneID
            self.serverChangeToken = serverChangeToken
        }
    
        init(zoneID: CKRecordZone.ID, serverChangeToken: CKServerChangeToken? = nil) {
            self.zoneID = zoneID
            self.serverChangeToken = serverChangeToken
        }
    
        static func decode(_ data: Data) -> ZoneInfo? {
            return try? NSKeyedUnarchiver.unarchivedObject(ofClass: ZoneInfo.self, from: data)
        }
    
        func encode() -> Data {
            return (try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: true)) ?? Data()
        }
    }
    

    Since I can't test this with an actual instance of CKServerChangeToken, I was only able to verify that the above works with a zoneID. If you have an issue at runtime when you try to unarchive an instance of ZoneInfo with a real CKServerChangeToken value, let me know in a comment what the error is so I can update the answer if needed.

    Note that the use of CodingKeys isn't normally used with NSSecureCoding. It's normally used for Swift Codable. Your use here does work, it's just a bit confusing.

    I also suggest that your encode method return Data? instead of returning an empty Data instance on failure. Returning empty data will just lead to issues when you try to unarchive that empty data.