将 NSSecureUnarchiveFromDataTransformer 用于 Transformable 属性时崩溃

Ada*_*fer 10 core-data ios nssecurecoding ios13

在 iOS 12 中,Apple 引入了NSSecureUnarchiveFromDataTransformerName用于 CoreData 模型实体的可转换属性。我曾经将 Transformer Name 字段保持为空,它隐式使用了NSKeyedUnarchiveFromDataTransformerName. 这个转换器现在已被弃用,将来保持该字段为空将意味着NSSecureUnarchiveFromDataTransformerName取而代之。

在 iOS 13 中,如果该字段为空,您现在会收到一个运行时警告,告诉您上述内容。我在任何地方都找不到关于此的任何文档,我得到的唯一参考是 WWDC 2018 核心数据最佳实践演讲,其中简要提到了我刚才所说的内容。

现在我有一个带有实体的模型,该实体直接将HTTPURLResponse对象存储在 Transformable 属性中。它符合NSSecureCoding,我在运行时检查supportsSecureCodingtrue

NSSecureUnarchiveFromDataTransformerNameTransformer Name 的设置崩溃并显示以下消息:

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)
Run Code Online (Sandbox Code Playgroud)

所以听起来 Transformable 属性只能是这些顶级对象。

我尝试对安全转换器进行子类化并allowedTopLevelClasses按照文档的建议覆盖该属性:

@available(iOS 12.0, *)
public class NSSecureUnarchiveHTTPURLResponseFromDataTransformer: NSSecureUnarchiveFromDataTransformer {

    override public class var allowedTopLevelClasses: [AnyClass] {
        return [HTTPURLResponse.self]
    }
}
Run Code Online (Sandbox Code Playgroud)

然后我想我可以创建一个自定义转换器名称,在模型中设置它并调用setValueTransformer(_:forName:)该名称,但是我找不到 API 来NSKeyedUnarchiveFromDataTransformer为我的自定义名称设置默认值,以防我在 iOS 11 上。

请记住,我使用的是 Xcode 11 Beta 5,但是如果我要接受我得到的错误含义,这似乎无关紧要。

欣赏任何想法。

Rud*_*dog 3

我编写了一个简单的模板类,它可以轻松地为任何实现NSSecureCoding. 它在 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()
Run Code Online (Sandbox Code Playgroud)

核心数据模型中使用的转换器的名称是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)
  }
}
Run Code Online (Sandbox Code Playgroud)