使用NSKeyedUnarchiver的unarchivedObject(ofClass:from:)方法解压缩数组

31

自从升级到Swift 4.2以来,我发现许多NSKeyedUnarchiver和NSKeyedArchiver方法已经被弃用,我们现在必须使用类型方法static func unarchivedObject<DecodedObjectType>(ofClass: DecodedObjectType.Type, from: Data) -> DecodedObjectType?来解档数据。

我成功地对我定制的NSObject子类WidgetData的一个数组进行了归档:

private static func archiveWidgetDataArray(widgetDataArray : [WidgetData]) -> NSData {

    guard let data = try? NSKeyedArchiver.archivedData(withRootObject: widgetDataArray as Array, requiringSecureCoding: false) as NSData
        else { fatalError("Can't encode data") }

    return data

}

当我尝试对这些数据进行解档时,问题出现了:
static func loadWidgetDataArray() -> [WidgetData]? {

    if isKeyPresentInUserDefaults(key: USER_DEFAULTS_KEY_WIDGET_DATA) {

        if let unarchivedObject = UserDefaults.standard.object(forKey: USER_DEFAULTS_KEY_WIDGET_DATA) as? Data {

            //THIS FUNCTION HAS NOW BEEN DEPRECATED:
            //return NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [WidgetData]

            guard let nsArray = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSArray.self, from: unarchivedObject as Data) else {
                fatalError("loadWidgetDataArray - Can't encode data")
            }

            guard let array = nsArray as? Array<WidgetData> else {
                fatalError("loadWidgetDataArray - Can't get Array")
            }

            return array

        }

    }

    return nil

}

但是这种方法失败了,因为使用Array.self而不是NSArray.self是被禁止的。我做错了什么?如何修复以解档我的数组?


请查看此链接以获取更快捷的解决方案:https://stackoverflow.com/a/51460950/5820010 - Shehata Gamal
"失败"是什么?你得到了什么输出?你没有在任何地方使用Array.self - Paulw11
当将NSArray.self更改为Array.self时,预编译器会抱怨:调用中的参数标签不正确(应该是“ofClasses:from:”,而不是“ofClass:from:”)。请将“ofClass”替换为“ofClasses”。这意味着不能使用Array.self。当我使用NSArray.self时,它可以编译并运行,但由于结果的“nsArray”为空,因此会被致命错误捕获。 - Geoff H
1
那么,当你使用 NSKeyedUnarchiver.unarchivedObject(ofClasses: [Array<WidgetData>.self], from: unarchivedObject as Data) 时会发生什么?另外,不要使用 try?,而应该使用 do/try/catch,这样你就可以打印出实际发生的错误。 - Paulw11
它导致以下投诉: 无法将类型为“Array<WidgetData>.Type”的值转换为预期的元素类型“AnyObject.Type”插入“as!AnyObject.Type”。 - Geoff H
2
请注意,以下是程序相关内容,请勿将其解释为其他内容:guard let unarchivedFavorites = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(favoritesData!) else { return } self.channelFavorites = unarchivedFavorites as! [ChannelFavorite] - user1828845
7个回答

47
您可以使用unarchiveTopLevelObjectWithData(_:)来解档由archivedData(withRootObject:requiringSecureCoding:)归档的数据(我相信这个方法目前还没有被弃用)。
但在展示一些代码之前,最好遵循以下几点:
  • 避免使用NSData,请使用Data

  • 避免使用try?,因为它会丢弃有用于调试的错误信息

  • 删除所有不必要的转换


尝试这样做:

private static func archiveWidgetDataArray(widgetDataArray : [WidgetData]) -> Data {
    do {
        let data = try NSKeyedArchiver.archivedData(withRootObject: widgetDataArray, requiringSecureCoding: false)

        return data
    } catch {
        fatalError("Can't encode data: \(error)")
    }

}

static func loadWidgetDataArray() -> [WidgetData]? {
    guard
        isKeyPresentInUserDefaults(key: USER_DEFAULTS_KEY_WIDGET_DATA), //<- Do you really need this line?
        let unarchivedObject = UserDefaults.standard.data(forKey: USER_DEFAULTS_KEY_WIDGET_DATA)
    else {
        return nil
    }
    do {
        guard let array = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(unarchivedObject) as? [WidgetData] else {
            fatalError("loadWidgetDataArray - Can't get Array")
        }
        return array
    } catch {
        fatalError("loadWidgetDataArray - Can't encode data: \(error)")
    }
}

但如果你正在开发一个新的应用程序,最好考虑使用Codable


一针见血。非常感谢。是的,我正在开发一个新应用程序。如果您能对Codable进行一些解释,那就太好了。您引起了我的注意。我应该从哪里开始学习它,以及它为什么很重要。 - Geoff H
2
@GeoffH,这是使用本地Swift的序列化(归档)方式。(例如,它可以与[WidgetData].self一起使用,您无需关心NSArray或其他NS相关内容。)最好从搜索“swift codable”开始,您可以找到许多好的文章。 - OOPer
好的。非常感谢您的帮助。 - Geoff H
完美的答案。非常感谢你。 - Umair_UAS
我们该如何处理让字典 = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Element] 这段代码已经被弃用了,而且 typealias Element = [String: Any]。 - Sanoj Kashyap
我们该如何处理让字典 = NSKeyedUnarchiver.unarchiveObject(with: data) as? [String: Element] 这段代码已经被弃用了,而且 typealias Element = [String: Any]。 - undefined

26
unarchiveTopLevelObjectWithData(_:) 

已经过时了。因此,如果未进行安全编码,则需要解压缩数据:

  1. 使用 init(forReadingFrom: Data) 创建NSKeyedUnarchiver
  2. 将新建的反归档器的 requiresSecureCoding 设置为false。
  3. 调用 decodeObject(of: [AnyClass]?, forKey: String) -> Any? 来获取您的对象,只需使用适当的类和NSKeyedArchiveRootObjectKey作为键即可。

对于使用NSKeyedArchiver(无论是否需要SecureCoding)存档数据的任何人,这就是答案。谢谢! - smakus

15

由于iOS14.3后unarchiveTopLevelObjectWithData也被弃用了,因此现在只有Hopreeeenjust的答案是正确的。

但是如果您不需要NSSecureCoding,则还可以使用Maciej S的答案

非常容易使用它,只需将扩展添加到NSCoding协议中即可:

extension NSCoding where Self: NSObject {
    static func unsecureUnarchived(from data: Data) -> Self? {
        do {
            let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
            unarchiver.requiresSecureCoding = false
            let obj = unarchiver.decodeObject(of: self, forKey: NSKeyedArchiveRootObjectKey)
            if let error = unarchiver.error {
                print("Error:\(error)")
            }
            return obj
        } catch {
            print("Error:\(error)")
        }
        return nil
    }
}

有了这个解压缩例如NSArray的扩展,您只需要:

let myArray = NSArray.unsecureUnarchived(from: data)

使用 NSObject 类别(category)来进行 Objective-C 编程:

+ (instancetype)unsecureUnarchivedFromData:(NSData *)data {
NSError * err = nil;
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData: data error: &err];
unarchiver.requiresSecureCoding = NO;
id res = [unarchiver decodeObjectOfClass:self forKey:NSKeyedArchiveRootObjectKey];
err = err ?: unarchiver.error;
if (err != nil) {
    NSLog(@"NSKeyedUnarchiver unarchivedObject error: %@", err);
}
return  res;

请注意,如果requiresSecureCoding为false,则不会实际检查未存档对象的类,并且即使从错误的类中调用它,objective c代码也会返回有效结果。而当从错误的类调用时,swift代码将返回nil(由于可选强制转换),但没有错误。


我无法强调我有多么感激这个答案的存在。鉴于被接受的答案建议的东西也将被弃用,拥有一个希望能够永久有效的答案是非常有用的。 - tcobbs
这是最好的。 - Denis Kutlubaev

6
您可能正在寻找以下内容:
if let widgetsData = UserDefaults.standard.data(forKey: USER_DEFAULTS_KEY_WIDGET_DATA) {
        if let widgets = (try? NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, WidgetData.self], from: widgetsData)) as? [WidgetData] {
            // your code
        }
    }

1
我可以使用“[NSArray.self,WidgetData.self]”来解决与您的方法相同的问题。但是我找不到关于您的方法的公共文档。您知道文档网址吗? - Mao Nishi

5

Swift 5- IOS 13

guard let mainData = UserDefaults.standard.object(forKey: "eventDetail") as? NSData
else {
    print(" data not found in UserDefaults")
    return
}
do {
    guard let finalArray =
    try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(mainData as Data) as? [EventDetail]
    else {
        return
    }
    self.eventDetail = finalArray
}

4
 if #available(iOS 12.0, *) {
        guard let unarchivedFavorites = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(favoritesData!)
            else {
                return
        }
        self.channelFavorites = unarchivedFavorites as! [ChannelFavorite]
    } else {
        if let unarchivedFavorites = NSKeyedUnarchiver.unarchiveObject(with: favoritesData!) as? [ChannelFavorite] {
            self.channelFavorites = unarchivedFavorites
        }

// 实现数据

 if #available(iOS 12.0, *) {
            // use iOS 12-only feature
            do {
                let data = try NSKeyedArchiver.archivedData(withRootObject: channelFavorites, requiringSecureCoding: false)
                UserDefaults.standard.set(data, forKey: "channelFavorites")
            } catch {
                return
            }
        } else {
            // handle older versions
            let data = NSKeyedArchiver.archivedData(withRootObject: channelFavorites)
            UserDefaults.standard.set(data, forKey: "channelFavorites")
        }

这是我更新代码的方式,并且它对我起作用了。


1
你的可用性宏并没有意义。在第一个代码块中,为什么会有这个?unarchiveTopLevelObjectWithData()是在iOS 9中引入的,并在iOS 12中被弃用。 - Ben Kennedy

-1

这与Hopreeeen的答案略有不同。无论如何,我喜欢他的答案,因为它简单明了,并使用了苹果推荐的语法。

    if let archivedData = UserDefaults.standard.object(forKey: "myKey") as? Data {
        do {
            if let myArray = try NSKeyedUnarchiver.unarchivedObject(ofClasses: [NSArray.self, NSString.self], from: archivedData) as? [String] {
             myPreviouslyDefinedArray = myArray
            }
        } catch {
             print("read error: \(error.localizedDescription)")
        }
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接