web-dev-qa-db-ja.com

SwiftのNSCodingでオプションの構造体の配列をアーカイブしますか?

Obj-Cで多くのNSCodingアーカイブを実行しましたが、Swiftで構造体を処理する方法や、オプションの値を持つ配列を処理する方法がわかりません。これが私のコードです:

public struct SquareCoords {
    var x: Int, y: Int
}

これが私が保存する必要のあるクラスです:

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

コーダーinitの最後の行で、XCodeに次のエラーメッセージが表示されます。

'AnyObject' is not convertible to [SquareCoords?]'

そしてencodeWithEncoderの最後の行:

Extra argument 'forKey' in call

誰かが私を正しい方向に動かすことができますか?

19
Chuck Smith

正確な問題が何であるかはわかりませんが、Swift配列ではなくNSMutableArrayを使用すると、問題は解決します。

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

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

AnyObjectからのキャストが何らかの理由で構造体配列に失敗する可能性があります。 -他の誰かがより多くの洞察を提供できると確信しています。これがある程度役立つことを願っています! Swift激しいことがあります:D

10
Woodstock

Swiftプログラミング言語 、Appleの状態:

Swiftは、非特定の型を操作するための2つの特別な型エイリアスを提供します。
-AnyObjectは、任意のクラスタイプのインスタンスを表すことができます。
-Anyは、関数型を含む、あらゆる型のインスタンスを表すことができます。

それを知っていると、タイプSquareCoords(Swift Structure)とタイプ[SquareCoords](Swift St​​ructureのSwift配列)はプロトコルAnyObjectに準拠できません。

一方、decodeObjectForKey:にはプロトコルAnyObjectに準拠するパラメーターが必要であり、encodeObject:forKey:AnyObjectを返します。したがって、次の2行はコンパイルできません。

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

したがって、SquareCoordsをプロトコルAnyObjectに準拠させる方法を見つけない限り(それが可能かどうかはわかりません)、SquareCoordsをSwift構造からクラスに変換する必要があります。

PS:この時点で、次のように尋ねることができます。「OK、しかし、タイプString-つまり実際にはSwift St​​ruct-プロトコルAnyObjectに準拠できますか? "これは、StringがFoundationのNSStringクラスにシームレスにブリッジされているためです(ArrayDictionaryNSArrayおよびNSDictionaryに同じ方法でブリッジされています)。読んでください このブログ投稿 もっとよく見たい場合は。

27
Imanou Petit

ここで与えられた他の答えはあなたの問題を解決します。しかし、最近、Swift構造をアーカイブしようとしたときに同様の問題に直面し、これを解決するための興味深い方法を考え出しました。NSCodingは構造をサポートしていません。

基本的に、次の方法では、構造プロパティを辞書要素に変換します。しかし、それはプロトコルを使用してエレガントにそれを行います。あなたがする必要があるのは、あなたの構造を辞書化することと辞書化しないことを助ける2つの方法を実装するプロトコルを定義することです。プロトコルとジェネリックを使用する利点は、構造が別の構造のプロパティである場合に機能することです。この入れ子は、任意の深さにすることができます。

私はプロトコルを「Dictionariable」と呼んでいます。これは、プロトコルに準拠するものはすべて辞書に変換できることを示しています。定義は以下のとおりです。

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

ここで、構造「映画」について考えてみましょう。

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

構造を拡張して、「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
        }
    }
}

基本的に、構造を辞書に安全に変換する方法があります。辞書がどのように形成されるかをすべての構造に個別に実装するので、私は安全だと言います。その実装が正しい限り、機能は機能します。辞書から構造を取り戻す方法は、失敗可能なイニシャライザーを使用することです。ファイルの破損やその他の理由により、アーカイブからの構造のインスタンス化が不完全になる可能性があるため、失敗する必要があります。これは決して起こらないかもしれませんが、失敗する方が安全です。

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!
}

上記の2つのメソッドは、「Movie」構造の配列をアーカイブおよびアーカイブ解除できます。注意する必要があるのは、アーカイブする必要のある各構造に「Dictionariable」プロトコルを実装することだけです。

チェック このブログ投稿から 構造をアーカイブおよびアーカイブ解除する3つの方法を比較して書いたSwift構造。より詳細な実装と、上記で説明したコードのプレイグラウンドファイルがあります。リンクで実行してテストします。

8

NSCodingは上記のような構造体をサポートしていないことに加えて、クラス自体とそのプロパティをエンコードおよびデコードできるようにするには、クラスもNSObjectから継承する必要があることをお勧めします。

0
JohnnyHockey