为 Transformable 采用 NSSecureUnarchiveFromDataTransformer 时发生崩溃 属性
Crash when adopting NSSecureUnarchiveFromDataTransformer for a Transformable property
在 iOS 12 中,Apple 引入了 NSSecureUnarchiveFromDataTransformerName
用于 CoreData 模型实体的 Transformable 属性。我曾经将 Transformer Name 字段保留为空,这隐含地使用了 NSKeyedUnarchiveFromDataTransformerName
。此转换器现已弃用,将来将该字段留空将意味着 NSSecureUnarchiveFromDataTransformerName
。
在 iOS 13 中,如果该字段为空,您现在会收到一个运行时警告,告诉您上述情况。我在任何地方都找不到这方面的任何文档,我得到的唯一参考是 WWDC 2018 核心数据最佳实践演讲,其中简要提到了我刚才所说的内容。
现在我有一个带有实体的模型,它直接将 HTTPURLResponse
个对象存储在可变形 属性 中。它符合NSSecureCoding
,我在运行时检查supportsSecureCoding
是true
。
为转换器名称设置 NSSecureUnarchiveFromDataTransformerName
时崩溃并显示此消息:
Object of class NSHTTPURLResponse is not among allowed top level class list (
NSArray,
NSDictionary,
NSSet,
NSString,
NSNumber,
NSDate,
NSData,
NSURL,
NSUUID,
NSNull
) with userInfo of (null)
所以听起来 Transformable 属性只能属于这些顶级对象。
我尝试将安全转换器子类化并按照文档的建议覆盖 allowedTopLevelClasses
属性:
@available(iOS 12.0, *)
public class NSSecureUnarchiveHTTPURLResponseFromDataTransformer: NSSecureUnarchiveFromDataTransformer {
override public class var allowedTopLevelClasses: [AnyClass] {
return [HTTPURLResponse.self]
}
}
然后我想我可以创建一个自定义转换器名称,在模型中设置它并为该名称调用 setValueTransformer(_:forName:)
,但我找不到 API 来设置默认值 NSKeyedUnarchiveFromDataTransformer
作为我的自定义名称,以防我在 iOS 11.
请记住,我使用的是 Xcode 11 Beta 5,但如果我要接受所陈述的错误的含义,这似乎并不相关。
感谢任何想法。
我也尝试使用 NSSecureUnarchiveFromDataTransformer
(虽然我不需要安全编码,见下文),但我没有成功。因此,我改为使用自定义值转换器。我的步骤是:
我实现了我的自定义值转换器class:
@objc(MyTransformer)
class MyTransformer: ValueTransformer {
override class func setValueTransformer(_ transformer: ValueTransformer?, forName name: NSValueTransformerName) {
ValueTransformer.setValueTransformer(transformer, forName: name)
}
override func transformedValue(_ value: Any?) -> Any? {
guard let value = value else { return nil }
let data = serialize(value) // A custom function, e.g. using an NSKeyedArchiver
return data as NSData
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let value = value else { return nil }
guard let data = value as? Data else { return nil }
let set = deserialize(data) // A custom function, e.g. using an NSKeyedUnarchiver
return set as NSSet // Or as an NSArray, or whatever the app expects
}
}
extension NSValueTransformerName {
static let myTransformerName = NSValueTransformerName(rawValue: „MyTransformer")
}
第一行(@objc
)为必填项,见!否则 coreData 无法识别自定义转换器!
接下来,我根据 在应用程序委托中实现了计算 属性:
private let transformer: Void = {
MyTransformer.setValueTransformer(MyTransformer(), forName: .myTransformerName)
}()
尽早执行此操作很重要,例如在应用程序委托中,以便 coreData 在初始化时识别转换器。
最终,我在 xcdatamodeld
文件中的可转换属性的属性检查器中将 Transformer 值设置为 MyTransformer
。
然后代码 运行 正确无 运行 时间日志。
请注意:在我的例子中,没有必要进行安全编码,但上面的代码可以很容易地修改为使用安全编码。只需相应地修改函数 serialize
和 deserialize
。
编辑(由于下面 kas-kad 的评论):
抱歉,很遗憾,我的代码不完整。
在应用程序委托中,我使用了以下计算的 属性(参见 )。这确保值转换器很早就被注册,甚至在 init
是 运行 之前。
private let transformer : Void = {
let myTransformer = MyValueTransformer()
ValueTransformer.setValueTransformer(myTransformer, forName:NSValueTransformerName("MyValueTransformer"))
}()
而 override class func setValueTransformer
在我的实现中显然没有任何作用。我从某个地方复制的(不记得了)。所以完全可以省略。
NSValueTransformerName
的扩展只是允许使用 .myTransformerName
作为转换器名称。
我写了一个简单的模板 class,它可以很容易地为实现 NSSecureCoding
的任何 class 创建和注册转换器。它在 iOS 12 和 13 中对我来说工作正常,至少在我使用 UIColor
作为可转换属性的简单测试中是这样。
使用方法(以UIColor
为例):
// Make UIColor adopt ValueTransforming
extension UIColor: ValueTransforming {
static var valueTransformerName: NSValueTransformerName {
.init("UIColorValueTransformer")
}
}
// Register the transformer somewhere early in app startup.
NSSecureCodingValueTransformer<UIColor>.registerTransformer()
要在 Core Data 模型中使用的转换器的名称是 UIColorValueTransformer
。
import Foundation
public protocol ValueTransforming: NSSecureCoding {
static var valueTransformerName: NSValueTransformerName { get }
}
public class NSSecureCodingValueTransformer<T: NSSecureCoding & NSObject>: ValueTransformer {
public override class func transformedValueClass() -> AnyClass { T.self }
public override class func allowsReverseTransformation() -> Bool { true }
public override func transformedValue(_ value: Any?) -> Any? {
guard let value = value as? T else { return nil }
return try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
}
public override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? NSData else { return nil }
let result = try? NSKeyedUnarchiver.unarchivedObject(
ofClass: T.self,
from: data as Data
)
return result
}
/// Registers the transformer by calling `ValueTransformer.setValueTransformer(_:forName:)`.
public static func registerTransformer() {
let transformer = NSSecureCodingValueTransformer<T>()
ValueTransformer.setValueTransformer(transformer, forName: T.valueTransformerName)
}
}
在 iOS 12 中,Apple 引入了 NSSecureUnarchiveFromDataTransformerName
用于 CoreData 模型实体的 Transformable 属性。我曾经将 Transformer Name 字段保留为空,这隐含地使用了 NSKeyedUnarchiveFromDataTransformerName
。此转换器现已弃用,将来将该字段留空将意味着 NSSecureUnarchiveFromDataTransformerName
。
在 iOS 13 中,如果该字段为空,您现在会收到一个运行时警告,告诉您上述情况。我在任何地方都找不到这方面的任何文档,我得到的唯一参考是 WWDC 2018 核心数据最佳实践演讲,其中简要提到了我刚才所说的内容。
现在我有一个带有实体的模型,它直接将 HTTPURLResponse
个对象存储在可变形 属性 中。它符合NSSecureCoding
,我在运行时检查supportsSecureCoding
是true
。
为转换器名称设置 NSSecureUnarchiveFromDataTransformerName
时崩溃并显示此消息:
Object of class NSHTTPURLResponse is not among allowed top level class list (
NSArray,
NSDictionary,
NSSet,
NSString,
NSNumber,
NSDate,
NSData,
NSURL,
NSUUID,
NSNull
) with userInfo of (null)
所以听起来 Transformable 属性只能属于这些顶级对象。
我尝试将安全转换器子类化并按照文档的建议覆盖 allowedTopLevelClasses
属性:
@available(iOS 12.0, *)
public class NSSecureUnarchiveHTTPURLResponseFromDataTransformer: NSSecureUnarchiveFromDataTransformer {
override public class var allowedTopLevelClasses: [AnyClass] {
return [HTTPURLResponse.self]
}
}
然后我想我可以创建一个自定义转换器名称,在模型中设置它并为该名称调用 setValueTransformer(_:forName:)
,但我找不到 API 来设置默认值 NSKeyedUnarchiveFromDataTransformer
作为我的自定义名称,以防我在 iOS 11.
请记住,我使用的是 Xcode 11 Beta 5,但如果我要接受所陈述的错误的含义,这似乎并不相关。
感谢任何想法。
我也尝试使用 NSSecureUnarchiveFromDataTransformer
(虽然我不需要安全编码,见下文),但我没有成功。因此,我改为使用自定义值转换器。我的步骤是:
我实现了我的自定义值转换器class:
@objc(MyTransformer)
class MyTransformer: ValueTransformer {
override class func setValueTransformer(_ transformer: ValueTransformer?, forName name: NSValueTransformerName) {
ValueTransformer.setValueTransformer(transformer, forName: name)
}
override func transformedValue(_ value: Any?) -> Any? {
guard let value = value else { return nil }
let data = serialize(value) // A custom function, e.g. using an NSKeyedArchiver
return data as NSData
}
override class func allowsReverseTransformation() -> Bool {
return true
}
override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let value = value else { return nil }
guard let data = value as? Data else { return nil }
let set = deserialize(data) // A custom function, e.g. using an NSKeyedUnarchiver
return set as NSSet // Or as an NSArray, or whatever the app expects
}
}
extension NSValueTransformerName {
static let myTransformerName = NSValueTransformerName(rawValue: „MyTransformer")
}
第一行(@objc
)为必填项,见
接下来,我根据
private let transformer: Void = {
MyTransformer.setValueTransformer(MyTransformer(), forName: .myTransformerName)
}()
尽早执行此操作很重要,例如在应用程序委托中,以便 coreData 在初始化时识别转换器。
最终,我在 xcdatamodeld
文件中的可转换属性的属性检查器中将 Transformer 值设置为 MyTransformer
。
然后代码 运行 正确无 运行 时间日志。
请注意:在我的例子中,没有必要进行安全编码,但上面的代码可以很容易地修改为使用安全编码。只需相应地修改函数 serialize
和 deserialize
。
编辑(由于下面 kas-kad 的评论):
抱歉,很遗憾,我的代码不完整。
在应用程序委托中,我使用了以下计算的 属性(参见 init
是 运行 之前。
private let transformer : Void = {
let myTransformer = MyValueTransformer()
ValueTransformer.setValueTransformer(myTransformer, forName:NSValueTransformerName("MyValueTransformer"))
}()
而 override class func setValueTransformer
在我的实现中显然没有任何作用。我从某个地方复制的(不记得了)。所以完全可以省略。
NSValueTransformerName
的扩展只是允许使用 .myTransformerName
作为转换器名称。
我写了一个简单的模板 class,它可以很容易地为实现 NSSecureCoding
的任何 class 创建和注册转换器。它在 iOS 12 和 13 中对我来说工作正常,至少在我使用 UIColor
作为可转换属性的简单测试中是这样。
使用方法(以UIColor
为例):
// Make UIColor adopt ValueTransforming
extension UIColor: ValueTransforming {
static var valueTransformerName: NSValueTransformerName {
.init("UIColorValueTransformer")
}
}
// Register the transformer somewhere early in app startup.
NSSecureCodingValueTransformer<UIColor>.registerTransformer()
要在 Core Data 模型中使用的转换器的名称是 UIColorValueTransformer
。
import Foundation
public protocol ValueTransforming: NSSecureCoding {
static var valueTransformerName: NSValueTransformerName { get }
}
public class NSSecureCodingValueTransformer<T: NSSecureCoding & NSObject>: ValueTransformer {
public override class func transformedValueClass() -> AnyClass { T.self }
public override class func allowsReverseTransformation() -> Bool { true }
public override func transformedValue(_ value: Any?) -> Any? {
guard let value = value as? T else { return nil }
return try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true)
}
public override func reverseTransformedValue(_ value: Any?) -> Any? {
guard let data = value as? NSData else { return nil }
let result = try? NSKeyedUnarchiver.unarchivedObject(
ofClass: T.self,
from: data as Data
)
return result
}
/// Registers the transformer by calling `ValueTransformer.setValueTransformer(_:forName:)`.
public static func registerTransformer() {
let transformer = NSSecureCodingValueTransformer<T>()
ValueTransformer.setValueTransformer(transformer, forName: T.valueTransformerName)
}
}