web-dev-qa-db-ja.com

Swift NSKeyedUnarchiver.decodeObjectクラッシュを防ぐ唯一の方法?

元のクラスが不明な場合、_NSKeyedUnarchiver.decodeObject_はクラッシュを引き起こします/ SIGABRT。私がこの問題を捕らえた唯一の解決策は、Swiftの初期の歴史からのもので、Objective Cを使用する必要がありました(以前の日付Swift 2のguardthrowstrycatch)。Objective Cルートを把握できましたが、可能であれば、Swiftのみのソリューションを理解したいと思います。

たとえば、データは_NSPropertyListFormat.XMLFormat_v1_0_でエンコードされています。エンコードされたデータのクラスが不明な場合、次のコードはunarchiver.decodeObject()で失敗します。

_//...
let dat = NSData(contentsOfURL: url)!
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)

//it will crash after this if the class in the xml file is not known

if let newListCollection = (unarchiver.decodeObject()) as? List {
    return newListCollection
} else {
    return nil
}
//...
_

私はSwift 2のみを探しています。データが有効かどうかをテストする前に_.decodeObject_-_.decodeObject_にはthrowsがないため- try-catchは、Swift(throwsのないメソッドはAFAIKでラップすることはできません。)またはオプションのデコード方法ではありません。エラーをスローするデータデコードが失敗した場合にキャッチできます。ユーザーにファイルをiCloudドライブまたはDropboxからインポートできるようにしたいため、適切に検証する必要があります。エンコードされたデータが安全であるとは思いません。

NSKeyedUnarchiverメソッド_.unarchiveTopLevelObjectWithData_および_.validateValue_の両方にthrowsがあります。これらを使用する方法はおそらくありますか?このコンテキストでvalidateValueを実装しようとする方法さえ理解できません。これは可能なルートですか?または、解決策のための他の方法の1つを探しているべきですか?

あるいは、誰かが代替案を知っていますかSwift 2この問題に対処する2つの唯一の方法?私が興味を持っているキーはおそらく_$classname_と題されていると思いますが、TBH validateValueの実装方法を解決しようとすることに関して、またはそれが忍耐するための正しいルートであるかどうかについて、私は深みがありません。


編集:ここに解決策があります-rintaroの素晴らしい回答のおかげで

最初の答えは私のために問題を解決しました-つまり、デリゲートを実装します。

しかし今のところ、次のように、rintaroの追加の編集された応答を中心に構築されたソリューションを使用しました。

_//...
let dat = NSData(contentsOfURL: url)!
let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)

do {
    let decodedDataObject = try unarchiver.decodeTopLevelObject()
    if let newListCollection = decodedDataObject as? List {
        return newListCollection
    } else {
        return nil
    }
}
catch {
    return nil
}
//...
_
34
simons

NSKeyedUnarchiverが不明なクラスに遭遇すると、 unarchiver(_:cannotDecodeObjectOfClassName:originalClasses:) デリゲートメソッドが呼び出されます。

デリゲートは、たとえば、クラスをランタイムに導入してクラスを返すためのコードをロードしたり、別のクラスオブジェクトを置き換えたりする場合があります。デリゲートがnilを返す場合、アーカイブ解除は中止され、メソッドはNSInvalidUnarchiveOperationExceptionを発生させます。

したがって、次のようにデリゲートを実装できます。

class MyUnArchiverDelegate: NSObject, NSKeyedUnarchiverDelegate {

    // This class is placeholder for unknown classes.
    // It will eventually be `nil` when decoded.
    final class Unknown: NSObject, NSCoding  {
        init?(coder aDecoder: NSCoder) { super.init(); return nil }
        func encodeWithCoder(aCoder: NSCoder) {}
    }

    func unarchiver(unarchiver: NSKeyedUnarchiver, cannotDecodeObjectOfClassName name: String, originalClasses classNames: [String]) -> AnyClass? {
        return Unknown.self
    }
}

次に:

let unarchiver = NSKeyedUnarchiver(forReadingWithData: dat)
let delegate = MyUnArchiverDelegate()
unarchiver.delegate = delegate

unarchiver.decodeObjectForKey("root")
// -> `nil` if the root object is unknown class.

[〜#〜] added [〜#〜]

NSCoderにはextensionがあり、より迅速なメソッドがあることに気づきませんでした:

extension NSCoder {
    @warn_unused_result
    public func decodeObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) -> DecodedObjectType?
    @warn_unused_result
    @nonobjc public func decodeObjectOfClasses(classes: NSSet?, forKey key: String) -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObject() throws -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObjectForKey(key: String) throws -> AnyObject?
    @warn_unused_result
    public func decodeTopLevelObjectOfClass<DecodedObjectType : NSCoding where DecodedObjectType : NSObject>(cls: DecodedObjectType.Type, forKey key: String) throws -> DecodedObjectType?
    @warn_unused_result
    public func decodeTopLevelObjectOfClasses(classes: NSSet?, forKey key: String) throws -> AnyObject?
}

あなたはできる:

do {
    try unarchiver.decodeTopLevelObjectForKey("root")
    // OR `unarchiver.decodeTopLevelObject()` depends on how you archived.
}
catch let (err) {
    print(err)
}
// -> emits something like:
// Error Domain=NSCocoaErrorDomain Code=4864 "*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked" UserInfo={NSDebugDescription=*** -[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyProject.MyClass) for key (root); the class may be defined in source code or a library that is not linked}
27
rintaro

別の方法は、NSCodingに使用されるクラスの名前を修正することです。あなたは単に使用する必要があります:

  • _NSKeyedArchiver.setClassName("List", forClass: List.self_シリアライズする前
  • NSKeyedUnarchiver.setClass(List.self, forClassName: "List")直列化復元する前

必要な場所。

IOSの拡張機能は、クラス名の前に拡張機能の名前が付いているように見えます。

18
teriiehina

実際、それは私たちが深く掘り下げるべき理由です。可能性があります。xxx.archiveという名前のアーカイブパスを作成してから、パス(xxx.archive)からアーカイブ解除すると、すべてが正常になりました。ただし、ターゲット名を変更すると、アーカイブ解除時にクラッシュが発生しました!!!これは、異なるオブジェクトをアーカイブおよびアーカイブ解除するためです(実際には、objだけでなくtarget.objをアーカイブおよびアーカイブ解除します)。簡単な方法は、アーカイブパスを削除するか、別のアーカイブパスを使用することです。そして、クラッシュをどのように回避するかを検討する必要があります。try-catchは rintaro で言及されているヘルパーです。

1
responser

私は同じ問題を抱えていました。クラス宣言に@objcを追加するとうまくいきました。

@objc(YourClass)
class YourClassName: NSObject {
}
0
Van