11

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

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

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

变压器名称的设置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]
    }
}

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

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

欣赏任何想法。

4

2 回答 2

3

我也尝试使用NSSecureUnarchiveFromDataTransformer(虽然我不需要安全编码,见下文),但我没有成功。因此,我使用了自定义值转换器。我的步骤是:

我实现了我的自定义值转换器类:

@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.

然后代码在没有运行时日志的情况下正确运行。
请注意:在我的情况下,不需要进行安全编码,但可以轻松修改上面的代码以使用安全编码。只需修改功能serializedeserialize相应地修改。

编辑(由于下面 kas-kad 的评论):

抱歉,很遗憾我的代码不完整。

在应用程序委托中,我使用了以下计算属性(请参阅此链接)。这确保了值转换器很早就注册,甚至在init运行之前。

private let transformer : Void = {
    let myTransformer = MyValueTransformer()
    ValueTransformer.setValueTransformer(myTransformer, forName:NSValueTransformerName("MyValueTransformer"))
}()  

在我override class func setValueTransformer的实现中显然什么也没做。我从某个地方复制了它(不记得了)。所以肯定可以省略。

的扩展名NSValueTransformerName无非是允许.myTransformerName用作转换器名称。

于 2019-10-03T16:18:55.040 回答
2

我写了一个简单的模板类,它可以很容易地为任何实现的类创建和注册一个转换器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()

在 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)
  }
}
于 2019-10-23T18:23:53.573 回答