I just finished the second tutorial for iOS development. When I add a new sighting, it works fine and turns up in the list of sightings. However, when I close the iOS simulator and reopen it, only the first entry is there (it is added manually). Should there be internal memory in the iOS simulator even after it is closed and reopened?
There seems to be some memory as if I add my contact info to the contacts, it is still there when I reopen it.
If so, how do I make sure that my array in the DataController file is similarly stored on the simulator/phone so it doesn't clear itself every time I reopen the simulator?
Thanks
You need to use persistent storage if you want to save data between sessions. Options include:
You can use plist files. For example if you have NSArray *array, you can save that to a plist using writeToFile:
[array writeToFile:filename atomically:YES];
You can then read this array with:
NSArray *array = [NSArray arrayWithContentsOfFile:filename];
This technique only works with standard NSString, NSNumber, etc., objects, not custom objects like BirdSighting, though.
For custom objects like BirdSighting, you could use NSKeyedArchiver and NSKeyedUnarchiver. By the way, these are not only generally useful classes for saving data for small data sets like this, but given that it features prominently in the new iOS 6 state preservation features, it's worth familiarizing yourself with this pattern.
You can use NSUserDefaults. This is really better suited for app settings and defaults, but theoretically could be used for saving data, too.
You can use CoreData. This is the preferred iOS technology for object persistence. It's a powerful and well engineered framework (though a tad complicated) and well suited if you're dealing with more significant amounts of data.
You can use SQLite, too. See this Ray Wenderlich article on using SQLite. And once you start using SQLite, you can consider using FMDB to simplify your coding effort.
If you wanted, for example, to use the second approach, NSKeyedArchiver and NSKeyedUnarchiver, the first thing is that you might want to do is make BirdSighting conform to NSCoding, by altering the #interface declaration in BirdSighting.h to say:
#interface BirdSighting : NSObject <NSCoding>
Second, you have to write the two NSCoding methods, initWithCoder and encodeWithCoder, in BirdSighting.m that define what properties can to be loaded/saved for this object:
- (NSArray *)keysForEncoding;
{
return #[#"name", #"location", #"date"];
}
- (id) initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self) {
for (NSString *key in [self keysForEncoding])
{
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
for (NSString *key in self.keysForEncoding)
{
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
Your BirdSighting can now be loaded and saved with NSKeyedUnarchiver and NSKeyedArchiver, respectively.
So, focusing on the loading of the sightings, you have to (a) tell BirdSightingDataController.m what file to look for; and (b) instruct it to read that file during initialization:
- (NSString *)filename
{
NSString *docsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
return [docsPath stringByAppendingPathComponent:#"BirdSightings"];
}
- (void)initializeDefaultDataList
{
NSString *filename = [self filename];
self.masterBirdSightingList = nil;
if ([[NSFileManager defaultManager] fileExistsAtPath:filename])
{
self.masterBirdSightingList = [NSKeyedUnarchiver unarchiveObjectWithFile:filename];
}
if (!self.masterBirdSightingList)
{
NSMutableArray *sightingList = [[NSMutableArray alloc] init];
self.masterBirdSightingList = sightingList;
BirdSighting *sighting;
NSDate *today = [NSDate date];
sighting = [[BirdSighting alloc] initWithName:#"Pigeon" location:#"Everywhere" date:today];
[self addBirdSightingWithSighting:sighting];
}
}
The BirdSightingDataController.m can also define a method to save the data:
- (BOOL)save
{
return [NSKeyedArchiver archiveRootObject:self.masterBirdSightingList toFile:[self filename]];
}
You can now, for example, call this save method whenever you add a sighting, e.g.:
- (void)addBirdSightingWithSighting:(BirdSighting *)sighting
{
[self.masterBirdSightingList addObject:sighting];
[self save];
}
Personally, rather than saving it every time a user does any change in the app, I might rather just have my app delegate save it when the app goes into background or terminates (but that requires further changes, so I won't go into that now).
But hopefully this code illustrates how you can use NSKeyArchiver and NSKeyUnarchiver to save and load data. And clearly, for more complicated scenarios, I would generally encourage you to consider Core Data. But for small data sets, like this, this archiver pattern can be useful (and as I said, is worth being familiar with because the basic technology is also used in iOS 6 app state restoration).
Related
I'm working on a bible iPhone app. Here are the basics of what I'm doing so far:
My XMLParser parses an xml file and creates a Bible object
Specifically, the xml is stored in each Chapter object.
Once parsing is done, the viewController grabs a Chapter from the Bible and displays its innerHtml in a UIWebview.
This works, but whenever I try to access the Bible object outside of -(void)viewDidLoad; it either gives me a BAD_ACCESS error, or the results for what I'm asking for is unreadable. I think this is a memory management problem..
Here's what I'm doing in the viewController
- (void)viewDidLoad
{
[super viewDidLoad];
//Create parser and XML data object.
//Then, parse that data
finalBible = [[Bible alloc]init];
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"KJV" ofType:#"html"];
NSData *myData = [NSData dataWithContentsOfFile:filePath];
//INT I WANTED TO USE TO ITERATE WHEN BUTTON WAS PRESSED TO ADVANCE TO NXT CHAPTER
chapToShow = 2;
parser = [[XMLParser alloc] init];
nsParser = [[NSXMLParser alloc] initWithData:myData];
//set delegate for NSXMLParser
[nsParser setDelegate:parser];
//PARSE THE XML FILE AND BUILD BIBLE OBJECT - PARSED OK!!
if ([nsParser parse]) {
NSLog(#"Parsed with no errors!! :)");
}else{
NSLog(#"Error parsing document");
}
//IT SEEMS MY PROBLEMS MAY BE IN THIS TRADE OFF.
//I WANT TO STORE THE PARSER'S BIBLE IN THIS NEW BIBLE OBJECT.
finalBible = [parser getBible];
//Test querying bible via pullVerse method - IT WORKS!!
NSLog(#"%#",[finalBible pullVerse:#"65.021.000"]);
NSString *firstChap = [[[[finalBible getTestament:0]getBook:#"Genesis"]getChapterWithInt:3]getInnerHtml];
//Try and load Genesis 1 - THIS WORKS!!
NSLog(#"...Loading Genesis 1...");
[bibleView loadHTMLString:firstChap baseURL:nil];
//LOADING THE VERSION WORKS HERE!!
NSLog(#"Version = %#", [finalBible getVersion]);
}
- (IBAction)buttonPressed:(id)sender {
NSLog(#"Now reading chapter %d", chapToShow);
//HERE I'M TRYING TO GET THE BIBLE VERSION BUT THE APP CRASHES AS A RESULT
NSLog(#"Testing the bible: Version = %# \n OK", [finalBible getVersion]);
//NOTE: I've even tried [[parser getBible] getVersion] and it still doesn't work.
// I don't release the parser till the view's dealloc method, so I'm not sure why I
// can't access it here...
}
Of course, I'd be happy to post any other code. I just didn't want to over-stuff the page with code, so I pasted only where I think the problem lies, or at least where it's occurring.
You allocate finalBible, but then you set finalBible = [parser getBible] so the previous allocation is pointless. Also, it appears as though [parser getBible] returns an autoreleased object, so you should call [[parser getBible] retain] to make sure it does not leave memory.
This is exactly what you need to do in your .h file add this line:
#property(nonatomic, retain) Bible finalBible;
Then in your .m file add this line at the top:
//This generates the methods (get, set) for your instance variable
#synthesize finalBible;
Then drop this line:
finalBible = [[Bible] alloc]init];
This is because if you keep it, there will be a memory leak right here:
finalBible = [parser getBible];
Because now you are pointing to a new memory location, and the previous memory location had an object with a retain count of 1 provided by alloc init, and since there will no longer be any reference to this object it will never be released causing a memory leak.
Although if you use this line:
self.finalBible = [parser getBible];
Because that uses the setter, a setter releases the previous value and retains the new one, so there would not be a memory leak, but it would still be pointless to allocate that object.
Since getBible does not have the new, alloc or init keyword it should return an autoreleased object, which is why the object is released in the next run loop (not guaranteed but most likely), which is why you cannot access it outside of the viewDidload() method.
You can resolve this problem by making "finalBible" variable a property of the class using
#property(nonatomic, retain) Bible finalBible; //this is in the .h file
#synthesis finalBible; // at the top of .m file
All the reference to "finalBible" should be made as "self.finalBible"
I would like to have a "permanent" NSDictionary in my app, in which from the beginning of the app launch, I can get access to the elements and even after I kill the app and start it again.
This NSDictionary needs to be stored tightly with the app. One way would be to just to create the NSDictionary from a Web Service data every time the app launches, then create a singleton class that would represent this NSDictionary, but I don't think this is good.
The NSDictionary will approximately hold 10-20 objects, where the key would be a NSString or NSDate and the value would be a NSArray. The NSArray would have a maximum of approximately 50 entries in it (on average probably there will only be 5-25 entries).
I am planning to use this NSDictionary as a part of a calculation that I am doing inside the delegate locationManager:didUpdateToLocation:fromLocation: each time a user moves every 50-100 meters.
Suggestions are appreciated on the best way I could do this.
Just having the NSDictionary in a singleton will not keep it between app launches, you'll need to save it to disk, and then read it from the disk when the app starts.
If you have no custom objects (subclasses you've created) in the NSDictionary, or in the NSArrays or non at all, you can use this method to save the NSDictionary is:
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
and to open the dictionary from disk:
- (id)initWithContentsOfFile:(NSString *)path
However if you do have custom objects they will need to conform to the NSCoding protocol. And you'll have to use 2 different methods to save and open it:
NSCoding is a protocol, so in your header you need to add it on the end of the interface line:
#interface myClassName : NSObject <NSCoding> {
(where the only thing you should add is <NSCoding>)
Then in your implementation of your subclass, you need to add the following methods:
- (id)initWithCoder:(NSCoder *)decoder
and:
- (void)encodeWithCoder:(NSCoder *)encoder
The initWithCoder: method gets called when you want to unarchive(/open) your NSDictionary (which somewhere contains this class)
encodeWithCoder: is what gets called when your NSDictionary is archived(/saved).
You don't call either of these yourself. You need to add code in them:
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super initWithCoder:decoder])) {
aProperty = [[decoder decodeObjectForKey:#"aProperty"] retain];
anotherProperty = [[decoder decodeObjectForKey:#"anotherProperty"] retain];
aFloat = [decoder decodeFloatForKey:#"aFloat"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:aProperty forKey:#"aProperty"];
[encoder encodeObject:anotherProperty forKey:#"anotherProperty"];
[encoder encodeFloat:aFloat forKey:#"aFloat"];
}
You need to have similar lines for each value you want to store (generally all the properties, [and instance variables] your class has). Note the how the float line is different to the others.
The keys can be any string you want, as long each property has it's own unique key and that they match between the two methods. I personally use the name of the property as it's just easier to understand.
when you actually want to save your NSDictionary you use:
[NSKeyedArchiver archiveRootObject:myDictionary toFile:pathToMyDictionary];
and to open the dictionary:
NSDictionary *myDictionary = [NSKeyedUnarchiver unarchiveObjectWithFile:pathToMyDictionary];
(depending on your code you may need to retain myDictionary)
To get the path to your dictionary (for both saving and opening) do:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pathToMyDictionary = [documentsDirectory stringByAppendingPathComponent:#"myDictionary.dat"];
Hope that helps, if you have any more questions about this answer, just comment :)
It sounds like you want NSUserDefaults. That stores app preferences but can be used other bits of data as well. It is essentially a dedicated, singleton dictionary that is universally accessible and is automatically saved.
You can easily generate an NSDictionary from a plist file using [NSDictionary dictionaryWithContentsOfFile:foo]
If appropriate, the plist could be included in your app bundle, or you could grab the data from a web service on first launch and write it out to a plist that the dictionary is loaded from in the future.
I've been developing an iPhone app for the last few months. Recently I wanted to up performance and cache a few of the images that are used in the UI. The images are downloaded randomly from the web by the user so I can't add specific images to the project. I'm also already using NSUserDefaults to save other info within the app.
So now I'm attempting to save a dictionary of UIImages to my NSUserDefaults object and get...
-[UIImage encodeWithCoder:]: unrecognized selector sent to instance
I then decided to subclass UIImage with a class named UISaveableImage and implement NSCoding. So now I'm at...
#implementation UISaveableImage
-(void)encodeWithCoder:(NSCoder *)encoder
{
[encoder encodeObject:super forKey:#"image"];
}
-(id)initWithCoder:(NSCoder *)decoder
{
if (self=[super init]){
super = [decoder decodeObjectForKey:#"image"];
}
return self;
}
#end
which isn't any better than where I started. If I was able to convert an UIImage to NSData I would be good, but all I can find are function like UIImagePNGRepresentation which require me to know what type of image this was. Something that UIImage doesn't allow me to do. Thoughts? I feel like I might have wandered down the wrong path...
You don't want to store images in NSUserDefaults. They're big blobs of data, and NSUserDefaults is stored as a plist; you want to write small bits of info to it.
You should write the images to disk, and then store the filenames to defaults:
NSString *filename = myImageFilename;
[UIImagePNGRepresentation(image) writeToFile: myImageFilename atomically];
[[NSUserDefaults standardDefaults] setObject: myImageFilename forKey: #"lastImageFilename"];
Stumbling upon this a year later. I would add (in case someone else stumbles here as well) that you should store the images in the cache directory and avoid iTunes trying to back them up.
- (NSString *)pathForSearchPath:(NSSearchPathDirectory)searchPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
NSString *directoryPath = [paths objectAtIndex:0];
return directoryPath;
}
- (NSString *)cacheDirectoryPath {
return [self pathForSearchPath:NSCachesDirectory];
}
I'm working on a homework app that uses custom Assignment objects for each assignment. I am trying to store an NSMutableArray (casted to an NSArray via initWithArray:) in standardUserDefaults but I'm having trouble with saving and reloading the array.
I have a table view from which you can choose to add a new assignment (which loads NewAssignmentViewController). When you save the assignment, it is pushed back to an array in AssigmentsViewController. And then you call it every time you load the UITableView which shows the assignments.
Here is the relating code:
-(void)saveToUserDefaults:(NSArray*)myArray{
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
if (standardUserDefaults) {
[standardUserDefaults setObject:[NSKeyedArchiver archivedDataWithRootObject:myArray] forKey:#"Assignments"];
[standardUserDefaults synchronize];
}
}
-(void)retrieveFromUserDefaults{
NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:#"Assignments"];
if (dataRepresentingSavedArray != nil) {
NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
if ([oldSavedArray count] != 0) {
[assignments setArray:[[NSMutableArray alloc] initWithArray:oldSavedArray]];
}
else {
assignments = [[NSMutableArray alloc] initWithCapacity:100];
}
}
}
-(void)backButtonPressed {
[self saveToUserDefaults:[[NSArray alloc] initWithArray:assignments]];
[self.navigationController popViewControllerAnimated:YES];
}
Please help. It does not load the array but does not give any error. Any tips about UserDefault or KeyedArchiver in general would be greatly appreciated.
Couple of things here:
If I understand you correctly, you're trying store an array whose contents are the assignment objects.
If you want to serialize these objects for storage into NSUserDefaults, the Assignment objects themselves need to conform the NSCoding protocol by overriding these methods:
- (void)encodeWithCoder:(NSCoder *)encoder;
- (id)initWithCoder:(NSCoder *)decoder;
Since you didn't post the code for your Assignment objects, dunno if you did this properly or at all. If you have you should be able to encode the object. See the Archives and Serializations Programming Guide for more.
As for NSUserDefaults, by my read, you're basically trying to store your application's object model there. Not the best idea. NSUserDefaults is best suited for use with light-weight persistent data: basic preferences, strings, scraps of universal data.
What I would do is write out your archived data to a file and load it when your view loads.
Here's some code from Beginning iPhone Development on that subject:
Creating an archive from an object or objects that conforms to NSCoding is relatively easy. First, we create an instance of NSMutableData to hold the encoded data and then create an NSKeyedArchiver instance to archive objects into that NSMutableData instance:
NSMutableData *data = [[NSMutableData alloc] init];
NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
After creating both of those, we then use key-value coding to archive any objects we wish to include in the archive, like this:
[archiver encodeObject:myObject forKey:#”keyValueString”];
Once we’ve encoded all the objects we want to include, we just tell the archiver we’re done, write the NSMutableData instance to the file system, and do memory cleanup on our objects.
[archiver finishEncoding]; BOOL success = [data writeToFile:#”/path/to/archive” atomically:YES];
[archiver release];
[data release];
To reconstitute objects from the archive, we go through a similar process. We create an NSData instance from the archive file and create an NSKeyedUnarchiver to decode the data:
NSData *data = [[NSData alloc] initWithContentsOfFile:path];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
After that, we read our objects from the unarchiver using the same key that we used to archive the object:
self.object = [unarchiver decodeObjectForKey:#”keyValueString”];
You'd also need to get your application's documents directory to save and load the files.
It's a wildly useful book, full of drop in code snippets. The chapter on persistence might be helpful for you. You might be much happier using Core Data for this task, come to think of it.
I'm not sure if this will fix your problem, but you don't have to pull the array out of Defaults as NSData. Check the NSUserDefaults reference and you'll see that Arrays are valid default objects.
I'm assuming my understanding of how to perform a deep copy isn't just there yet. The same with some sub-optimal memory handling that I'm performing down below. This code below probably depicts a shallow copy, and I believe that's where my problem might be. I have some cookie-cutter code for an example that looks like the following:
NSArray *user = [[xmlParser createArrayWithDictionaries:dataAsXML
withXPath:kUserXPath] retain];
if([user count] > 0) {
self.name = [[user valueForKey:#"name"] copy];
}
// Crash happens if I leave the next line un-commented.
// But then we have a memory leak.
[user release];
[xmlParser release];
Unfortunately when I comment out [user release], the code works, but we have an obvious memory leak. The method createArrayWithDictionaries:withXPath: was refactored last night when the SO community helped me understand better memory management. Here's what it looks like:
- (NSArray *)createArrayWithDictionaries:(NSString *)xmlDocument
withXPath:(NSString *)XPathStr {
NSError *theError = nil;
NSMutableArray *dictionaries = [NSMutableArray array];
CXMLDocument *theXMLDocument = [CXMLDocument alloc];
theXMLDocument = [theXMLDocument initWithXMLString:xmlDocument
options:0
error:&theError];
NSArray *nodes = [theXMLDocument nodesForXPath:XPathStr error:&theError];
for (CXMLElement *xmlElement in nodes) {
NSArray *attributes = [xmlElement attributes];
NSMutableDictionary *attributeDictionary;
attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue]
forKey:[attribute name]];
}
[dictionaries addObject:attributeDictionary];
}
[theXMLDocument release];
return dictionaries;
}
I'm guessing there's a couple of issues that might be going on here:
Auto release on my dictionaries array is happening, thus my app crashing.
I'm not performing a deep copy, only a shallow copy. Thus when the user array is released, self.name is done for.
With NSZombieEnabled, I see the following:
*** -[CFString respondsToSelector:]:
message sent to deallocated instance 0x1ae9a0
Also, the final call where the backtrace shows this is crashing contains the following code in a separate module from the other two methods:
User *u = self.user;
NSString *uri = [NSString stringWithFormat:#"%#/user/%#/%#",
[self groupName], u.userId, kLocationsUri];
Between all the auto releasing/copies/retain happening between the client code and createArrayWithDictionaries:withXPath, I'm a bit confused as to the real problem here. Thanks again for helping me understand.
OK, you don't need to retain the return value from createArrayWithDictionaries: since you're not keeping it around. The return value is autoreleased. I'd strongly recommend reading up on how autoreleasing works. You only retain things that you intend to keep around in your object.
Also, user is an NSArray. If you call [user valueForKey:#"name"], you'll get another NSArray of values representing the values of the name key for each of the objects in users. Furthermore, how is the name property on your object defined? If you declared it as copy or retain (I believe retain is the default if you don't specify it yourself), you don't need to copy or retain the value. Indeed, the accessor should always be responsible for doing the memory management, not the caller. If you wrote your own accessor (i.e. you didn't use the #synthesize keyword), you need to make sure you do the memory management there.
I'm guessing what you meant to write was something more like this:
NSArray *user = [xmlParser createArrayWithDictionaries:dataAsXML withXPath:kUserXPath];
if ([user count] > 0)
self.name = [[user objectAtIndex:0] objectForKey:#"name"];
[xmlParser release];
I think your troubles are stemming from a misunderstanding of how memory management works in Objective-C.
Hope this helps.
Auto release on my dictionaries array is happening, thus my app crashing.
If the caller intends to keep the array around somewhere, it needs to retain it. Otherwise, it will crash when it tries to access the (now-deceased) object.
If the caller is going to store it in a property, it must use the self.dictionaries = […] syntax, not dictionaries = […]. The former is a property access, which calls the setter method; the latter is a direct instance variable assignment.
Coming back to your actual question, that of a deep copy: You need to get the sub-elements of every element and put them in each element's dictionary.
Basically, you need a recursive method (or a queue, but that's harder—file under premature optimization until you've proven you need it) that takes an element and returns a dictionary, and then you need to call this method on each of your element's child elements, and collect the results into an array and put that into the dictionary you're creating.
I would recommend making this recursive method an instance method of the element. Something like:
- (NSDictionary *) dictionaryRepresentation {
NSMutableDictionary *attributeDictionary = [NSMutableDictionary dictionary];
for (CXMLNode *attribute in attributes) {
[attributeDictionary setObject:[attribute stringValue] forKey:[attribute name]];
}
NSArray *childElements = [self childElements];
return [NSDictionary dictionaryWithObjectsAndKeys:
attributeDictionary, #"attributes",
[childElements valueForKey:#"dictionaryRepresentation"], #"childElements",
nil];
}
Then you replace the loop in createArrayWithDictionaries:withXPath: with a similar valueForKey: message. I'll leave you to fill it in.
valueForKey: is Key-Value Coding's principal method. In both places, we're making use of NSArray's handy implementation of it.
(If the use of valueForKey: still doesn't make sense to you, you should read the KVC Programming Guide. KVC is vitally important in modern Cocoa, so you do need to read this sooner or later.)