Using Core Data Transformations for Encryption - ios

I'm stuck on why my transformable classes aren't being called. The following screenshot shows my Entity attribute as a transformable:
According to the documentation, it should automatically call the class "StringEncryptionTransformer" and perform the transformation.
I followed this guide in order to set up the class. I'm using the EncryptionTransformer and StringEncryptionTransformer classes provided, the only change I made was with the encryption to use RNcryptor.
Encryption:
return [RNEncryptor encryptData:data withSettings:kRNCryptorAES256Settings password:[self key] error:&error];
and Decryption:
return [RNDecryptor decryptData:data withPassword:[self key] error:&error];
The saved entity appears never to go through the transformation, is there something I'm missing? I tried adding an initialize to the NSManagedObject, but the results were the same.

you need to register value transformer like below
extension NSValueTransformerName {
static let classNameTransformerName = NSValueTransformerName(rawValue: "ClassNameTransformer")
}
ValueTransformer.setValueTransformer(ClassNameTransformer(), forName: .classNameTransformerName)

Related

Why `NSUserDefaults` not allowed to store objects of Custom Classes?

I've seen that NSUserDefaults do not allow objects other than Array, Dictionary, String, NSData, NSNumber, Bool, NSDate. But
Why it is not allowed to store other objects?
What if I need to store some properties as a single object? If I do it using dictionary, I've to write the keys somewhere which I've used to store the value. So, what could be the other alternatives.
What were the problems will arise if Apple allows the other objects also to store.
What if we use CoreData instead NSUserDefaults. I Know NSUserDefaults is globally available.
What is the best way to make a value available globally even after we relaunched the app if get terminated?
As suggested by #Hoa, previously I forgot mention NSCoding option also
What if we have many properties in a custom class, Do we need to encode and decode all of them using NSCoding methods?
You can save any object to a plist file as long as it is compliant with the NSCoding protocol.
You can use code similar to this:
+(id) readObjectFromFile:(NSString*) sFileName
{
return [NSKeyedUnarchiver unarchiveObjectWithFile:sFileName];
}
+(bool) saveObject:(id <NSCoding>) anObject ToFile:(NSString*) sFileName
{
NSData * data = [NSKeyedArchiver archivedDataWithRootObject:anObject];
NSError * error;
[data writeToFile:sFileName options:NSDataWritingAtomic error:&error];
if (error)
{
NSLog(#"Save Cats Data error: %#", error.description);
return NO;
}
return YES;
}
Swift Version:
func readObjectFromFile(sFileName: String) -> AnyObject {
return NSKeyedUnarchiver.unarchiveObjectWithFile(sFileName)
}
func saveObject(anObject: AnyObject, ToFile sFileName: String) -> Bool {
var data: NSData = NSKeyedArchiver.archivedDataWithRootObject(anObject)
var error: NSError
data.writeToFile(sFileName, options: NSDataWritingAtomic, error: error)
if error != nil {
print("Save Cats Data error: \(error.description)")
return false
}
return true
}
To learn more about the NSCoding protocol you can read:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSCoding_Protocol/
The intention behind NSUserDefaults is to save contextual data relevant to the application state, for example saving the user's preferences, the state of the application when the user stopped using it (so you can return to that state when it fires up), login session data, etc..
Core Data is a more appropriate way to store persistent data, you can map your data model as you like and has a broader variety of options to save datatypes.
Whilst NSUserDefaults is available "everywhere", this should not be a turning point to decide if this is a better option for saving your data.
You can write a singleton class that serves as a data provider so that you can access your data in the same way you access the NSUserDefaults shared instance. You just need to keep in mind that this class or module should do only one thing, serve as an interface between your model and your implementation, so you do not store any objects in this class, just use it to pass over the requests to get and save data to CoreData.
This class could look something like this:
class CoreDataProvider {
static let sharedInstance = SUProvider()
let managedObjectContext : NSManagedObjectContext
let sortDescriptor: NSSortDescriptor
let fetchRequest: NSFetchRequest
private init(){
managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
fetchRequest = NSFetchRequest(entityName: "MyObject")
sortDescriptor = NSSortDescriptor (key:"objectAttribute", ascending:true)
self.fetchRequest.sortDescriptors = [self.sortDescriptor]
}
func getSavedObjects() -> [MyObject]? {
fetchRequest.sortDescriptors = [sortDescriptor]
do {
return try self.managedObjectContext.executeFetchRequest(fetchRequest) as? [MyObject]
} catch {
print("no records found")
}
}
}
Which you would use like this:
func getAllRecords() {
let records = CoreDataProvider.sharedInstance.getSavedObjects()
//- Do whatever you need to do
}
A temporary way to store object is to create json strings of your dictionaries or arrays. I have used it in some low scale apps. You can then store those string in NSUserDefault.., and when you need to use it, you can extract it, and you can use Object Mapper library that will automatically map the json data to object type.
example, you can create a function in your class extension that parses json data to your objects any time you need it.
I would suggest using the above method only for small scale apps. If you are going for high traffic/large scale app, you might wanna look into Core Data or even SQlite3..
Feel free to ask any question
Reference to Object Mapper library is here

Xml Serialization/Deserialization of Class in Swift

As being new to Swift
i don't know how to serialize a class into xml
Class Employee
{
var mName : String = ""
var Name : String
{
get
{
return mName
}
set
{
mName = newValue
}
}
var mDesingation : String = ""
var Desingation: String
{
get
{
return mDesingation
}
set
{
mDesingation = newValue
}
}
}
I have searched a lot but haven't come across any XML Serialization Engine for Swift.
XML Serialization
For XML serialization, I suggest you use following library:
https://github.com/skjolber/xswi
As the usage is quite easy but well documented, I won't copy it here, instead you can just use examples that they provide. Since your class is very easy, it is sufficient solution. AFAIK there is no library that provides automatic serialization, because it is not used on iOS. Core data provide you with option to serialize as XML, but that is highly problematic and mostly not usable for what you want.
NSCoding / NSKeyedArchiver
If you need to just store the class on the disk and load it again, there is better option, and that is to use NSKeyedArchiver / NSCoding protocol. Again, there is great article about how to use it with extensive examples, so just the basics:
You extend your class so it conforms to NSCoding protocol
You write implementation of two methods - encodeWithCoder: and initWithDecoder:
You use NSKeyedArchiver to archive your class
You write NSData that you save to disk (and vice versa)
Hope it helps!

Object from JSON with Mantle: ignore a property

My app creates some ObJC objects from JSON. Things were working fine, until I added a new property to my ObjC model class, which doesn't have a counterpart in JSON.
I've configured the mapping as follows:
+ (NSDictionary *)JSONKeyPathsByPropertyKey
{
return #{
#"firstName" : #"firstname",
#"middleName" : #"middlename",
#"lastName" : #"lastname",
// etc.
#"phoneNumber" : NSNull.null // no JSON data for this one
};
}
However I get an assertion failure in Mantle, in MTLJSONAdapter initWithModelClass: "phoneNumber must either map to a JSON key path or a JSON array of key paths, got: null."
This is how I create the model object:
MyData *myData = [MTLJSONAdapter modelOfClass:[MyData class]
fromJSONDictionary:json error:&error];
How can I have the phoneNumber property in the data class without it mapping to a JSON value?
Just don't specify its mapping to + JSONKeyPathsByPropertyKey.
The accepted answer didn't end up working for me (I wasn't parsing from a JSON object) and I found subclassing the encodingBehaviorsByPropertyKey to work. The comments in the header file read as follows:
/// Determines how the +propertyKeys of the class are encoded into an archive.
/// The values of this dictionary should be boxed MTLModelEncodingBehavior
/// values.
///
/// Any keys not present in the dictionary will be excluded from the archive.
///
/// Subclasses overriding this method should combine their values with those of
/// `super`.
///
/// Returns a dictionary mapping the receiver's +propertyKeys to default encoding
/// behaviors. If a property is an object with `weak` semantics, the default
/// behavior is MTLModelEncodingBehaviorConditional; otherwise, the default is
/// MTLModelEncodingBehaviorUnconditional.
+ (NSDictionary *)encodingBehaviorsByPropertyKey;
Overriding that method from within my MTLModel subclass like so ended up working for me:
+ (NSDictionary *)encodingBehaviorsByPropertyKey {
NSMutableDictionary *encodingBehaviorsDictionary = [[super encodingBehaviorsByPropertyKey] mutableCopy];
[encodingBehaviorsDictionary removeObjectForKey:#"propertyToBeOmitted"];
return encodingBehaviorsDictionary;
}
FYI: I'm using Mantle version 2.1.

Using Mantle with Core Data - NSSet and NSArray

I need your help with something, as I can't get my head around this. I'm using Mantle together with CoreData in iOS.
I have relationships defined that look as follows:
Post 1:N Comment
When I pull the data from my REST Service, I create a Mantle Object Post that has a NSMutableArray of Comments in it. This works flawlessly.
I then store this in Core Data, and this is where I don't know whether I am doing things right.
[MTLManagedObjectAdapter managedObjectFromModel:post insertingIntoContext:[self getManagedObjectContext] error:&error];
So I'm doing this to store my post object into Core Data. The Core Data Model has a relationship called "post_has_comments" which is a cascading One-to-Many relationship. So on the object Post I have "posts_has_comments" -> cascading, on my object "Comment" I have a one-to-one relationship with "Nullify".
Afaik, Core Data treats this as a NSSet. What I'm trying to put in is a NSMutableArray though, as Mantle will take care of this (at least thats what a quick look in its source told me).
Unfortunately, when I get the object back from Core Data with
Post* post = [MTLManagedObjectAdapter modelOfClass:Post.class fromManagedObject:[[self fetchedResultsController] objectAtIndexPath:indexPath] error:nil];
The property comments on the post object is a empty NSSet, and I get quite some errors upon inserting the thing beforehand. The errors I get:
Core Data: annotation: repairing missing delete propagation for to-many relationship post_has_comments on object [...]
I am stuck - Maybe I am missing something huge here?
My Post Class implements the following static methods:
+ (NSDictionary *)managedObjectKeysByPropertyKey {
return #{
#"post_id" : #"id",
#"comments" : #"post_has_comments"
};
}
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
return #{
#"post_id" : #"id",
};
}
+ (NSDictionary *)relationshipModelClassesByPropertyKey {
return #{
#"comments" : IHComment.class
};
}
A simple workaround is to write your own property setter method and if value being set is NSSet then convert it to NSMutableArray before setting it back to your property ivar.
For example:
- (void)setComments:(NSMutableArray *)comments {
if ([comments isKindOfClass:NSSet.class]) {
_comments = [NSMutableArray arrayWithArray:[((NSSet *)comments) allObjects]];
} else {
_comments = comments;
}
}
I've done it myself quite a few times and it works like a charm!
From the Mantle documentation:
Mantle makes it easy to write a simple model layer for your Cocoa or Cocoa Touch application.
This is simply an unproven statement. Looking at the framework, I do not see where the evidence is. You should get your objects, and insert them into Core Data using Apple's APIs.
Post *cdPost = [NSEntityDescription insertNewObjectForEntityForName:#"Post"
inManagedObjectContext:self.managedObjectContext];
// configure the cdPost object with the data from the web service
for (id commentObject in commentArrayFromPostObject) {
Comment *cdComment =
[NSEntityDescription insertNewObjectForEntityForName:#"Comment"
inManagedObjectContext:self.managedObjectContext];
// configure the cdComment object with the data from the web service
cdComment.post = cdPost;
}
That's all there is to it.

Update NSError UserInfo

I'm trying to update an NSError object with more information. For example, an api call may fail and I want to update the error object returned from the api class with view controller information (method name that caused error, client message, any additional info). There isn't a setter method for the UserInfo dictionary and trying to set a value for the dictionary raises an exception (Not key value code compliant I believe). I thought about creating a new NSError object with the updated user info, but I wasn't sure if I might lose information.
Question
What's the best way to update the user info dictionary of an NSError object?
With swift extentions it's easy:
extension NSError {
func addItemsToUserInfo(newUserInfo: Dictionary<String, String>) -> NSError {
var currentUserInfo = userInfo
newUserInfo.forEach { (key, value) in
currentUserInfo[key] = value
}
return NSError(domain: domain, code: code, userInfo: currentUserInfo)
}
}
usage:
var yourError = NSError(domain: "com.app.your", code: 999, userInfo: nil)
yourError = yourError.addItemsToUserInfo(["key1":"value1","key2":"value2"])
The canonical approach would be to make a new NSError all your own and then put the original NSError in the userInfo dictionary under the key NSUnderlyingErrorKey. This is a slightly different result, but as best I can tell NSErrors are quite intentionally immutable.
Easiest way to do this would be to get a mutable copy of the userInfo dictionary and add whatever you like to that. Then you would have to create a new NSError (since there is not setUserInfo: method) with the same domain and code as the original one.

Resources