Have tried storing my NSMutableArray's object to NSUserDefaults but, no luck.
My NSMutableArray contains this log right here:
`ALAsset - Type:Photo, URLs:assets-library://asset/asset.JPG?id=92A7A24F-D54B-496E-B250-542BBE37BE8C&ext=JPG`
I know that its a ALAsset object, in the AGImagePickerController it is compared as NSDictionary, so what I needed to do is save the NSDictionary or the Array I used to where I store my ALAsset object then save it in either in NSDocu or NSCaches as a file then retrieve it again (This was my idea).
But the problem is,Though I tried this code but not working, and doesn't display anything in NSDocu or NSCache Directories.
First try (info is the one that contains ALAsset object):
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *basePath = ([paths count] > 0) ? [paths objectAtIndex:0] : nil;
NSString *filePath = [basePath stringByAppendingPathComponent:#"filename.plist"];
NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:filePath];
NSString *error;
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
if(plistData) {
[info writeToFile:filePath atomically:YES];
} else {
NSLog(error);
}
Second try:
- (NSString *)createEditableCopyOfFileIfNeeded:(NSString *)_filename {
// First, test for existence.
BOOL success;
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *writableFilePath = [documentsDirectory stringByAppendingPathComponent: _filename ];
success = [fileManager fileExistsAtPath:writableFilePath];
if (success) return writableFilePath;
// The writable file does not exist, so copy the default to the appropriate location.
NSString *defaultFilePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent: _filename ];
success = [fileManager copyItemAtPath:defaultFilePath toPath:writableFilePath error:&error];
if (!success) {
NSLog([error localizedDescription]);
NSAssert1(0, #"Failed to create writable file with message '%#'.", [error localizedDescription]);
}
return writableFilePath;
}
Save it this way:
NSString *writableFilePath = [self createEditableCopyOfFileIfNeeded:[NSString stringWithString:#"hiscores"]];
if (![info writeToFile:writableFilePath atomically:YES]){
NSLog(#"WRITE ERROR");
}
Third try:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:??????];
[info writeToFile:filePath atomically:YES];
Fourth try(Unsure of because of its modifying in the appbundle):
https://stackoverflow.com/a/6311129/1302274
Is there other way? Hope someone would guide me.
You can store your NSMutableArray to NSUserDefault by archiving it to NSData and than retrieving it by Unarchiving it back to NSMutableArray.
-(NSData*) getArchievedDataFromArray:(NSMutableArray*)arr
{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
return data;
}
-(NSMutableArray*) getArrayFromArchievedData:(NSData*)data
{
NSMutableArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return arr;
}
For saving array to NSUserDefault :
[[NSUserDefaults standardUserDefaults] setObject:[self getArchievedDataFromArray: yourArray] forKey:#"YourKey"];
[[NSUserDefaults standardUserDefaults] synchronize];
For retrieving array back from NSUserDefault :
NSMutableArray *yourArray = [self getArrayFromArchievedData:[[NSUserDefaults standardUserDefaults]objectForKey:#"YourKey"]];
Also you can save Array in form of NSData to a file in NSDocumentDirectory or NSCachesDirectory. Hope this helps....
Edited: An UIImage+NSCoding category
.h file
#import <UIKit/UIKit.h>
#interface UIImage (NSCoding)
- (id) initWithCoderForArchiver:(NSCoder *)decoder;
- (void) encodeWithCoderForArchiver:(NSCoder *)encoder ;
#end
.m file
#import "UIImage+NSCoding.h"
#import <objc/runtime.h>
#define kEncodingKey #"UIImage"
#implementation UIImage (NSCoding)
+ (void) load
{
#autoreleasepool {
if (![UIImage conformsToProtocol:#protocol(NSCoding)]) {
Class class = [UIImage class];
if (!class_addMethod(
class,
#selector(initWithCoder:),
class_getMethodImplementation(class, #selector(initWithCoderForArchiver:)),
protocol_getMethodDescription(#protocol(NSCoding), #selector(initWithCoder:), YES, YES).types
)) {
NSLog(#"Critical Error - [UIImage initWithCoder:] not defined.");
}
if (!class_addMethod(
class,
#selector(encodeWithCoder:),
class_getMethodImplementation(class, #selector(encodeWithCoderForArchiver:)),
protocol_getMethodDescription(#protocol(NSCoding), #selector(encodeWithCoder:), YES, YES).types
)) {
NSLog(#"Critical Error - [UIImage encodeWithCoder:] not defined.");
}
}
}
}
- (id) initWithCoderForArchiver:(NSCoder *)decoder {
if (self = [super init]) {
NSData *data = [decoder decodeObjectForKey:kEncodingKey];
self = [self initWithData:data];
}
return self;
}
- (void) encodeWithCoderForArchiver:(NSCoder *)encoder {
NSData *data = UIImagePNGRepresentation(self);
[encoder encodeObject:data forKey:kEncodingKey];
}
#end
The documentation of NSArray for the "writeToFile:atomically:" method, shows that all members must be property list objects. ALAsset is not a property list object, so writing that to a file is not going to work.
I know that its a ALAsset object, in the AGImagePickerController it is
compared as NSDictionary
If you looked carefully then you would have seen that it does not compare ALAsset's, but their 'ALAssetPropertyURLs' property. The value of that property is an NSDictionary.
As ALAsset does not have a public constructor, there is no way you can reconstruct it after reading from a file or NSUserDefaults, even if you manage to write it.
So the best thing you can do is to re-fetch the ALAssets from the source that you originally got them from. I assume that is an ALAssetsGroup? Instead of saving to file and retrieving again, why don't you just regenerate them with the same query on ALAssetsGroup as you originally used to generate them?
EDIT:
So you say you got the original ALAsset's from an AGImagePickerController. In order to store them, you can take Matej's advice in the comments and store the URLs that identify them.
But keep in mind that AGImagePickerController is a means for the user to pick a number of photos and then do something with them. That is, the ALAssets are simply intermediare results pointing to the original locations of the photos. If you store the URL's and retrieve them later, there is no guarantee at all that the originals are still there.
So ask yourself: what is it that you want the user to do with the photos, and store the result of that action, rather than the assets themselves. For example, one reasonable action you could do is to create a new ALAssetGroup (with the addAssetsGroupAlbumWithName: method on ALAssetsLibrary), and store the assets in there. ALAssetGroups are automatically saved, so you don't need to do anything yourself for that.
EDIT 2 - after more information from the OP
What Matej hints at in the comments, is to convert the array of ALAssets that you have into an array of dictionaries by retrieving the urls from the assets. As you can read in the ALAsset class documentation you can do that in the following way:
NSArray *assetArray = // your array of ALAssets
NSMutableArray *urls = [NSMutableArray arrayWithCapacity:assetArray.count];
for( ALAsset *asset in assetArray ) {
NSDictionary *urlDictionary = [asset valueForProperty:#"ALAssetPropertyURLs"];
[urls addObject:urlDictionary];
}
The resulting array of dictionaries you can save in any way you like.
After restart of your app, you read the array of dictionaries back from where you stored it. Then Matej suggests to use ALAssetsLibrary's assetForURL:resultBlock:failureBlock: to recreate the ALAssets. But as we now know you want to put a checkmark on the original assets again, it is better to fetch the original array of ALAssets, and check whether any of them are present in the recovered urls. The following should work for that:
NSArray *assetArray = // the full array of ALAssets from AGImagePickerController
NSArray *urls = // the recovered array of NSDictionaries
for( ALAsset *asset in assetArray ) {
NSDictionary *urlDictionary = [asset valueForProperty:#"ALAssetPropertyURLs"];
if( [urls containsObject:urlDictionary] ) {
... // set checkmark on asset
}
}
This assumes the original assets have not changed, which is not under your control (the user has removed/added photos, for example).
This is the method I use for storing array or dictionary objects.
- (NSArray*)readPlist
{
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *plistPath = [[documentPaths lastObject] stringByAppendingPathComponent:#"filename.plist"];
NSFileManager *fMgr = [NSFileManager defaultManager];
if (![fMgr fileExistsAtPath:plistPath]) {
[self writePlist:[NSArray array]];
}
return [NSArray arrayWithContentsOfFile:plistPath];
}
- (void)writePlist:(NSArray*)arr
{
NSArray *documentPaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *plistPath = [[documentPaths lastObject] stringByAppendingPathComponent:#"filename.plist"];
NSFileManager *fMgr = [NSFileManager defaultManager];
if ([fMgr fileExistsAtPath:plistPath])
[fMgr removeItemAtPath:plistPath error:nil];
[arr writeToFile:plistPath atomically:YES];
}
Related
I'm trying to save a huge NSArray to a file and then retrieve it...
NSURL *documentsDirectory =
[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
inDomains:NSUserDomainMask] lastObject];
NSURL *fileURL = [documentsDirectory
URLByAppendingPathComponent:#"scorecards.dgs"];
NSMutableArray *savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfURL:fileURL];
[savedArrayOfScorecards addObject:currentScoreCard];
[savedArrayOfScorecards writeToURL:fileURL atomically:YES];
NSMutableArray *mynewArray = [NSMutableArray arrayWithContentsOfURL:fileURL];
NSLog(#"%#, %#",[[mynewArray objectAtIndex:0] objectForKey:#"info"], course);
The NSLog comes back with...
(null), BridgeMill
Which BridgeMill should be returned on both sides of the comma. But array won't save...
Check if it's being saved correctly:
if([savedArrayOfScorecards writeToURL:fileURL atomically:YES])
{
NSLog(#"Was saved");
}
else
{
NSLog(#"Houston we have a problem...");
}
On the documentation:
- (BOOL)writeToURL:(NSURL *)aURL atomically:(BOOL)atomically
You can also use this to check what is going on:
- (BOOL)writeToURL:(NSURL )aURL options:(NSDataWritingOptions)mask error:(NSError *)errorPtr
what class is course?
writeToURL only supports types that can be converted into plist format. Those types are NSString, NSData, NSArray, or NSDictionary.
If you have other types in your NSArray you have to archive the array.
I've been stuck on this for ever and I finally figured it out and now just out of the blue it stopped working again...
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/scoreCards.dgs",documentsDirectory];
NSMutableArray *savedArrayOfScorecards = [[NSMutableArray alloc]init];
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
[savedArrayOfScorecards addObject:currentScoreCard];
[savedArrayOfScorecards writeToFile:filePath atomically:YES];
The file scoreCards.dgs is not even getting created...
What am I doing wrong?
There could be a couple things going wrong here.
1) The kind of data you're storing in the array might not be encodable or archive-able to a file. And the code snippet you included doesn't give a good hint as to what kind of data you're trying to save. If you have custom objects in your array (i.e. things that are not NSString, NSNumber, NSDate, etc.), then that's definitely the problem. There are plenty of questions here on StackOverflow that might help you solve this issue.
2) Your array's filepath could be bogus. For example, you're not checking to see if "documentsDirectory" is nil or valid or writeable.
3) Also possible, but not likely, "savedArrayOfScorecards" might be a nil array. You should do error checking to make sure "savedArrayOfScorecards" was instantiated and that there is more than one object in the array.
Your problem is, that although you create an array, before reading the file it is getting nil-ed on your call to:
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
So, because this savedArrayOfScorecards is now nil, your call to write it to a file is not doing anything.
You should load the array to another variable, and check it being nil, and create the new array only if the one read from the file is nil. Something like this:
NSMutableArray *savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
if (!savedArrayOfScorecards) {
savedArrayOfScorecards = [[NSMutableArray alloc]init];
}
Are you sure the file exists when loading it?
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
This line creates a new NSMutableArray from the file. If the file does not exist, it returns nil. writeToFile is then sent to nil and nothing would happen.
Add a check to see if it's nil and create a new array if it is:
NSMutableArray *savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
if(savedArrayOfScorecards == nil) savedArrayOfScorecards = [NSMutableArray array];
[savedArrayOfScorecards addObject:currentScoreCard];
[savedArrayOfScorecards writeToFile:filePath atomically:YES];
NSMutableArray is not a property-list-compliant format. You must use an NSArchiver to make it plist compliant.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [NSString stringWithFormat:#"%#/scoreCards.dgs",documentsDirectory];
NSMutableArray *savedArrayOfScorecards = [[NSMutableArray alloc]init];
savedArrayOfScorecards = [NSMutableArray arrayWithContentsOfFile:filePath];
[savedArrayOfScorecards addObject:#"ALLLAALLAAALLA"];
NSMutableData *data = [NSMutableData data];
NSKeyedArchiver *archive = [[NSKeyedArchiver alloc]initForWritingWithMutableData:data];
[archive encodeObject:savedArrayOfScorecards forKey:#"Scorecards"];
[archive finishEncoding];
BOOL result = [data writeToFile:filePath atomically:YES];
NSLog(result ? #"YES" : #"NO");
The correct answers are already here, just adding a better solution:
NSFileManager* fileManager = [NSFileManager defaultManager];
NSMutableArray* array;
if ([fileManager fileExistsAtPath:filePath]) {
array = [NSMutableArray arrayWithContentsOfFile:filePath];
NSAssert(array != nil, #"Invalid data in file.");
}
else {
array = [[NSMutableArray] alloc] init];
}
[array addObject:currentScoreCard];
[array writeToFile:filePath atomically:YES];
I am having an app in which at the time of launcing the app XML parsing is giving Main category from URL like hp, dell, etc...I am displaying it in the Tableview.
Then on click of particular cell i can get the detail of main category means its subcategory like http://www.dealsbell.com/findcoupon.php?store=hp
Here also i am getting data properly after parsing.
But my concern over here is, in ( http://www.dealsbell.com/findcoupon.php?store=hp ) this link i am getting images.
Each particular subcategory will have a same image. So i want to do something like that the image if first time loaded from the URL then it will display image from parsing otherwise i would like to store that image as its byte code in folder / file / in any way in my device on first parsing.
If once the image is stored to the particular way in my device next time when i will go to see the subcategory it will first check this image is stored locally to my device or not.
If yes then it should go to the particular location to fetch this local image & display it to each cell otherwise will parse & display image.
I hope you are getting, what i want to ask.
Please guide me, how can this be possible & what is the way to get result.
If any example or link you can suggest, then it will be more efficient to me.
Thanks in advance.
There are probably two ways to achive this.
Get NSData out of Image and hold that in UserDefaults or database.
Dump image in application folder and pick image from that place.
So whenever you try to load image for subcatogory check at one of place and if present use that. IF in case you have stored image and if any updated image comes,then remove previous copy and store new one.
-(void) SaveImageinDocumentWithName:(UIImage*) aUIImage Name:(NSString*) aName
{
if(aUIImage)
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableString* str = [[NSMutableString alloc] initWithCapacity:300];
[str appendString:documentsDirectory];
[str appendString:#"/"];
[str appendString:aName];
NSFileManager *fileManager = [NSFileManager defaultManager];
[UIImagePNGRepresentation(aUIImage) writeToFile:str atomically:YES];
if(str)
{
[str release];
str = nil;
}
if(fileManager)
{
[fileManager release];
fileManager = nil;
}
[pool release];
}
}
-- Getting saved image
-(UIImage*)GetSavedImageWithName:(NSString*) aFileName
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSMutableString* str = [[NSMutableString alloc] initWithCapacity:300];
[str appendString:documentsDirectory];
[str appendString:#"/"];
[str appendString:aFileName];
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL success = [fileManager fileExistsAtPath:str];
NSData *dataToWrite = nil;
UIImage* image = nil;
if(!success)
{
}
else
{
image = [[UIImage alloc] initWithContentsOfFile:str];
}
if(dataToWrite)
{
[dataToWrite release];
dataToWrite = nil;
}
if(str)
{
[str release];
str = nil;
}
if(fileManager)
{
[fileManager release];
fileManager = nil;
}
return image;
}
Parse dealsbell.com/wp-content/uploads/mobile/hp.gif and take only hp.gif
NSString *strImage = [NSString stringWithFormat:#"%#",aBook.image];
UIImage* image = [self GetSavedImageWithName:strImage];
if(image) // This means Image exists
{
// Do what you want
}
else
{
NSURL *url4Image = [NSURL URLWithString:strImage];
NSData *data = [NSData dataWithContentsOfURL:url4Image];
if(data != NULL)
{
image =[[UIImage alloc] initWithData:data];
[self SaveImageinDocumentWithName:image Name:strImage]; // save for future ref.
}
else
{
image =[UIImage imageNamed:#"icon.png"];
}
}
Okay I have spent the last 2 days trying to sort this one out. I have a class thats controlling my property list, It is used as a singleton class so its only instantiated once and it has one other method in it that is used to save the values to the plist.
The plist works sweet no matter what you do up untill you remove the app from the multitasking bar (i.e. close the app completely) then all of the values in the plist are reset to null.. However the plist is not lost.
So I am thinking now maybe I am overwriting the values in my plist that I have made in the root document directory for read and write capability. I am hoping with my code example you guys might be able to spot my mistake... I'm not sure what else I need to add.. if you have any question that will help you help me answer this problem then please let me know.
any help would be hugely appreciated and my children's children's children will be in debuted to you.
#import "EnginePropertiesController.h"
static EnginePropertiesController *sharedMyManager = nil;
#implementation EnginePropertiesController
#synthesize pSignature;
#synthesize pVersion;
#synthesize rNumber;
#synthesize dVReturned;
#synthesize cacheValue;
#synthesize manu;
#synthesize mod;
#synthesize submod;
#pragma mark Singleton Methods
+ (id)sharedManager {
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
- (id)init {
if (self = [super init]) {
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.plist"];
NSLog(#"myplist path read = %#", plistPath);
// check to see if Data.plist exists in documents
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath])
{
// if not in documents, get property list from main bundle
plistPath = [[NSBundle mainBundle] pathForResource:#"EngineProperties" ofType:#"plist"];
}
// read property list into memory as an NSData object
NSData *plistXML = [[NSFileManager defaultManager] contentsAtPath:plistPath];
NSString *errorDesc = nil;
NSPropertyListFormat format;
// convert static property liost into dictionary object
NSDictionary *tempRoot = (NSMutableDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML mutabilityOption:NSPropertyListMutableContainersAndLeaves format:&format errorDescription:&errorDesc];
manu = [cacheValue objectForKey:#"Manu"];
mod = [cacheValue objectForKey:#"Mod"];
submod = [cacheValue objectForKey:#"SubMod"];
if (tempRoot && [tempRoot count]){
// assign values
self.pVersion = [tempRoot objectForKey:#"PSignature"];
self.pVersion = [tempRoot objectForKey:#"PVersion"];
self.rNumber = [tempRoot objectForKey:#"RNumber"];
self.dVReturned = [tempRoot objectForKey:#"DVReturned"];
cacheValue = [tempRoot objectForKey:#"Cache Value"];
}
}
return self;
}
- (void) saveData:(NSString *)methodName pSignature:(NSString *)TemppSignature pVersion:(NSNumber *)TemppVersion rNumber:(NSNumber *)TemprNumber dVReturned:(NSNumber *)TempdvReturned cacheValue:(NSNumber *)cValue
{
// get paths from root direcory
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
// get documents path
NSString *documentsPath = [paths objectAtIndex:0];
// get the path to our Data/plist file
NSString *plistPath = [documentsPath stringByAppendingPathComponent:#"EngineProperties.plist"];
// set the variables to the values in the text fields
self.pSignature = TemppSignature;
self.pVersion = TemppVersion;
self.rNumber = TemprNumber;
self.dVReturned = TempdVReturned;
//This checks with methodName I would like to save the value from
//The reason for this is there are 3 different cache values I get at seperate times to save This if statment just makes sure they are in the correct order.
if ([methodName isEqualToString:#"Getmanu"]) {
self.manu = cValue;
} else if ([methodName isEqualToString:#"GetMod"]) {
self.mod = cValue;
} else if ([methodName isEqualToString:#"GetSubMod"]) {
self.submod = cValue;
}
//Set up cacheValue Dictionary that will be added to the property list root dictionary
self.cacheValue = [NSDictionary dictionaryWithObjectsAndKeys:
manu, #"Manu",
mod, #"Mod",
subMod, #"SubMod", nil];
//Pass appropriate values into plist
NSDictionary *plistDict = [NSDictionary dictionaryWithObjectsAndKeys:
protocolSignature, #"Protocol Signature",
pVersion, #"Protocol Version",
rNumber, #"Request Number",
dVReturned, #"Data Version returned",
cacheValue, #"Cache Value", nil];
NSString *error = nil;
// create NSData from dictionary
NSData *plistData = [NSPropertyListSerialization dataFromPropertyList:plistDict format:NSPropertyListXMLFormat_v1_0 errorDescription:&error];
// check is plistData exists
if(plistData) {
// write plistData to our Data.plist file
[plistData writeToFile:plistPath atomically:YES];
} else {
NSLog(#"Error in saveData: %#", error);
}
}
#end
The Problem: nil Values
Here's a problem: pVersion is assigned twice, while pSignature remains nil.
self.pVersion = [tempRoot objectForKey:#"PSignature"];
self.pVersion = [tempRoot objectForKey:#"PVersion"];
This should probably be
self.pSignature = [tempRoot objectForKey:#"PSignature"];
self.pVersion = [tempRoot objectForKey:#"PVersion"];
This in turn leads to trouble at saving time:
NSDictionary *plistDict = [NSDictionary dictionaryWithObjectsAndKeys:
pSignature, #"PSignature",
pVersion, #"PVersion",
rNumber, #"RNumber",
dVReturned, #"DVReturned",
cacheValue, #"Cache Value", nil];
This constructor doesn't know how many arguments it has, it just keeps going until it hits nil. If pSignature is nil, it will save an empty dictionary.
The Fix: Robust NSDictionary Creation
While the immediate solution is to fix the pSignature reading, it's always fragile to construct dictionaries using dictionaryWithObjectsAndKeys:. It's much better to store each object individually, using setObject:forKey. This throws an exception if the object is nil, so test before calling.
NSMutableDictionary *plistDict = [NSMutableDictionary dictionary];
if (pSignature) [plistDict setObject:pSignature forKey:#"PSignature"];
if (pVersion) [plistDict setObject:pVersion forKey:#"PVersion"];
// ... and so on.
The Alternative: NSCoding
Another option for storage is to serialize. See the Archives and Serializations Programming Guide. This has tradeoffs: while saving and loading is simpler when it works, the resulting files are less human-readable.
Debugging
Finally, three places to put log statements, breakpoints or dialog boxes that help in cases like this:
Log the dictionaries being loaded and the dictionaries being saved. If you open your app and trigger a save immediately, are the dictionaries identical?
Check the first argument sent to nil-terminated methods like arrayWithObjects: and dictionaryWithObjectsAndKeys:
On the simulator, log the path used for saving and open it in Finder. Is there a file there? If it's a property list, you can open it in XCode and see if it has the content you expect.
I'm working on simple iPhone game. When a game finish i'm trying to save some scores info to a .plist file. Can you help me to understand what i'm doing wrong?
Here is method for a "Done" game:
- (void) gameDone {
// something ....
// save our current score values
NSDictionary *currentScore = [[NSDictionary alloc] initWithObjectsAndKeys:[NSNumber numberWithInt:[score getScoreValue]], #"score", [NSNumber numberWithFloat:[time getSeconds]], #"time", nil];
[score saveValueToFile:currentScore toFile:fileName];
[currentScore release];
// something ....
}
Here is 2 methods to save a scores from my other class:
- (NSMutableArray *) loadFromFile:(NSString *) fileName {
{
// get a paths
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *plistPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.plist", fileName]];
NSMutableArray *scoreTable = [NSMutableArray arrayWithContentsOfFile:plistPath];
return scoreTable;
}
// save a score table to plist file
- (void) saveValueToFile:(NSDictionary *)value toFile:(NSString *)fileName {
// load all score table
NSMutableArray *scoreTable;
scoreTable = [self loadFromFile:fileName];
// insert new value
// [MEMORY_LEAKS_LINE] ATTENTION: Instrument > Memory Leaks point to this line
[scoreTable insertObject:value atIndex:[scoreTable count]];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"%#.plist", fileName]];
[scoreTable writeToFile:path atomically:YES];
[scoreTable release];
}
When I launch a new game (actually my game designed with possibility to play many games one after another) Memory Leaks Instrument always add 32 bytes to Total Leaks Bytes and link to [MEMORY_LEAKS_LINE]
I thought my problem might be with currentScore value. But I release it in first part of sample code here.
What I'm doing wrong?
i'm not sure about the leak, but you are doing 'scoreTable release' , but your scoreTable is already in autorelease mode (you called [NSMutableArray arrayWithContentsOfFile:plistPath]), so it's not retained