I have NSData that I'm loading from a filePath. The data is then getting passed around and I need the filePath for where the data came from.
What's the easiest way to do this short of passing the filePath around as an extra parameter?
Wrap both in a container object that represents the file, e.g.
#interface File : NSObject
#property (strong) NSData *fileContents;
#property (copy) NSString *filePath;
// perhaps, even a nice constructor to fill these properties
- (id)initWithFilePath:(NSString *)filePath;
#end
While you should probably create a separate struct or class that contains the data you need, you can also add an association to NSData.
You can even do it as a category on NSData for convenience... assuming you are using a string... use NSURL if using a URL... that way, none of your other code needs to change... you can still use your NSData as before, with the newly added property.
NOTE: There are lots of reasons to use or not use categories. I assume you will make the choice best for your code situation, and defer the wars over good/bad use of categories to others.
Try something like this...
NSData+AssociatedFilePath.h
#import <Foundation/Foundation.h>
#interface NSData (AssociatedFilePath)
#property (nonatomic, strong) NSString *filePath;
#end
NSData+AssociatedFilePath.m
#import <objc/runtime.h>
static char kFilePath;
#implementation NSData (AssociatedFilePath)
- (void)setFilePath:(NSString*)filePath
{
objc_setAssociatedObject(self, &kFilePath, filePath, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString*)filePath
{
return objc_getAssociatedObject(self, &kFilePath);
}
#end
Now, in your code, you can do this...
NSData *data = // whatever you do to create the data object..
data.filePath = someFilePath;
Whenever you want the file path of a NSData object...
NSString *filePath = myNSDataObject.filePath;
Since it is a category, you can use this on any NSData object, and if a file path was never set, it will just return nil.
This is a general solution for any time you need to add behavior to an existing class (of course, following appropriate practices for when to best use categories).
Related
I create NSData from a class instance like this:
NSData* data = [NSKeyedArchiver archivedDataWithRootObject:db];
[data writeToFile:#"/Users/.../db.data" atomically:true];
Then I need to load this instance:
NSURL* dbUrl = [[NSBundle mainBundle]URLForResource:#"db" withExtension:#"data"];
NSData* dbData = [NSData dataWithContentsOfURL:dbUrl];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.db = [NSKeyedUnarchiver unarchiveObjectWithData:dbData];
});
This loading lasts cca. 6 seconds for ~70MB file size. It is very slow! I tried to separate db instance into 2 different parts (~35MB) and load them like this:
self.db = [[Database alloc]init];
NSURL* dbUrl1 = [[NSBundle mainBundle]URLForResource:#"db1" withExtension:#"data"];
NSData* dbData1 = [NSData dataWithContentsOfURL:dbUrl1];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.db.nodes = [NSKeyedUnarchiver unarchiveObjectWithData:dbData1];
});
NSURL* dbUrl2 = [[NSBundle mainBundle]URLForResource:#"db2" withExtension:#"data"];
NSData* dbData2 = [NSData dataWithContentsOfURL:dbUrl2];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
self.db.trips = [NSKeyedUnarchiver unarchiveObjectWithData:dbData2];
});
With this way loading lasts cca. 3 seconds (2x faster). Do you know another ways to load it faster?
EDIT 1:
I'm not sure that it can help, but archived object has two NSMutableArray instances. The first one contains a lot of Node objects:
#interface DatabaseNode : NSObject <NSCoding>
#property (assign, nonatomic) NSInteger index;
#property (strong, nonatomic) NSString* name;
#property (assign, nonatomic) double lat;
#property (assign, nonatomic) double lon;
#end
And the second one contains a lot of Trip objects:
#interface DatabaseTrip : NSObject <NSCoding>
#property (assign, nonatomic) NSInteger index;
#property (strong, nonatomic) NSString* name;
#property (assign, nonatomic) NSInteger service;
#property (strong, nonatomic) NSMutableArray* departureTimes; //Contains NSDate
#end
Given that you need all the data in memory from the beginning of your app's session, you're going to have to go with a solution other than NSKeyedArchiver. NSKA is designed to archive complex object graphs and, thus, is optimized to solve a different problem.
In your case, I'd suggest a build phase that writes the static data to a file format that can be mapped into memory without parsing. Then, at runtime, map the file and have a pass that turns it into a minimal encapsulation in Foundation types.
For Strings, a string table like solution will work well enough. Dates can be a little bit trickier, maybe. I'd test to see if the time interval based creation methods are fast. If they are, then you can just store your dates as arrays (not NSArrays, but flat C arrays of NSTimeIntervals) in the mapped file and do a fixup pass.
Of course, you may also skip the fixup of some subset of data based on the access patterns in your app. If your users don't really look at all the dates at once, then there is no need to fix 'em up on load, but they can be created on demand.
I need to save many NSMutableArray with my custom objects and I want to know what is the best way to do this.
Maybe NSUserDefaults is not the best way to do it.
What should I use?
If your array contains non-plist objects, then you cannot use NSUserDefaults without first encoding the array.
The value parameter can be only property list objects: NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary. For NSArray and NSDictionary objects, their contents must be property list objects.
You'll want to encode it using NSKeyedArchiver. This will give you an NSData object that you can then store in NSUserDefaults, or write it to file through NSKeyedArchiver itself.
All you have to do is conform to NSCoding in your custom object, and override initWithCoder: to initialise your object when it's loaded and encodeWithCoder: to encode your variables when it gets encoded. For example, your custom object will look something like this:
#interface customArrayObject : NSObject <NSCoding>
#property (nonatomic) NSString* foo;
#property (nonatomic) NSInteger bar;
#end
#implementation customArrayObject
-(instancetype) initWithCoder:(NSCoder *)aDecoder { // decode variables
if (self = [super init]) {
_foo = [aDecoder decodeObjectForKey:#"foo"];
_bar = [aDecoder decodeIntegerForKey:#"bar"];
}
return self;
}
-(void) encodeWithCoder:(NSCoder *)aCoder { // encode variables
[aCoder encodeObject:_foo forKey:#"foo"];
[aCoder encodeInteger:_bar forKey:#"bar"];
}
#end
It's also worth noting that NSUserDefaults is used to store user preferences, and therefore if your array contains data that isn't in any way to do with a user preference, you shouldn't be using NSUserDefaults - you should be writing it to disk yourself.
Writing your array to disk is actually a lot more trivial than it sounds, you can use the archiveRootObject:toFile: method on NSKeyedArchiver. For example, this will write your custom array to the documents directory:
// Gets the documents directory path
NSString* documentsPath = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES)[0];
// Archive and save the file to foo.dat in the documents directory. Returns whether the operation was successful.
BOOL success = [NSKeyedArchiver archiveRootObject:customArray toFile:[NSString stringWithFormat:#"%#/%#", documentsPath, #"foo.dat"]]
However, it is also worth noting that this flat (yes/no) as to whether the operation was successful isn't that great when it comes to error handling. If you want to implement custom error handling, then you'll want to first encode the object using NSKeyedArchiver's archivedDataWithRootObject: method, and then NSData's writeToFile:options:error: method.
Let us say I have an NSObject Class Person.
#interface Person : NSObject
#property NSString *id;
#property NSString *name;
#property Address *billingAddress;
#end
#interface Address : NSObject
#property NSString *lane;
#property NSString *country;
#property NSString *zip;
#end
Now when I fetch the response from a URL, the response is in the form:
{
"response":
{
"Persons":[{"id":"2232","name":"John","Address":{"lane":"Adelaide Avenue","country":"Canada","zip":"45503"}}{"id":"3422","name":"Jane","Address":{"lane":"Victoria Avenue","country":"Australia","zip":"34903"}}]
}
}
I want to parse the response directly into objects without having to write a method to read and assign objects from NSDictionary. Is there are no objects to parse directly from the response to Object based on the Object parameters similar to "GSon" in Android.
EDIT:
I have used the below code to have generic class that does the job for strings without having to know about the object itself.
for (NSString *key in [dct allKeys]) {
if ([cat respondsToSelector:NSSelectorFromString(key)]) {
[cat setValue:[dct valueForKey:key] forKey:key];
}
}
There is no such magic, not even in Android's GSon!!!
Some where down the line you need to write code for converting JSON to your object.
You may create a generic class, or a method (just once) to convert all dictionary values to your object.
After some digging I did get a JSON framework that does exactly what I wanted - JSONModel.
We just need to specify Models and relationships and all the logic for converting JSON response to the models is handled by the framework. Very handy.
Basic usage :
Consider you have a JSON response like
{"id":"10", "country":"Germany", "dialCode": 49, "isInEurope":true}
The corresponding model will be
#import "JSONModel.h"
#interface CountryModel : JSONMode
#property (assign, nonatomic) int id;
#property (strong, nonatomic) NSString* country;
#property (strong, nonatomic) NSString* dialCode;
#property (assign, nonatomic) BOOL isInEurope;
#end
We don't need to write additional code in the .m file to parse and assign values to the variables. Now to initialise the model from the response we just need to do the below
NSString* json = (fetch JSON here)
NSError* err = nil;
CountryModel* country = [[CountryModel alloc] initWithString:json error:&err];
The works well with complex data structures as well.
Short Version:
I define a property with (nonatomic, retain) and assumed that the property would be retained. But unless I call retain when assigning a dictionary to the property, The app crashes with an EXEC BAD ACCESS error.
Long Version:
I have a singleton which has a dictionary. The header is defined like this
#interface BRManager : NSObject {
}
#property (nonatomic, retain) NSMutableDictionary *gameState;
+ (id)sharedManager;
- (void) saveGameState;
#end
In the implementation file, I have a method that's called in the init. This method loads a plist form the bundle and makes a copy of it in the users documents folder on the device.
- (void) loadGameState
{
NSFileManager *fileManger=[NSFileManager defaultManager];
NSError *error;
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *destinationPath= [doumentDirectoryPath stringByAppendingPathComponent:#"gameState.plist"];
NSLog(#"plist path %#",destinationPath);
if (![fileManger fileExistsAtPath:destinationPath]){
NSString *sourcePath=[[[NSBundle mainBundle] resourcePath]stringByAppendingPathComponent:#"gameStateTemplate.plist"];
[fileManger copyItemAtPath:sourcePath toPath:destinationPath error:&error];
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
}else{
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:destinationPath];
}
}
Now here's how I thought this should work. In the header I define the gameState property with (nonatomic, retain). I assumed (probably incorrectly) that 'retain' meant that the gameState dictionary would be retained. However, I have another method in my singleton (saveGameState) that get's called when the AppDelegate -> 'applicationWillResignActive'.
- (void) saveGameState
{
NSArray *pathsArray = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask,YES);
NSString *doumentDirectoryPath=[pathsArray objectAtIndex:0];
NSString *plistPath = [doumentDirectoryPath stringByAppendingPathComponent:#"gameState.plist"];
[gameState writeToFile:plistPath atomically:YES];
}
This throws an EXEC BAD ACCESS error on gameState. If I modify loadGameState to retain the gameState dictionary, everything works as it should. eg:
gameState = [[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath] retain];
I'm guessing this is the correct behaviour, but why? Does (nonatomic, retain) not mean what I think it means, or is something else at play here?
I've not really grok'd memory management yet, so I stumble on this stuff all the time.
You must use the accessor:
self.gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
or (is equivalent to):
[self setGameState:[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]];
instead of
gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath];
which only sets the ivar without any notion of property.
Where do you declare gameState as an ivar? I'm presuming you do so in the implementation.
The real problem is that in your implementation, you access gameState directly and don't actually invoke the property you've declared. To do so you must send self the appropriate message:
[self gameState]; // invokes the synthesized getter
[self setGameState:[NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]]; // invokes the synthesized setter -- solves your problem
or
whatever = self.gameState; // invokes the getter
self.gameState = [NSMutableDictionary dictionaryWithContentsOfFile:sourcePath]; // invokes the synthesized setter -- solves your problem
Make sure you get around to groking that memory management literature... this is a very basic question which, according to the strict rules of StackOverflow, I shouldn't be answering. Good luck!
I have developed a small app that stores locally in iOS through archiving an array of custom objects containing:
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *dateCreated;
#property (nonatomic, copy) NSString *desc;
#property (nonatomic, copy) NSString *url;
I want to sync said archive using iCloud and I believe the recommended mechanism is through a UIDocument subclass.
All UIDocument examples I found utlilized a single instance with 1 single NSString, so I am a little confused how to go about syncing a whole array of custom objects but utilizing UIDocument (like I do today locally through NSCoding).
Should I create an array of UIDocument objects containing the properties listed above, should I create an instance of UIDocument containing 1 instance of the data object described above and then create an array containing all the instances, or should 1 single UIDocument contain the complete array of custom objects ?
I have done some research but I am still confused.
In the end I would need to sync just 1 file containing an array of said custom objects.
Thanks in advance for your help
What I have today is a custom class as described above with 4 strings called Snippet and in my Root view Controller I have an NSMutableArray called list where I add each new instance of that Snippet Class.
self.list = [[NSMutableArray alloc] init];
Snippet *newEntry = [[Snippet alloc] init];
[self.list addObject:newEntry];
Should I create an UI Document subclass that owns the array of custom objects ?
The example in the docs does indeed show a UIDocument subclass that just has one string, but it returns a NSData from -contentsForType:error:. You can store as many objects as you like in an NSData using an NSKeyedArchiver. Read Serializing Objects to learn how to encode objects using NSKeyedArchiver (and keep reading to learn how to get them back!).
Using your properties as an example...
#interface MyDocument : UIDocument
{
}
#property (nonatomic, copy) NSString *name;
#property (nonatomic, copy) NSString *dateCreated;
#property (nonatomic, copy) NSString *desc;
#property (nonatomic, copy) NSString *url;
#end
#implementation MyDocument
//...
- (id)contentsForType:(NSString *)typeName error:(NSError **)outError
{
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:name forKey:#"name"];
[archiver encodeObject:dateCreated forKey:#"created"];
[archiver encodeObject:desc forKey:#"desc"];
[archiver encodeObject:url forKey:#"url"];
[archiver finishEncoding];
// release archiver if you're not using ARC
return data;
}
#end;
WARNING: I haven't compiled the code above, so no guarantees. This should serve as an example to illustrate using an archiver to store multiple objects in a single data object which you can return as your document's content.