archive array of optional structs with NSCoding in Swift? archive array of optional structs with NSCoding in Swift? arrays arrays

archive array of optional structs with NSCoding in Swift?


In The Swift Programming Language, Apple states:

Swift provides two special type aliases for working with non-specific types:
- AnyObject can represent an instance of any class type.
- Any can represent an instance of any type at all, including function types.

Knowing that, type SquareCoords (Swift Structure) and type [SquareCoords] (Swift array of Swift Structure) can't conform to protocol AnyObject.

On the other hand, decodeObjectForKey: requires a parameter that conforms to protocol AnyObject, and encodeObject:forKey: returns AnyObject. Thus, the two following lines can't compile:

moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as [SquareCoords?]aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")

Therefore, unless you find a way to make SquareCoords conform to protocol AnyObject (I don't know if it's possible), you will have to transform SquareCoords from Swift Structure to Class.

PS: At this point, you may ask: "OK, but how is it possible that type String - that is in fact a Swift Struct - can conform to protocol AnyObject?" Well, that's because String is bridged seamlessly to Foundation’s NSString class (Array, Dictionary are bridged to NSArray and NSDictionary the same way). Read this blog post if you want to have a better look at it.


I'm not sure what the exact problem is, but if you use NSMutableArray rather than a Swift array the problem resolves:

public struct SquareCoords {    var x: Int, y: Int}public class Player: NSCoding {    var playerNum: Int    var name = ""    var moveHistory: NSMutableArray = NSMutableArray()    init (playerNum: Int, name: String) {        self.playerNum = playerNum        self.name = name    }    public required init(coder aDecoder: NSCoder!) {        playerNum = aDecoder.decodeIntegerForKey("playerNumKey")        name = aDecoder.decodeObjectForKey("nameKey") as String        moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as NSMutableArray    }    public func encodeWithCoder(aCoder: NSCoder!) {        aCoder.encodeInteger(playerNum, forKey: "playerNumKey")        aCoder.encodeObject(name, forKey: "nameKey")        aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")    }}

It seems to be the case that when aDecoder.decodeObjectForKey returns an implicitly unwrapped AnyObject this won't cast to a SquareCoords array.

Been playing with this a little further and I noticed it may have something to do with the use of a struct. (you're creating an array of structs which are value types.) This is a bit of a guess but I noticed if a class type is used for SquareCoords there is no issue, e.g.

public class SquareCoords {    var x: Int = 0, y: Int = 0}public class Player: NSCoding {    var playerNum: Int    var name = ""    private var moveHistory: [SquareCoords] = [SquareCoords]()init (playerNum: Int, name: String) {    self.playerNum = playerNum    self.name = name}public required init(coder aDecoder: NSCoder!) {    playerNum = aDecoder.decodeIntegerForKey("playerNumKey")    name = aDecoder.decodeObjectForKey("nameKey") as String    moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as [SquareCoords]}public func encodeWithCoder(aCoder: NSCoder!) {    aCoder.encodeInteger(playerNum, forKey: "playerNumKey")    aCoder.encodeObject(name, forKey: "nameKey")    aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")    }}

Maybe the cast from AnyObject fails to a struct array for some reason. - I'm sure someone else can provide more insight, hope this helps somewhat! Swift can be tempestuous :D


The other answers given here solves your problem. But, I faced a similar issue recently while trying to archive my Swift structures and came up with an interesting way to solve this. NSCoding doesn't support structures.

Essentially, the following method involves converting the structure properties into dictionary elements. But, it does that elegantly using protocols. All you need to do is define a protocol which implements two methods which aid in Dictionary-fying and un-Dictionary-fying your structure. The advantage of using protocols and generics is that it works when a structure is a property of another structure. This nesting could be to any depth.

I call the protocol 'Dictionariable', which indicates that anything that conforms to the protocol can be converted to a dictionary. The definition goes as below.

protocol Dictionariable {    func dictionaryRepresentation() -> NSDictionary    init?(dictionaryRepresentation: NSDictionary?)}

Now, consider a structure 'Movie'

struct Movie {    let name: String    let director: String    let releaseYear: Int}

Let me extend the structure and make it conform to 'Dictionariable' protocol.

extension Movie: Dictionariable {    func dictionaryRepresentation() -> NSDictionary {        let representation: [String: AnyObject] = [            "name": name,            "director": director,            "releaseYear": releaseYear        ]        return representation    }    init?(dictionaryRepresentation: NSDictionary?) {        guard let values = dictionaryRepresentation else {return nil}        if let name = values["name"] as? String,            director = values["director"] as? String,            releaseYear = values["releaseYear"] as? Int {                self.name = name                self.director = director                self.releaseYear = releaseYear        } else {            return nil        }    }}

Basically, we now have a way to safely convert a structure to a dictionary. I say safe because we implement how the dictionary is formed, individual to every structure. As long as that implementation is right, the functionality would work. The way to get back the structure from the dictionary is by using a failable initialiser. It has to be failable because file corruptions and other reasons could make the structure's instantiation from an archive incomplete. This may never happen, but, it's safer that it's failable.

func extractStructuresFromArchive<T: Dictionariable>() -> [T] {    guard let encodedArray = NSKeyedUnarchiver.unarchiveObjectWithFile(path()) as? [AnyObject] else {return []}    return encodedArray.map{$0 as? NSDictionary}.flatMap{T(dictionaryRepresentation: $0)}}func archiveStructureInstances<T: Dictionariable>(structures: [T]) {    let encodedValues = structures.map{$0.dictionaryRepresentation()}    NSKeyedArchiver.archiveRootObject(encodedValues, toFile: path())}//Method to get path to encode stuctures tofunc path() -> String {    let documentsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).first    let path = documentsPath?.stringByAppendingString("/Movie")    return path!}

The two above methods can archive and unarchive an array of 'Movie' structure. All you need to take care is the implementation of the 'Dictionariable' protocol to each of your structures that needs to be archived.

Check out this blogpost I wrote comparing three ways to archive and unarchive swift structures. There is a more detailed implementation and a playground file of the above explained code which you can run and test in the link.