1

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…

2 Answers 2

1

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")
    }      
}
Sign up to request clarification or add additional context in comments.

Comments

0

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")
    }      
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.