NSInvalidUnarchiveOperationException 仅在 iOS 扩展中引发,而不是主应用程序

Posted

技术标签:

【中文标题】NSInvalidUnarchiveOperationException 仅在 iOS 扩展中引发,而不是主应用程序【英文标题】:NSInvalidUnarchiveOperationException raised only in iOS extension, not main app 【发布时间】:2015-03-23 00:06:36 【问题描述】:

我已将符合 NSCoding 的类“分配”的数组归档到共享(应用程序组)容器中的文件中:

class private func updateSharedFiles() 
    let path = NSFileManager().containerURLForSecurityApplicationGroupIdentifier("group.agenda-touch")!.path! + "/widgetData"
    if let incompleteAssignmentsArray = self.incompleteAssignments 
        NSKeyedArchiver.archiveRootObject(incompleteAssignmentsArray, toFile: path)
    

现在,当我的扩展程序想要从该文件中读取时,我在该文件上调用了 NSFileManager.fileExistsAtPath: 并且它返回 true。最后我打电话给NSKeyedUnarchiver.unarchiveObjectWithFile(path) as? AssignmentArray 并得到一个 NSInvalidUnarchiveOperationException。问题是,当我在我的主应用程序中解压缩文件时,我没有遇到这样的异常。需要明确的是,我已将 Assignment.swift 添加到小部件的编译源中。所以我的 TodayViewController 知道 Assignment 是什么,但由于某种原因无法对其进行解码。 作为附录,这里是 Assignment 的 NSCoding 实现:

//MARK: NSCoding
    func encodeWithCoder(aCoder: NSCoder) 
        aCoder.encodeObject(self.assignmentName, forKey: "assignmentName")
        aCoder.encodeObject(self.assignmentDueDate, forKey: "assignmentDueDate")
        aCoder.encodeObject(self.assignmentSubject, forKey: "assignmentSubject")
        aCoder.encodeBool(self.completed, forKey: "assignmentCompleted")
        aCoder.encodeObject(self.creationDate, forKey: "assignmentCreationDate")
        aCoder.encodeInteger(self.assignmentType.rawValue, forKey: "assignmentType")
        aCoder.encodeBool(self.earmarkedForDeletion, forKey: "assignmentEarmarked")
        aCoder.encodeObject(self.modificationDates, forKey: "assignmentModificationDates")
    
    required convenience init(coder aDecoder: NSCoder) 
        let assignmentName = aDecoder.decodeObjectForKey("assignmentName") as String
        let assignmentDueDate = aDecoder.decodeObjectForKey("assignmentDueDate") as NSDate
        let assignmentSubject = aDecoder.decodeObjectForKey("assignmentSubject") as String
        let completed = aDecoder.decodeBoolForKey("assignmentCompleted")
        self.init(name:assignmentName,dueDate:assignmentDueDate,subject:assignmentSubject,completed:completed)
        self.creationDate = aDecoder.decodeObjectForKey("assignmentCreationDate") as NSDate
        let assignmentTypeRaw =  aDecoder.decodeIntegerForKey("assignmentType")
        self.assignmentType = AssignmentType(rawValue: assignmentTypeRaw)!
        self.earmarkedForDeletion = aDecoder.decodeBoolForKey("assignmentEarmarked")
        self.modificationDates = aDecoder.decodeObjectForKey("assignmentModificationDates") as Dictionary<String,NSDate>
    

【问题讨论】:

【参考方案1】:

我也有同样的问题。我试图从编译源中删除和添加文件。也不工作。你找到解决方案了吗?

与我的问题的唯一区别:我尝试取消归档对象 xxx 的 NSArray。当在主应用程序中存档和取消存档或在 WatchKit 应用程序中存档和取消存档时,它可以工作,但当我在主应用程序中存档并在 WatchKit 应用程序中取消存档时(或其他)则不起作用。

let defaults: NSUserDefaults = NSUserDefaults(suiteName: "group.name")!

if let object: NSData = defaults.objectForKey(ArrayKey) as? NSData 
    if let array: AnyObject = NSKeyedUnarchiver.unarchiveObjectWithData(object) 
        return array as NSArray
    


return NSArray()

通过我的应用程序组中的 NSUserDefaults 交换对象有效。我只遇到带有对象 xxx 的 NSArray 的 NSKeyedArchiver 和 NSKeyedUnarchiver 的问题。

* 由于未捕获的异常 'NSInvalidUnarchiveOperationException' 导致应用程序终止,原因:'* -[NSKeyedUnarchiver decodeObjectForKey:]:无法解码类 xxx 的对象


编辑:我解决了使我的对象符合 NSSecureCoding 的问题。因此我更换了

propertyName = aDecoder.decodeObjectForKey("propertyName")

propertyName = aDecoder.decodeObjectOfClass(NSString.self, forKey: "propertyName") as NSString

并添加了

class func supportsSecureCoding() -> Bool 
    return true

我还在主应用的 applicationFinishLaunchingWithOptions 和 WatchKitApp 的第一个 WKInterfaceController 中设置了 NSKeyedUnarchiver 和 NSKeyedArchiver 的 className

    NSKeyedArchiver.setClassName("Song", forClass: Song.self)
    NSKeyedUnarchiver.setClass(Song.self, forClassName: "Song")

【讨论】:

等等,我不认为这是一个答案。 对不起,由于声誉低,我无法对原始问题发表评论。 :) 我编辑了帖子,所以它可能包含原始问题的答案。 只需致电NSKeyedArchiver.setClassnameNSKeyedUnarchiver.setClass。采用NSSecureCoding 应该没有任何区别。根本问题是存储的类名包括模块,并且您的扩展程序和主应用程序具有不同的模块名。【参考方案2】:

我也遇到了同样的问题。 从之前的答案和 cmets 总结,解决方案在于 setClass/setClassName 语句。我在下面的示例代码(Swift 5)中将我的编码包装为函数:

import Foundation


public class User: NSObject, NSSecureCoding

    public var id: String
    public var name: String

    public init(id: String, name: String)
    
        self.id = id
        self.name = name
    

    // NSSecureCoding

    public static var supportsSecureCoding: Bool  return true 

    public func encode(with coder: NSCoder)
    
        coder.encode(id  , forKey: "id")
        coder.encode(name, forKey: "name")
    

    public required init?(coder: NSCoder)
    
        guard
            let id   = coder.decodeObject(forKey: "id") as? String,
            let name = coder.decodeObject(forKey: "name") as? String
        else 
            return nil
        

        self.id = id
        self.name = name
    

    // (Un)archiving

    public static func decode(from data: Data) -> Self?
    
        NSKeyedUnarchiver.setClass(Self.self, forClassName: String(describing: Self.self))

        return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? Self
    

    public func encode() -> Data?
    
        NSKeyedArchiver.setClassName(String(describing: Self.self), for: Self.self)

        return try? NSKeyedArchiver.archivedData(withRootObject: self, requiringSecureCoding: Self.supportsSecureCoding)
    

【讨论】:

以上是关于NSInvalidUnarchiveOperationException 仅在 iOS 扩展中引发,而不是主应用程序的主要内容,如果未能解决你的问题,请参考以下文章