在Swift中使用NSCoding存档可选结构的数组?

Chu*_*ith 19 arrays struct nscoding optional swift

我在Obj-C中做了很多NSCoding归档,但我不确定它如何处理Swift中的结构,也不确定具有可选值的数组.这是我的代码:

public struct SquareCoords {
    var x: Int, y: Int
}
Run Code Online (Sandbox Code Playgroud)

这是我需要存储的类:

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")
    }
...
Run Code Online (Sandbox Code Playgroud)

在编码器init的最后一行,我在XCode中收到以下错误消息:

'AnyObject' is not convertible to [SquareCoords?]'
Run Code Online (Sandbox Code Playgroud)

并在encodeWithEncoder的最后一行:

Extra argument 'forKey' in call
Run Code Online (Sandbox Code Playgroud)

谁能让我朝着正确的方向前进?

Ima*_*tit 27

Swift编程语言中,Apple指出:

Swift提供了两种特殊类型的别名来处理非特定类型:
- AnyObject可以表示任何类类型的实例.
- Any可以表示任何类型的实例,包括函数类型.

知道,类型SquareCoords(Swift结构)和类型[SquareCoords](Swift结构的Swift数组)不能符合协议AnyObject.

另一方面,decodeObjectForKey:需要一个符合协议的参数AnyObject,并encodeObject:forKey:返回AnyObject.因此,以下两行无法编译:

moveHistory = aDecoder.decodeObjectForKey("moveHistoryKey") as [SquareCoords?]
aCoder.encodeObject(moveHistory, forKey: "moveHistoryKey")
Run Code Online (Sandbox Code Playgroud)

因此,除非您找到SquareCoords符合协议的方法AnyObject(我不知道是否可能),否则您必须SquareCoords从Swift Structure 转换为Class.

PS: 此时,您可能会问:"好的,但是这种类型String- 实际上是Swift Struct - 可以符合协议AnyObject吗?" 嗯,这是因为String无缝桥接至基金会的NSString类(Array,Dictionary彼此桥接NSArrayNSDictionary以同样的方式).如果您想更好地了解它,请阅读此博客文章.


Woo*_*ock 10

我不确定究竟是什么问题,但如果你使用NSMutableArray而不是Swift数组,问题就解决了:

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

似乎是当aDecoder.decodeObjectForKey返回一个隐式解包的AnyObject时,它不会转换为SquareCoords数组.

一直在玩这个,我注意到它可能与结构的使用有关.(你正在创建一个值类型的结构数组.)这是一个猜测,但我注意到如果一个类类型用于SquareCoords没有问题,例如

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

由于某种原因,AnyObject的转换可能会失败到结构数组. - 我相信其他人可以提供更多的见解,希望这有点帮助!斯威夫特可以是暴风雨的:D

  • 转换失败的原因是AnyObject只能是类的实例而不是struct/enum/tuple. (4认同)
  • @Lance谢谢! - 所以,如果我理解正确,decodeObjectForKey返回一个AnyObject,我们不能将它转换为Swift结构数组? - 你会建议处理这个问题的最佳方法是什么? - 使用NSMutableArray?或使用类而不是结构? (2认同)
  • @John谢谢......这让我走上正确的解决之道!我现在已经确定非移动(玩家没有时间进行该移动)将只是在(-1,-1),因为那是在棋盘上,而不是使用一组选项.另外,我将SquareCoords改为上面推荐的类.它仍然感觉像是一个黑客,但我认为这是最干净的,我将能够按照NSCoding的工作方式制作我的代码. (2认同)
  • @ChuckSmith看着这个问题:http://stackoverflow.com/questions/336367/encoding-c-structs - 看起来值类型不能很好地与NSCoding一起使用 - 我知道Swift.String是一个值类型但它是免费桥接到它的NSString等价物,Swift.Int桥接到NSNumber. (2认同)

Vis*_*kar 8

这里给出的其他答案解决了您的问题.但是,我最近在尝试归档我的Swift结构时遇到了类似的问题,并提出了一种有趣的方法来解决这个问题.NSCoding不支持结构.

实质上,以下方法涉及将结构属性转换为字典元素.但是,它优雅地使用协议.您需要做的就是定义一个协议,该协议实现了两种方法,这些方法有助于Dictionary-fying和un-Dictionary-fying您的结构.使用协议和泛型的优点是,当结构是另一个结构的属性时,它可以工作.这种嵌套可以达到任何深度.

我将协议称为"Dictionariable",表示符合协议的任何内容都可以转换为字典.定义如下.

protocol Dictionariable {
    func dictionaryRepresentation() -> NSDictionary
    init?(dictionaryRepresentation: NSDictionary?)
}
Run Code Online (Sandbox Code Playgroud)

现在,考虑一个结构'电影'

struct Movie {
    let name: String
    let director: String
    let releaseYear: Int
}
Run Code Online (Sandbox Code Playgroud)

让我扩展结构并使其符合'Dictionariable'协议.

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上,我们现在有一种方法可以安全地将结构转换为字典.我说安全是因为我们实现了字典的形成方式,每个结构都是如此.只要该实现是正确的,功能就可以工作.从字典中获取结构的方法是使用可用的初始化器.它必须是可用的,因为文件损坏和其他原因可能使结构的存档实例化不完整.这种情况可能永远不会发生,但它更安全,可以使用.

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 to
func path() -> String {
    let documentsPath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).first
    let path = documentsPath?.stringByAppendingString("/Movie")
    return path!
}
Run Code Online (Sandbox Code Playgroud)

上述两种方法可以归档和取消归档"Movie"结构数组.您需要注意的是为每个需要归档的结构实施"Dictionariable"协议.

看看我写的这篇博文,比较了三种存档和取消存档快速结构的方法.有一个更详细的实现和上面解释的代码的操场文件,您可以在链接中运行和测试.