web-dev-qa-db-ja.com

クラスのオブジェクトをデコードできません


ウォッチキット拡張機能に「クラス」を送信しようとしていますが、このエラーが発生します。

*キャッチされない例外 'NSInvalidUnarchiveOperationException'によりアプリを終了しています。理由: '*-[NSKeyedUnarchiver decodeObjectForKey:]:クラス(MyApp.Person)のオブジェクトをデコードできません

アーカイブとアーカイブ解除はiOSアプリでは正常に機能しますが、ウォッチキット拡張機能との通信中は機能しません。どうしましたか?

InterfaceController.Swift

    let userInfo = ["method":"getData"]

    WKInterfaceController.openParentApplication(userInfo,
        reply: { (userInfo:[NSObject : AnyObject]!, error: NSError!) -> Void in

            println(userInfo["data"]) // prints <62706c69 7374303...

            if let data = userInfo["data"] as? NSData {
                if let person = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Person {
                    println(person.name)
                }
            }

    })

AppDelegate.Swift

func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
    reply: (([NSObject : AnyObject]!) -> Void)!) {

        var bob = Person()
        bob.name = "Bob"
        bob.age = 25

        reply(["data" : NSKeyedArchiver.archivedDataWithRootObject(bob)])
        return
}

Person.Swift

class Person : NSObject, NSCoding {
    var name: String!
    var age: Int!

    // MARK: NSCoding

    required convenience init(coder decoder: NSCoder) {
        self.init()
        self.name = decoder.decodeObjectForKey("name") as! String?
        self.age = decoder.decodeIntegerForKey("age")
    }

    func encodeWithCoder(coder: NSCoder) {
        coder.encodeObject(self.name, forKey: "name")
        coder.encodeInt(Int32(self.age), forKey: "age")
    }
}
27
fabian

注:この回答の情報は正しいですが、wayより良い回答は、@ agyによる以下の回答です。

これは、同じクラスからコンパイラがMyApp.PersonMyAppWatchKitExtension.Personを作成することが原因です。これは通常、2つのターゲット間で同じクラスを共有することによって発生します代わりにそれを共有するフレームワークを作成する。

2つの修正:

適切な修正は、Personをフレームワークに抽出することです。メインアプリとウォッチキット拡張機能の両方でフレームワークを使用する必要があり、同じ*.Personクラスを使用します。

回避策は、保存して渡す前に、クラスをFoundationオブジェクト(NSDictionaryなど)にシリアル化することです。 NSDictionaryはコードであり、アプリと拡張機能の両方でデコード可能です。これを行う良い方法は、代わりにRawRepresentablePersonプロトコルを実装することです。

15
Jack

Objective-C APIとの相互作用 によると:

_@objc(_name_)_属性をSwiftクラスで使用すると、クラスは名前空間なしでObjective-Cで使用できるようになります。その結果、この属性は、アーカイブ可能なObjective-CクラスをSwiftに移行するときにも役立ちます。アーカイブされたオブジェクトは、クラスの名前をアーカイブに保存するため、 _@objc(_name_)_属性を使用してObjective-Cクラスと同じ名前を指定し、古いアーカイブをアーカイブ解除できるようにする新しいSwiftクラス。

注釈@objc(name)を追加することで、Swiftを使用しているだけでも、名前空間は無視されます。実演してみましょう。ターゲットAが3つのクラスを定義すると想像してください。

_@objc(Adam)
class Adam:NSObject {
}

@objc class Bob:NSObject {
}

class Carol:NSObject {
}
_

ターゲットBがこれらのクラスを呼び出す場合:

_print("\(Adam().classForCoder)")
print("\(Bob().classForCoder)")
print("\(Carol().classForCoder)")
_

出力は次のようになります。

_Adam
B.Bob
B.Carol
_

ただし、ターゲットAがこれらのクラスを呼び出すと、結果は次のようになります。

_Adam
A.Bob
A.Carol
_

問題を解決するには、@ objc(name)ディレクティブを追加します。

_@objc(Person)
class Person : NSObject, NSCoding {
    var name: String!
    var age: Int!

    // MARK: NSCoding

    required convenience init(coder decoder: NSCoder) {
        self.init()
        self.name = decoder.decodeObjectForKey("name") as! String?
        self.age = decoder.decodeIntegerForKey("age")
    }

    func encodeWithCoder(coder: NSCoder) {
        coder.encodeObject(self.name, forKey: "name")
        coder.encodeInt(Int32(self.age), forKey: "age")
    }
}
_
55
agy

NSKeyedUnarchiverが適切に機能するようにフレームワークを設定した後、次の行を追加する必要がありました。

アーカイブを解除する前に:

NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")

アーカイブする前に:

NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
28
Xishan

同様の状況で、アプリがCoreフレームワークを使用し、すべてのモデルクラスを保持しました。例えば。 UserProfileNSKeyedArchiverを使用してNSKeyedUnarchiverオブジェクトを格納および取得しましたが、すべてのクラスをMyAppNSKeyedUnarchiverに移動すると、格納されたオブジェクトがCore.UserProfileではなくMyApp.UserProfileであったため、エラーがスローされ始めました。私がそれを解決した方法は、NSKeyedUnarchiverのサブクラスを作成し、classforClassName関数をオーバーライドすることでした:

class SKKeyedUnarchiver: NSKeyedUnarchiver {
    override open func `class`(forClassName codedName: String) -> Swift.AnyClass? {
        let lagacyModuleString = "Core."
        if let range = codedName.range(of: lagacyModuleString), range.lowerBound.encodedOffset == 0  {
            return NSClassFromString(codedName.replacingOccurrences(of: lagacyModuleString, with: ""))
        }
        return NSClassFromString(codedName)
    }
}

次に、ここでの回答の1つで提案されているように、アーカイブする必要があるクラスに@objc(name)を追加しました。

次のように呼び出します。

if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
    currentUserProfile = unarchivedObject
}

それはとてもうまくいきました。

UserProfilevar address: Addressが含まれている場合など、ネストされたオブジェクトに対しては機能しないため、ソリューションNSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")が私には適していなかった理由。アンアーカイバーは、UserProfileで成功しますが、Addressのレベルに達すると失敗します。

そして、@objc(name)ソリューションだけではうまくいかなかった理由は、OBJ-CからSwiftに移行しなかったためで、問題はUserProfile-> MyApp.UserProfileではなくCore.UserProfile-> MyApp.UserProfile

9
Au Ris