NSSecureUnarchiveFromDataTransformer in CoreData

Sandeep Joshi
Yudiz Solutions
Published in
5 min readDec 27, 2019

Overview: CoreData supports Transformable type entity attributes.
Transformable type allows us to store custom data types as an object of the attribute of an entity. The basic requirement is that our custom data type should confirm to NSValueTransformer and NSCoding.

If you have already implemented NSCoding without NSValueTransformer then in iOS 13 you will be getting a warning/error on CoreData Transformable attributes in Xcode 11. I will demonstrate this to you.

Topics:

Create a Transformable attribute

Register Transformer

Create a Transformable attribute:

Let’s create a new project with a Single View App and name it TransformableAttributeDemo.

Clicking on the Next button to our project setup is ready. Please take care that Use Core Data option is ticked before clicking the Next button. If you forgot to tick Use Core Data then you can add it manually.

Our TransformableAttributeDemo.xcdatamodeld Data Model file is ready.
Now let’s set up the Entity.

  • Click on Add Entity to create the LoggedUser entity.
  • LoggedUser Class section inspector we will set the Codegen as Manual/None and set Module as Current Product Module, and set Name to LoggedUser (Manually set up this class later).
  • pushSettings’ attribute added to the entity. Set this attribute type as transformable.

Let’s now create LoggedUser Class.

import CoreDataclass LoggedUser: NSManagedObject{    @NSManaged var pushSettings: [AccountPushSetting]!
}

Now, ‘pushSettings’ data type is an array of AccountPushSetting and ‘AccountPushSetting’ conforms to NSSecureUnarchiveFormDataTransformer and NSCoding.

import Foundationenum AccountPushSettingType: Int {      case general
case tips
case meeting_request
var display_title: String {
switch self {
case .general:
return “General”
case .meeting_request:
return “Meeting Request”
case .tips:
return “Tips”
}
}
}
// MARK: — AccountPushSettingclass AccountPushSetting: NSSecureUnarchiveFromDataTransformer, NSCoding { var type: AccountPushSettingType
var isOn: Bool
init(type settingType: AccountPushSettingType, val: Bool) {
type = settingType
isOn = val
}
func encode(with coder: NSCoder) {
coder.encode(type.rawValue, forKey: “type”)
coder.encode(isOn, forKey: “isOn”)
}
required init?(coder: NSCoder) {
type = AccountPushSettingType(rawValue:
coder.decodeInteger(forKey: “type”))!
isOn = coder.decodeBool(forKey: “isOn”)
}
}

NOTE:
Now our LoggedUser class ready but when trying to save or fetch you will be getting a warning /error in the console like following.

CoreData: fault: One or more models in this application are using transformable properties with transformer names that are either unset or set to NSKeyedUnarchiveFromDataTransformerName. Please switch to using “NSSecureUnarchiveFromData” or a subclass of NSSecureUnarchiveFromDataTransformer instead. At some point, Core Data will default to using “NSSecureUnarchiveFromData” when nil is specified, and transformable properties containing classes that do not support NSSecureCoding will become unreadable.

CoreData: warning: Property pushSettings on Entity ‘LoggedUser’ are using nil or an insecure NSValueTransformer. Please switch to using “NSSecureUnarchiveFromData” or a subclass of NSSecureUnarchiveFromDataTransformer instead.

Register Transformer:

Let’s create ValueTransformer class and the name of this class is ‘SJParentValueTransformer’

import Foundationpublic class SJParentValueTransformer<T: NSCoding & 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
}
/// The name of this transformer. This is the name used to
register the transformer using
`ValueTransformer.setValueTransformer(_:forName:)
`
public static var transformerName: NSValueTransformerName {
let className = “\(T.self.classForCoder())”
return NSValueTransformerName(“\(className)Transformer”) //
we append the Transformer due easily identify.
Example. Clase name UserSetting then the name
of the transformer is UserSettingTransformer

}

/// Registers the transformer by calling
`ValueTransformer.setValueTransformer(_:forName:)`.

public static func registerTransformer() {
let transformer = SJParentValueTransformer<T>()
ValueTransformer.setValueTransformer(transformer, forName:
transformerName)
}
}

This class used to registers the transformer with for name. This class method is used to register the transformer and the name of the transformer.

ValueTransformer.setValueTransformer(_ transformer: ValueTransformer?, forName name: NSValueTransformerName)

Static Variable transformerName will return the name of the transformer. Example If your class name is UserSetting then the name of the transformer is UserSettingTransformer. We’re appending the “Transformer” for easy identification. You can add your own suffix or prefix to identify.

public static var transformerName: NSValueTransformerName {     let className = “\(T.self.classForCoder())”     return NSValueTransformerName(“\(className)Transformer”) // we   
append the Transformer due easily identify. Example.
Clase name UserSetting then the name of the transformer
is UserSettingTransformer
}

This transformerName will be set on Value Transformer Name in the entity of attributes. Let’s set the Transformer name AccountPushSettingTransformer’ on pushSettings’ attribute in the LoggedUser entity.

Register the transformer in AppDelegate when App first time launch on didFinishLaunchingWithOptions method.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {     // Override point for customization after
application launch.
SJParentValueTransformer<AccountPushSetting>.
registerTransformer()
// Name of Transformer is
AccountPushSettingTransformer (ClaseName+Transformer)
return true
}

Now our Transformable attributes are ready and when trying to save or fetch error/warning will be fixed.

Conclusion: Confirm NSSecureUnarchiveFromDataTransformer and NSCoding on a custom data type for transformable. Register Transformer on app launch and Set Transformer Name on transformable attributes of the entity. We are replacing the older object with a new object with the new value set. So CoreData will track the changes. So in iOS 13 & Xcode 11 NSValueTransformer warning/error will be fixed.

I hope this helps.

--

--

Sandeep Joshi
Yudiz Solutions

 iOS | ⌚ watchOS | Swift | SwiftUI | Objective-C | Jira | git | 📱 Mobile App 👨‍💻 | 💎 Ruby Rails