ウォッチキット拡張機能に「クラス」を送信しようとしていますが、このエラーが発生します。
*キャッチされない例外 '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")
}
}
注:この回答の情報は正しいですが、wayより良い回答は、@ agyによる以下の回答です。
これは、同じクラスからコンパイラがMyApp.Person
&MyAppWatchKitExtension.Person
を作成することが原因です。これは通常、2つのターゲット間で同じクラスを共有することによって発生します代わりにそれを共有するフレームワークを作成する。
2つの修正:
適切な修正は、Person
をフレームワークに抽出することです。メインアプリとウォッチキット拡張機能の両方でフレームワークを使用する必要があり、同じ*.Person
クラスを使用します。
回避策は、保存して渡す前に、クラスをFoundationオブジェクト(NSDictionary
など)にシリアル化することです。 NSDictionary
はコードであり、アプリと拡張機能の両方でデコード可能です。これを行う良い方法は、代わりにRawRepresentable
にPerson
プロトコルを実装することです。
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")
}
}
_
NSKeyedUnarchiver
が適切に機能するようにフレームワークを設定した後、次の行を追加する必要がありました。
アーカイブを解除する前に:
NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
アーカイブする前に:
NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
同様の状況で、アプリがCore
フレームワークを使用し、すべてのモデルクラスを保持しました。例えば。 UserProfile
とNSKeyedArchiver
を使用してNSKeyedUnarchiver
オブジェクトを格納および取得しましたが、すべてのクラスをMyApp
NSKeyedUnarchiver
に移動すると、格納されたオブジェクトが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
}
それはとてもうまくいきました。
UserProfile
にvar address: Address
が含まれている場合など、ネストされたオブジェクトに対しては機能しないため、ソリューションNSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
が私には適していなかった理由。アンアーカイバーは、UserProfile
で成功しますが、Address
のレベルに達すると失敗します。
そして、@objc(name)
ソリューションだけではうまくいかなかった理由は、OBJ-CからSwiftに移行しなかったためで、問題はUserProfile
-> MyApp.UserProfile
ではなくCore.UserProfile
-> MyApp.UserProfile
。