According to this post I can use the encryption/decryption methods to store/retrieve plist file securely.
But the problem is:
Q: After I have decrypted the plist file, how can I parse and store the plist file as NSDictrionary object
Probably NSPropertyListSerialization is what you are looking for.
As seen in this Post:
Plist Array to NSDictionary
You could use core foundation approach here with the method CFPropertyListCreateFromXMLData
If the plist represents the NSDictionary content, the following check should be passed:
if ([(id)plist isKindOfClass:[NSDictionary class]])
and the plist object might be safely casted to NSDictionary. If no, something is wrong with the data or decription process.
The easiest way would be creating a category for NSDictionary like this:
NSDictionaryWithData.h:
#interface NSDictionary (NSDictionaryWithData)
+ (id)dictionaryWithData:(NSData *)data;
- (id)initWithData:(NSData *)data;
#end
NSDictionaryWithData.m:
#implementation NSDictionary (NSDictionaryWithData)
+ (id)dictionaryWithData:(NSData *)data
{
return [[[NSDictionary alloc] initWithData:data] autorelease];
}
- (id)initWithData:(NSData *)data
{
self = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:data
mutabilityOption:NSPropertyListImmutable
format:NULL
errorDescription:nil];
return [self retain];
}
#end
Usage:
NSDictionary* myDict = [[NSDictionary alloc] initWithData:decryptedData];
One of the possible reasons dictionaryWithData doesn't exist is a property list is not necessarily a dictionary at the root level. It could equally be an NSArray.
Here is my take on a solution: a category that utilises NSPropertyListSerialization
Features
Silently discards data that contains arrays at the root level.
Checks which method to use ( propertyListFromData:mutabilityOption:format:errorDescription: is depreciated )
NSMutableDictionary also supported
Note - this takes an unorthodox approach of wrapping a class factory method with an init method. This is for efficiency - most of the time you will be using the factory method, which just wraps NSPropertyListSerialization, which internally invokes alloc/init/autorelease to return an appropriate object.
NSDictionary+DictionaryWithData.h
#import <Foundation/Foundation.h>
#interface NSDictionary (DictionaryWithData)
+ (id)dictionaryWithData:(NSData *)data;
- (id)initWithData:(NSData *)data;
#end
NSDictionary+DictionaryWithData.m
#import "NSDictionary+DictionaryWithData.h"
#implementation NSDictionary (DictionaryWithData)
+(NSPropertyListMutabilityOptions) mutabilityOption {
return NSPropertyListImmutable;
}
+ (id)dictionaryWithData:(NSData *)data
{
static BOOL methodChecked = NO;
static BOOL use_propertyListWithData = NO;
if (!methodChecked) {
SEL sel = #selector(propertyListWithData:options:format:error:);
use_propertyListWithData = [[NSPropertyListSerialization class]
respondsToSelector:sel];
methodChecked = YES;
}
id result;
if (use_propertyListWithData) {
result = [NSPropertyListSerialization propertyListWithData:data
options:[self mutabilityOption]
format:nil
error:nil];
} else {
result = [NSPropertyListSerialization propertyListFromData:data
mutabilityOption:[self mutabilityOption]
format:NULL
errorDescription:nil];
}
return [result isKindOfClass:[NSDictionary class]] ? result : nil;
}
- (id)initWithData:(NSData *)data
{
id result = [[self class] dictionaryWithData:data];
self = result ? [self initWithDictionary:result ] : nil;
return self;
}
#end
#implementation NSMutableDictionary (DictionaryWithData)
+(NSPropertyListMutabilityOptions) mutabilityOption {
return NSPropertyListMutableContainersAndLeaves;
}
#end
Related
I am using a NSManagedObject to save some values, but not using entity for that. Because I want to not limit it to some entities. I tried using (Oist *)managedObject.entity, not working. How to use this managedObject to get theIt's like this:
Oist.h
#class File;
#interface Oist : NSManagedObject
#property (nonatomic, retain) NSSet *files;
#end
#interface Oist (CoreDataGeneratedAccessors)
- (void)addFilesObject:(File *)value;
File.h
#class Oist;
#interface File : NSManagedObject
#property (nonatomic, retain) Oist *gist;
#end
- (void)newManagedObjectWithClassName:(NSString *)className forRecords:(NSDictionary *)records
{
NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:className inManagedObjectContext:[[GPCoreDataController sharedInstance] backgroundManagedObjectContext]];
[records enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[self setValue:obj forKey:key forManagedObject:newManagedObject];
}];
}
- (void)setValue:(id)value forKey:(NSString *)key forManagedObject:(NSManagedObject *)managedObject
{
File *fileEntity = [NSEntityDescription insertNewObjectForEntityForName:#"File" inManagedObjectContext:[[GPCoreDataController sharedInstance] backgroundManagedObjectContext]];
if ([key isEqualToString:#"files"])
{
NSDictionary *files = (NSDictionary *)value;
for (NSString *key in [files allKeys]) {
NSDictionary *file = [files valueForKey:key];
NSLog(#"%#", file[#"filename"]);
[fileEntity setValue:file[#"filename"] forKey:#"filename"];
[fileEntity setValue:file[#"type"] forKey:#"type"];
}
} else if ([key isEqualToString:#"id"])
{
if ([managedObject.entity.name isKindOfClass:[Oist class]]) {
//here I want to use Oist's method - (void)addFilesObject:(File *)value; how to do that?
}
[managedObject setValue:[NSNumber numberWithInt:[value integerValue]] forKey:#"gistID"];
}
}
I may be misunderstanding your question, but it looks like you're hoping to cast a generically typed (NSManagedObject *) as a more specifically typed subclass.
The way to check if the pointer passed generically is in fact an instance of your subclass is this:
if ([managedObject isKindOfClass:[Oist self]]) {
// managedObject may safely be cast to an Oist*
}
You can do the cast without an assignment, like this:
[(Oist *)managedObject addFiles...];
Or, with proper parentheses, access a property (the dot operator has higher precedence than the cast, so you need to tell the compiler you want the cast first):
((Oist *)managedObject).entity
But I think that's less clear to the reader and potentially more keystrokes than an explicit assignment with a cast, which is what I'd recommend:
if ([managedObject isKindOfClass:[Oist self]]) {
Oist *oist = (Oist *)managedObject;
[oist addFilesObject:someFilesObject];
}
This question can be a duplicated one. But I was unable to find a proper answer for my issue.
I have following xml file downloading from a server.
<INCIDENTTYPES>
<INCIDENT FORMNAME="first" TEXT="First incident">
<TYPE>Type1</TYPE>
<TYPE>Type2</TYPE>
<TYPE>Type3 Tag</TYPE>
<TYPE>Type4 Tag</TYPE>
<LOCATION>Council</LOCATION>
<LOCATION>Domestic</LOCATION>
<LOCATION>Commercial</LOCATION>
</INCIDENT>
<INCIDENT FORMNAME="second" TEXT="Second incident">
<TYPE>Second type</TYPE>
<TYPE>Second/first type</TYPE>
<TYPE>Second3</TYPE>
<LOCATION>Council</LOCATION>
<LOCATION>Domestic</LOCATION>
<LOCATION>Commercial</LOCATION>
</INCIDENT>
</INCIDENTTYPES>
I am using RaptureXML to parse the xml file and parsing can be done as follows.
RXMLElement *rootXML = [RXMLElement elementFromXMLData:self.connectionData];
[rootXML iterate:#"INCIDENT" usingBlock: ^(RXMLElement *incidents) {
if([[incidents attribute:#"FORMNAME"] isEqualToString:#"first"]){
NSArray *listArray = [NSArray array];
listArray = [incidents children:#"TYPE"];
IncidentData *iData = [[IncidentData alloc] init];
[iData.type setValue:listArray forKey:#"type"];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:iData];
[self.userDefault setObject:data forKey:#"firstObject"];
}
}
The IncidentData class as follows.
#import "IncidentData.h"
#implementation IncidentData
- (id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
//self.type is an array
self.type = [aDecoder decodeObjectForKey:#"type"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder{
[aCoder encodeObject:self.type forKey:#"type"];
}
#end
I am trying to load data as following.
NSData *dd = [self.userDefault objectForKey:#"firstObject"];
IncidentData *items = (IncidentData *)[NSKeyedUnarchiver unarchiveObjectWithData:dd];
NSArray *typeArray = [items.type objectAtIndex:0];
NSLog(#"Incident type : %#", typeArray);
But typeArray is null.
Summary: I am trying to parse a remote xml file and save the values on to the disk and use later stage of the app.
Can you please help me on this ?
RaptureXML, RXMLElement might not support NSCoding.
try substituting:
listArray = [incidents children:#"TYPE"];
with:
listArray = [[incidents children:#"TYPE"] valueForKeyPath:#"text"];
Basically I am using some open source code called OrderedDictionary that is derived from NSMutableDictionary. Then I want to save the ordered dictionary data to NSUserDefaults by adding encode and decode method to the OrderedDictionary class. However, I realized the encode and decode methods are not being called, as a result, the decoded dictionary is no longer ordered. Below is my code:
#interface OrderedDictionary : NSMutableDictionary <NSCopying, NSCoding>
{
NSMutableDictionary *dictionary;
NSMutableArray *array;
}
In the implementation file:
/**
* encode the object
**/
- (void)encodeWithCoder:(NSCoder *)coder
{
[super encodeWithCoder:coder];
[coder encodeObject:dictionary forKey:#"dictionary"];
[coder encodeObject:array forKey:#"array"];
}
/**
* decode the object
*/
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self != nil)
{
dictionary = [coder decodeObjectForKey:#"dictionary"];
array = [coder decodeObjectForKey:#"array"];
}
return self;
}
Quick example code for using this:
dictionary = [[OrderedDictionary alloc] init];
[dictionary setObject:#"one" forKey:#"two"];
[dictionary setObject:#"what" forKey:#"what"];
[dictionary setObject:#"7" forKey:#"7"];
NSLog(#"Final contents of the dictionary: %#", dictionary);
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"myDictionary"] == nil)
{
[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:dictionary]
forKey:#"myDictionary"];
}
else
{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *savedDictionary = [currentDefaults objectForKey:#"myDictionary"];
if (savedDictionary != nil)
{
OrderedDictionary *oldData = [NSKeyedUnarchiver unarchiveObjectWithData:savedDictionary];
if (oldData != nil)
{
NSLog(#"Final contents of the retrieved: %#", oldData);
}
}
}
The thing is, the final retrievedDictionary does NOT have the original data order and the encode and decode methods are not called at all.
Thanks for any help in advance! :)
There's an NSObject method called -classForCoder that you need to override in OrderedDictionary. From the docs:
classForCoder
Overridden by subclasses to substitute a class other than its own during coding.
-(Class)classForCoder
Return Value
The class to substitute for the receiver's own class during coding.
Discussion
This method is invoked by NSCoder. NSObject’s
implementation returns the receiver’s class. The private subclasses of
a class cluster substitute the name of their public superclass when
being archived.
That last line is the key. So, in OrderedDictionary.m:
- (Class)classForCoder
{
return [self class]; // Instead of NSMutableDictionary
}
Also, if you're not using ARC, make sure you retain the objects coming back from -decodeObjectForKey. As rob mayoff mentions below, you shouldn't call [super initWithCoder:] (or [super encodeWithCoder:'). I also changed the key strings to avoid collisions.
- (id)initWithCoder:(NSCoder *)coder
{
if (self != nil)
{
dictionary = [[coder decodeObjectForKey:#"OrderedDictionary_dictionary"] retain];
array = [[coder decodeObjectForKey:#"OrderedDictionary_array"] retain];
}
return self;
}
You are possibly creating a new OrderedDictionary with the wrong initializer, initWithDictionary:. Try this instead:
OrderedDictionary *oldData = [NSKeyedUnarchiver unarchiveObjectWithData: savedDictionary];
if (oldData != nil)
{
NSLog(#"Final contents of the retrieved: %#", oldData);
}
Make sure initWithDictionary: expects OrderedDictionary as an argument. My guess is that it expects an NSDictionary.
Edit: the code to save the defaults should include something like this:
OrderedDictionary *myDict = ...;
NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myDict];
[[NSUserDefaults standardDefaults] setObject: data forKey: #"myDictionary"];
Ok, I'm trying to avoid global variables, so I read up on singleton classes.
This is a try to set and read a mutable array, but the result is null.
//Content.h
#interface Content : NSObject {
NSMutableArray *contentArray;
}
+ (Content *) sharedInstance;
- (NSMutableArray *) getArray;
- (void) addArray:(NSMutableArray *)mutableArray;
#end
.
//Content.m
#implementation Content
static Content *_sharedInstance;
+ (Content *) sharedInstance
{
if (!_sharedInstance)
{
_sharedInstance = [[Content alloc] init];
}
return _sharedInstance;
}
- (NSMutableArray *) getArray{
return contentArray;
}
- (void) addArray:(NSMutableArray *)mutableArray{
[contentArray addObject:mutableArray];
}
#end
And in a ViewController I added #import "Content.h", where I try to call this:
NSMutableArray *mArray = [NSMutableArray arrayWithObjects:#"test",#"foo",#"bar",nil];
Content *content = [Content sharedInstance];
[content addArray:mArray];
NSLog(#"contentArray: %#", [content getArray]);
You need to alloc and init the array first. Personally I'd do it in the init method of the content class like so:
-(id)init{
if(self = [super init]){
…the rest of your init code…
contentArray = [[NSMutableArray alloc] init];
}
return self;
}
You never actually alloc/initialise the contentArray array.
I have a feeling that this is stupid question, but I'll ask anyway...
I have a collection of NSDictionary objects whose key/value pairs correspond to a custom class I've created, call it MyClass. Is there an easy or "best practice" method for me to basically do something like MyClass * instance = [map NSDictionary properties to MyClass ];? I have a feeling I need to do something with NSCoding or NSKeyedUnarchiver, but rather than stumble through it on my own, I figure someone out there might be able to point me in the right direction.
The -setValuesForKeysWithDictionary: method, along with -dictionaryWithValuesForKeys:, is what you want to use.
Example:
// In your custom class
+ (id)customClassWithProperties:(NSDictionary *)properties {
return [[[self alloc] initWithProperties:properties] autorelease];
}
- (id)initWithProperties:(NSDictionary *)properties {
if (self = [self init]) {
[self setValuesForKeysWithDictionary:properties];
}
return self;
}
// ...and to easily derive the dictionary
NSDictionary *properties = [anObject dictionaryWithValuesForKeys:[anObject allKeys]];
There is no allKeys on NSObject. You'll need to create an extra category on NSObject like below:
NSObject+PropertyArray.h
#interface NSObject (PropertyArray)
- (NSArray *) allKeys;
#end
NSObject+PropertyArray.m
#import <objc/runtime.h>
#implementation NSObject (PropertyArray)
- (NSArray *) allKeys {
Class clazz = [self class];
u_int count;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++) {
const char* propertyName = property_getName(properties[i]);
[propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
}
free(properties);
return [NSArray arrayWithArray:propertyArray];
}
#end
Example:
#import "NSObject+PropertyArray.h"
...
MyObject *obj = [[MyObject alloc] init];
obj.a = #"Hello A"; //setting some values to attributes
obj.b = #"Hello B";
//dictionaryWithValuesForKeys requires keys in NSArray. You can now
//construct such NSArray using `allKeys` from NSObject(PropertyArray) category
NSDictionary *objDict = [obj dictionaryWithValuesForKeys:[obj allKeys]];
//Resurrect MyObject from NSDictionary using setValuesForKeysWithDictionary
MyObject *objResur = [[MyObject alloc] init];
[objResur setValuesForKeysWithDictionary:objDict];
Assuming that your class conforms to the Key-Value Coding protocol, you could use the following: (defined as a category on NSDictionary for convenience):
// myNSDictionaryCategory.h:
#interface NSDictionary (myCategory)
- (void)mapPropertiesToObject:(id)instance
#end
// myNSDictionaryCategory.m:
- (void)mapPropertiesToObject:(id)instance
{
for (NSString * propertyKey in [self allKeys])
{
[instance setValue:[self objectForKey:propertyKey]
forKey:propertyKey];
}
}
And here's how you would use it:
#import "myNSDictionaryCategory.h"
//...
[someDictionary mapPropertiesToObject:someObject];
If your doing this sort of thing chances are your dealing with JSON and you should probably have a look at Mantle
https://github.com/Mantle/Mantle
You will then get a convenient method dictionaryValue
[anObject dictionaryValue];
Just add category for NSObject for getting dictionaryRepresentation from your custom objects (in my case using in JSON serialization only):
// NSObject+JSONSerialize.h
#import <Foundation/Foundation.h>
#interface NSObject(JSONSerialize)
- (NSDictionary *)dictionaryRepresentation;
#end
// NSObject+JSONSerialize.m
#import "NSObject+JSONSerialize.h"
#import <objc/runtime.h>
#implementation NSObject(JSONSerialize)
+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary {
return [[self alloc] initWithDictionary:aDictionary];
}
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary {
aDictionary = [aDictionary clean];
self.isReady = NO;
for (NSString* propName in [self allPropertyNames]) {
[self setValue:aDictionary[propName] forKey:propName];
}
//You can add there some custom properties with wrong names like "id"
//[self setValue:aDictionary[#"id"] forKeyPath:#"objectID"];
self.isReady = YES;
return self;
}
- (NSDictionary *)dictionaryRepresentation {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSArray *propertyNames = [self allPropertyNames];
id object;
for (NSString *key in propertyNames) {
object = [self valueForKey:key];
if (object) {
[result setObject:object forKey:key];
}
}
return result;
}
- (NSArray *)allPropertyNames {
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *rv = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
//You can add there some custom properties with wrong names like "id"
//[rv addObject:#"objectID"];
//Example use inside initWithDictionary:
//[self setValue:aDictionary[#"id"] forKeyPath:#"objectID"];
free(properties);
return rv;
}
#end
Also, you can see that my solution will not work with custom objects with nested objects or arrays. For Arrays - just change the lines of code in dictionaryRepresentation method:
if (object) {
if ([object isKindOfClass:[NSArray class]]) {
#autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
for (id item in (NSArray *)object) {
[array addObject:[item dictionaryRepresentation]];
}
[result setObject:array forKey:key];
}
} else {
[result setObject:object forKey:key];
}
}