Crash when adopting NSSecureUnarchiveFromDataTransformer for a Transformable property - ios

In iOS 12 Apple introduced NSSecureUnarchiveFromDataTransformerName for use on CoreData model entities' Transformable properties. I used to keep the Transformer Name field empty, which implicitly used NSKeyedUnarchiveFromDataTransformerName. This transformer is now deprecated, and keeping the field empty in the future will mean NSSecureUnarchiveFromDataTransformerName instead.
In iOS 13, if that field is empty, you now get a runtime warning telling you the aforementioned. I couldn't find any documentation on this anywhere, the only reference I got was a WWDC 2018 Core Data Best Practices talk which briefly mentioned what I just said.
Now I have a model with an entity which directly stores HTTPURLResponse objects in a Transformable property. It conforms to NSSecureCoding, and I checked in runtime that supportsSecureCoding is true.
Setting NSSecureUnarchiveFromDataTransformerName for the Transformer Name crashes with this message:
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)
So it sounds like Transformable properties can only be of these top level objects.
I tried subclassing the secure transformer and override the allowedTopLevelClasses property as suggested by the documentation:
#available(iOS 12.0, *)
public class NSSecureUnarchiveHTTPURLResponseFromDataTransformer: NSSecureUnarchiveFromDataTransformer {
override public class var allowedTopLevelClasses: [AnyClass] {
return [HTTPURLResponse.self]
}
}
Then I'd imagine I can create a custom transformer name, set it in the model and call setValueTransformer(_:forName:) for that name, but I couldn't find API to set the default NSKeyedUnarchiveFromDataTransformer for my custom name in case I'm on iOS 11.
Keep in mind, I'm using Xcode 11 Beta 5, but this doesn't seem to be related if I am to accept the meaning of the error I'm getting as stated.
Appreciate any thoughts.

I wrote a simple template class which makes it easy to create and register a transformer for any class that implements NSSecureCoding. It works fine for me in iOS 12 and 13, at least in my simple test using UIColor as the transformable attribute.
To use it (using UIColor as an example):
// Make UIColor adopt ValueTransforming
extension UIColor: ValueTransforming {
static var valueTransformerName: NSValueTransformerName {
.init("UIColorValueTransformer")
}
}
// Register the transformer somewhere early in app startup.
NSSecureCodingValueTransformer<UIColor>.registerTransformer()
The name of the transformer to use in the Core Data model is 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)
}
}

I tried to use NSSecureUnarchiveFromDataTransformer also (although I don't need secure coding, see below), but I did not have success. Thus I used a custom value transformer instead. My steps were:
I implemented my custom value transformer 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")
}
The 1st line (#objc) is required, see this post! Otherwise coreData does not recognise the custom transformer!
Next, I implemented a computed property in the app delegate, according to this post:
private let transformer: Void = {
MyTransformer.setValueTransformer(MyTransformer(), forName: .myTransformerName)
}()
It is important to do this early, e.g. in the app delegate, so that coreData does recognise the transformer when it is initialised.
Eventually, I set in the attribute inspector of the transformable attribute in the xcdatamodeld file the Transformer value to MyTransformer.
Then the code run correctly without run time logs.
Please note: In my case, it was not necessary to do secure coding, but the code above can easily be modified to use secure coding instead. Just modify the functions serialize and deserialize accordingly.
EDIT (due to the comment of kas-kad below):
Sorry, my code was unfortunately not complete.
In the app delegate, I used the following computed property (see this link). This ensures that the value transformer is registered very early, even before init is run.
private let transformer : Void = {
let myTransformer = MyValueTransformer()
ValueTransformer.setValueTransformer(myTransformer, forName:NSValueTransformerName("MyValueTransformer"))
}()
And to override class func setValueTransformer does in my implementation obviously nothing. I copied it from somewhere (cannot remember). So one can surely omit it.
The extension of NSValueTransformerName does nothing more than to allow to use .myTransformerName as the transformer name.

Related

"Prepare for segue" for WatchKit

I am attempting to transfer a string from one view controller to another. The Problem that I having is that it is required to set the string 'as any object'.
The code I have below returns a nil in the console. Is there any way to send data across VC's as strings? Thanks in advance.
In MainVc-
func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
// You may want to set the context's identifier in Interface Builder and check it here to make sure you're returning data at the proper times
// Return data to be accessed in ResultsController
var currentValue = "Hellooo"
return currentValue as String as AnyObject
}
In receiving VC.
override func awake(withContext context: Any?) {
super.awake(withContext: context)
if let val: Any = context as? Any {
print(val)
} else {
print("An error occurred")
} // Configure interface objects here.
}
The output is simply nil.
First of all currentValue as String has no effect, currentValue is already a String.
Secondly, casting val to Any again makes no sense, since val is already of non-explicit type, Any. You should conditional cast it to String, since you are expecting a String variable.
For such a simple case, I wouldn't use 'contextForSegueWithIdentifier' at all, I would just simply use presentController(withName:context:) or pushController(withName:context:).
Using these you don't need to do any casting, you can simply do presentController(withName: "MyContoller", context: "Hello").
I came across with the same problem. The solution was as Larme suggested, used the appropriate function for swift 3 or 4, in this case, I assume you tried to use this code
override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
but the compiler suggests that doesn't have to be overridden because Method does not override any method from its superclass
The easy solution is to remove the override. The app compiles but the contextForSegue function is never dispatched.
The correct structure is as follow:
override func contextForSegue(withIdentifier segueIdentifier: String) -> Any?
{

Return an array of type Self

I'm having trouble figuring out a way to return an array of instances of a specific dynamic class type, at runtime, in Swift.
I successfully compiled and tested this version which returns a single instance of a class:
class Generic {
class func all() -> Self {
return self.init()
}
required init() {
}
}
class A: Generic {
}
let a = A.all() // is of type A
The challenge here is to get compilation to allow the all function to be prototyped as follows: class func all() -> [Self] (i.e return an array of instances, working with subclasses, without any cast).
class Generic {
class func all() -> [Self] {
return [self.init()]
}
required init() {
}
}
class A: Generic {
}
let a = A.all() // won't compile
I could return an array of Generic instances with class func all() -> [Generic] but this requires an additional cast with as! to get the correct type A. I'd like to take advantage of begin in the context of class A and using the Self keyword, to let the compiler infer the 'real' type. Do you guys think it's possible?
It seems to be only possible to return single instances, not arrays.
EDIT: Got this to work using AnyObject. Better, but not optimal as it requires a cast to the correct type.
class Generic {
class func all() -> [AnyObject] {
return [self.init()]
}
required init() {
}
}
class A: Generic {
}
let a = A.all() as! [A]
Thanks!
PS: Any other way to do this using generics or protocols/protocol extensions is also an option. If you have a more "Swifty" version in mind, please be my guest. Can't help myself thinking there's maybe a better way to do this, but can't figure out how.
The only option I can see of doing something like that is using protocols instead of a base class, like this:
protocol Generic {
func all() -> [Self]
init()
}
extension Generic {
func all() -> [Self] {
return [self.dynamicType.init()]
}
}
final class A : Generic {
}
A().all()
You have two limitations doing it like this. First, all classes that conform to your protocol have to be final. Second, all classes must obviously implement the init defined in the protocol, otherwise we wouldn't be able to have the all method defined.
Edit: you don't actually need to define the init as long as you don't define any other initializers
Edit 2: I didn't notice you used class functions, you can modify my example to use class functions instead of instance methods by replacing func all() -> [Self] with static func all() -> [Self] and
func all() -> [Self] {
return [self.dynamicType.init()]
}
with
static func all() -> [Self] {
return [self.init()]
}
Unfortunately, there doesn't seem to be a way to do this using Self. Self cannot be used in expressions, so [Self] or Array<Self> are not allowed.
However, I think that your use case is completely valid and you should repot it as a bug.

Cannot decode object of class

I am trying to send a "Class" to my Watchkit extension but I get this error.
* Terminating app due to uncaught exception 'NSInvalidUnarchiveOperationException', reason: '*
-[NSKeyedUnarchiver decodeObjectForKey:]: cannot decode object of class (MyApp.Person)
Archiving and unarchiving works fine on the iOS App but not while communicating with the watchkit extension. What's wrong?
InterfaceController.swift
let userInfo = ["method":"getData"]
WKInterfaceController.openParentApplication(userInfo,
reply: { (userInfo:[NSObject : AnyObject]!, error: NSError!) -> Void in
println(userInfo["data"]) // prints <62706c69 7374303...
if let data = userInfo["data"] as? NSData {
if let person = NSKeyedUnarchiver.unarchiveObjectWithData(data) as? Person {
println(person.name)
}
}
})
AppDelegate.swift
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
reply: (([NSObject : AnyObject]!) -> Void)!) {
var bob = Person()
bob.name = "Bob"
bob.age = 25
reply(["data" : NSKeyedArchiver.archivedDataWithRootObject(bob)])
return
}
Person.swift
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
According to Interacting with Objective-C APIs:
When you use the #objc(name) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when you migrate an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the #objc(name) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.
By adding the annotation #objc(name), namespacing is ignored even if we are just working with Swift. Let's demonstrate. Imagine target A defines three classes:
#objc(Adam)
class Adam:NSObject {
}
#objc class Bob:NSObject {
}
class Carol:NSObject {
}
If target B calls these classes:
print("\(Adam().classForCoder)")
print("\(Bob().classForCoder)")
print("\(Carol().classForCoder)")
The output will be:
Adam
B.Bob
B.Carol
However if target A calls these classes the result will be:
Adam
A.Bob
A.Carol
To resolve your issue, just add the #objc(name) directive:
#objc(Person)
class Person : NSObject, NSCoding {
var name: String!
var age: Int!
// MARK: NSCoding
required convenience init(coder decoder: NSCoder) {
self.init()
self.name = decoder.decodeObjectForKey("name") as! String?
self.age = decoder.decodeIntegerForKey("age")
}
func encodeWithCoder(coder: NSCoder) {
coder.encodeObject(self.name, forKey: "name")
coder.encodeInt(Int32(self.age), forKey: "age")
}
}
I had to add the following lines after setting up the framework to make the NSKeyedUnarchiver work properly.
Before unarchiving:
NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName")
Before archiving:
NSKeyedArchiver.setClassName("YourClassName", forClass: YourClassName.self)
NOTE: While the information in this answer is correct, the way better answer is the one below by #agy.
This is caused by the compiler creating MyApp.Person & MyAppWatchKitExtension.Person from the same class. It's usually caused by sharing the same class across two targets instead of creating a framework to share it.
Two fixes:
The proper fix is to extract Person into a framework. Both the main app & watchkit extension should use the framework and will be using the same *.Person class.
The workaround is to serialize your class into a Foundation object (like NSDictionary) before you save & pass it. The NSDictionary will be code & decodable across both the app and extension. A good way to do this is to implement the RawRepresentable protocol on Person instead.
I had a similar situation where my app used my Core framework in which I kept all model classes. E.g. I stored and retrieved UserProfile object using NSKeyedArchiver and NSKeyedUnarchiver, when I decided to move all my classes to MyApp NSKeyedUnarchiver started throwing errors because the stored objects were like Core.UserProfile and not MyApp.UserProfile as expected by the unarchiver. How I solved it was to create a subclass of NSKeyedUnarchiver and override classforClassName function:
class SKKeyedUnarchiver: NSKeyedUnarchiver {
override open func `class`(forClassName codedName: String) -> Swift.AnyClass? {
let lagacyModuleString = "Core."
if let range = codedName.range(of: lagacyModuleString), range.lowerBound.encodedOffset == 0 {
return NSClassFromString(codedName.replacingOccurrences(of: lagacyModuleString, with: ""))
}
return NSClassFromString(codedName)
}
}
Then added #objc(name) to classes which needed to be archived, as suggested in one of the answers here.
And call it like this:
if let unarchivedObject = SKKeyedUnarchiver.unarchiveObject(withFile: UserProfileServiceImplementation.archiveURL.path) as? UserProfile {
currentUserProfile = unarchivedObject
}
It worked very well.
The reason why the solution NSKeyedUnarchiver.setClass(YourClassName.self, forClassName: "YourClassName") was not for me because it doesn't work for nested objects such as when UserProfile has a var address: Address. Unarchiver will succeed with the UserProfile but will fail when it goes a level deeper to Address.
And the reason why the #objc(name) solution alone didn't do it for me was because I didn't move from OBJ-C to Swift, so the issue was not UserProfile -> MyApp.UserProfile but instead Core.UserProfile -> MyApp.UserProfile.
I started facing this after the App Name change,
The error I got was - ".....cannot decode object of class (MyOldModuleName.MyClassWhichISerialized) for key....."
This is because code by default saves Archived object with ModuleName prefix, which will not be locatable after ModuleName changes. You can identify the old Module Name from the error message class prefix, which here is  "MyOldModuleName". 
I simply used the old names to locate the old Archived objects.
So before Unarchieving add line,
NSKeyedUnarchiver.setClass(MyClassWhichISerialized.self, forClassName: "MyOldModuleName.MyClassWhichISerialized")
And before Archieving add line
NSKeyedArchiver.setClassName("MyOldModuleName.MyClassWhichISerialized", for: MyClassWhichISerialized.self)

Swift - Dynamic cast class unconditional?

It doesn't seem like I can cast a generic type to another? Swift is throwing DynamicCastClassException.
Basically here is the problem:
// T is defined as T: NSObject
let oebj1 = NetworkResponse<User>()
let oebj2 = oebj1 as NetworkResponse<NSObject>
Here is why I need to do this casting
class BaseViewController: UIViewController {
// Not allowed to make a generic viewController and therefore have to cast the generic down to NSObject
func fetchData(completion: (NetworkResponse<NSObject>)->()) {
fatalError("You have to implement fetchData method")
}
}
class UsersViewController: BaseViewController {
override func fetchData(completion: (NetworkResponse<NSObject>)->()) {
userNetworkManager.fetchUsers { networkUSerResponse in
completion(networkUSerResponse as NetworkResponse<NSObject>)
}
}
}
class UserNetworkManager {
func fetchUsers(completion: (NetworkResponse<User>)->()) {
// Do stuff
}
}
In general, there doesn't seem to be a way to do this. The basic problem is that NetworkResponse<NSObject> and NetworkResponse<User> are essentially completely unrelated types that happen to have identical functionality and similar naming.
In this specific case, it really isn't necessary since you're throwing away the known Userness of the result anyway, meaning that if you really want to treat it as a User later you'll have to do a conditional cast back. Just remove the generic from NetworkResponse and it will all work as expected. The major drawback is that within UserVC.fetchData you won't have access to the returned User result without a (conditional) cast.
The alternative solution would be to separate out whatever additional information is in NetworkResponse from the payload type (User/NSObject) using a wrapper of some sort (assuming there's significant sideband data there). That way you could pass the NetworkResponse to super without mutilation and down-cast the payload object as needed.
Something like this:
class User : NSObject {
}
class Transaction {
let request:NSURLRequest?
let response:NSURLResponse?
let data:NSData?
}
class Response<T:NSObject> {
let transaction:Transaction
let payload:T
init(transaction:Transaction, payload:T) {
self.transaction = transaction
self.payload = payload
}
}
class UserNetworkManager {
func fetchUsers(completion: (Response<User>) -> ()) {
completion(Response(transaction:Transaction(), payload:User()))
}
}
let userNetworkManager = UserNetworkManager();
class BaseVC {
func fetchData(completion: (Response<NSObject>) -> ()) {
fatalError("Gotta implement fetchData")
}
}
class UserVC : BaseVC {
override func fetchData(completion: (Response<NSObject>) -> ()) {
userNetworkManager.fetchUsers { response -> () in
completion(Response(transaction: response.transaction, payload: response.payload))
}
}
}
Although at that point, you're probably better off just separating the transaction information and payload information into separate arguments to the callback.

Swift, Self from AnyObject

Is it possible to get Self from AnyObject?
Take this example:
// Superclass
class ManagedObject {
class func findByID(id: String) -> AnyObject? {
let objects = objectsWithPredicate(NSPredicate(format: "id == %#", id))
return objects.firstObject() // Returns AnyObject
}
}
// Subclass
class User : ManagedObject {
class func returnFirstSelf() -> Self? {
return findById("1") // This doesn't work because it returns AnyObject, but I need Self.
}
}
If not, what is the best alternative way to ensure that when calling User.returnFirstSelf(), the compiler gives back a User, and when calling UserSubclass.returnFirstSelf(), it gives back a UserSubclass.
You can simply return User? from your class function, if this is an option:
public class func returnFirstSelf() -> User? {
if let found = findByID("1") as? User {
return found
}
return nil
}
There's currently no way (I'm aware of) to return Self? with Swift as it stands. The problem is that Self has a somewhat... "dynamic" meaning, separate from concrete types, protocols, and even generics. A particular example that demonstrates this is: What if you have a class StudentUser that extends User? If you tried to implement it like this:
class func returnFirstSelf() -> Self? {
if let found = findById("1") as? Self { // Note the check that 'found' is a 'Self'
return found
}
return nil
}
Then you encounter a compiler error because you cannot use Self outside the result of a protocol or class method. And if you try to implement it like this:
class func returnFirstSelf() -> Self? {
if let found = findById("1") as? User { // Note 'User' instead of 'Self'
return found
}
return nil
}
Then you run the risk of Self actually meaning StudentUser, and even if you pass the check that requires found to be a User, it doesn't guarantee that found will be a StudentUser. This will occur in the case that StudentUser does not override the method to ensure that the as? checks against StudentUser.
The critical flaw here in my opinion is that you cannot use the required keyword on class methods, requiring subclasses to override them. This would allow the compiler to ensure that any subclasses have overridden the method and provided an implementation that can guarantee type safety.
Craig Otis' answer is spot on. However, one option is to create a copy constructor. This may not work for the asker's scenario where they're inheriting from a NSManagedObject, but it should work for non-managed objects.
required init(user: User) {
super.init(user: User) // or super.init() if top of inheritance.
self.name = user.name
self.email = user.email
// etc.
}
class func returnFirstSelf() -> Self? {
if let found = findById("1") { // Note the check that 'found' is a 'Self'
return self.init(copy: found as! User)
}
return nil
}

Resources