I've done a lot of NSCoding archiving in Obj-C, but I'm not sure how it handles structs in Swift, nor arrays with optional values. Here is my code:
public struct SquareCoords {
var x: Int, y: Int
}
and here is the class which I need to store:
public class Player: NSCoding {
var playerNum: Int
var name = ""
private var moveHistory: [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")
}
...
On the last line of the coder init, I get the following error message in XCode:
'AnyObject' is not convertible to [SquareCoords?]'
and on the last line of encodeWithEncoder:
Extra argument 'forKey' in call
Can anyone get me moving in the right direction?
In The Swift Programming Language, Apple states:
Knowing that, type
SquareCoords
(Swift Structure) and type[SquareCoords]
(Swift array of Swift Structure) can't conform to protocolAnyObject
.On the other hand,
decodeObjectForKey:
requires a parameter that conforms to protocolAnyObject
, andencodeObject:forKey:
returnsAnyObject
. Thus, the two following lines can't compile:Therefore, unless you find a way to make
SquareCoords
conform to protocolAnyObject
(I don't know if it's possible), you will have to transformSquareCoords
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 protocolAnyObject
?" Well, that's becauseString
is bridged seamlessly to Foundation’sNSString
class (Array
,Dictionary
are bridged toNSArray
andNSDictionary
the same way). Read this blog post if you want to have a better look at it.Besides that NSCoding doesn't support structs as mentioned above, my suggestion is your class should also inherit from NSObject in order to be able to encode and decode itself and its properties.
I'm not sure what the exact problem is, but if you use NSMutableArray rather than a Swift array the problem resolves:
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.
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.
Now, consider a structure 'Movie'
Let me extend the structure and make it conform to 'Dictionariable' protocol.
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.
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.