Jens Kohl
Jens Kohl

Reputation: 5969

Encoding/Decoding an array of objects which implements a protocol in Swift 2

I have got a class that inherits from NSObject and I want it to be NSCoding compliant. But I ran into trouble while encoding an array of objects which should implement a protocol.

protocol MyProtocol {
    var myDescription: String { get }
}

class DummyClass: NSObject, NSCopying, MyProtocol {
    var myDescription: String {
        return "Some description"
    }

    func encodeWithCoder(aCoder: NSCoder) {
        // does not need to do anything since myDescription is a computed property
    }

    override init() { super.init() }
    required init?(coder aDecoder: NSCoder) { super.init() }
}

class MyClass: NSObject, NSCoding {
    let myCollection: [MyProtocol]

    init(myCollection: [MyProtocol]) {
        self.myCollection = myCollection

        super.init()
    }

    required convenience init?(coder aDecoder: NSCoder) {
        let collection = aDecoder.decodeObjectForKey("collection") as! [MyProtocol]

        self.init(myCollection: collection)
    }

    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(myCollection, forKey: "collection")
    }
}

For aCoder.encodeObject(myCollection, forKey: "collection") I get the error:

Cannot convert value of type '[MyProtocol]' to expected argument type 'AnyObject?'

OK, a protocol obviously is not an instance of a class and so it isn't AnyObject? but I've no idea how to fix that. Probably there is a trick that I'm not aware? Or do you do archiving/serialization differently in Swift as in Objective-C?

There's probably a problem with let collection = aDecoder.decodeObjectForKey("collection") as! [MyProtocol], too but the compiler doesn't complain yet…

Upvotes: 1

Views: 762

Answers (2)

davejlin
davejlin

Reputation: 1068

I know your title specifies Swift 2, but just for reference, for a similar problem I was working on, I found that in Swift 3, you don't need to convert anymore to AnyObject.

The following works for me in Swift 3 (using your example):

class MyClass: NSObject, NSCoding {
    let myCollection: [MyProtocol]

    init(myCollection: [MyProtocol]) {
        self.myCollection = myCollection
        super.init()
    }

    required convenience init?(coder aDecoder: NSCoder) {
        let collection = aDecoder.decodeObject(forKey: "collection") as! [MyProtocol]    
        self.init(myCollection: collection)
    }

    func encodeWithCoder(aCoder: NSCoder) {    
        aCoder.encode(aCollection, forKey: "collection")
    }      
}

Upvotes: 0

Jens Kohl
Jens Kohl

Reputation: 5969

I've just found the solution myself: The key is to map myCollection into [AnyObject] and vice-versa, like so:

class MyClass: NSObject, NSCoding {
    let myCollection: [MyProtocol]

    init(myCollection: [MyProtocol]) {
        self.myCollection = myCollection

        super.init()
    }

    required convenience init?(coder aDecoder: NSCoder) {
        let collection1 = aDecoder.decodeObjectForKey("collection") as! [AnyObject]

        let collection2: [MyProtocol] = collection1.map { $0 as! MyProtocol }


        self.init(myCollection: collection2)
    }

    func encodeWithCoder(aCoder: NSCoder) {
        let aCollection: [AnyObject] = myCollection.map { $0 as! AnyObject }

        aCoder.encodeObject(aCollection, forKey: "collection")
    }      
}

Upvotes: 1

Related Questions