iOS : Call a method just one time - iphone

Hi I was wondering how can I call a method just for one time in application life ... My application should download some files from server and I need do it just for one time; I mean mean just one time per installation
here is my method
//Download some images from server and save it into directory
- (void) downloadCovers {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
[self saveFile:#"mag1" ofType:#"png" fromURL:#"http://myweb.com/mag1.png" inDirectory:documentsDirectory];
}
and this method set images as UIButton BG :
- (void)buttonsBGImage {
UIImage * bgMag1 = [self loadImage:#"mag1" ofType:#"png" inDirectory:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
[mag1 setBackgroundImage:bgMag1 forState:UIControlStateNormal];
NSLog(#"BG IS SET");
}

why not just testing if the file is exist or not in local storage!
//Download some images from server and save it into directory
- (void) downloadCovers {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *pathToImg = [NSString stringWithFormat:#"%#/mag1.png",documentsDirectory];
BOOL isExist = [[NSFileManager defaultManager]fileExistsAtPath:pathToImg];
if (!isExist) {
[self saveFile:#"mag1" ofType:#"png" fromURL:#"http://myweb.com/mag1.png" inDirectory:documentsDirectory];
}
}

You can't do it for a method, but you can do it for a function using pthread_once:
static pthread_once_t once = PTHREAD_ONCE_INIT;
pthread_once(& once, SomeFunction);
or you can execute a block once using dispatch_once (the most natural choice for your current implementation).
In some cases (not this one), you may also prefer to do your work in +initialize.
EDIT: Question was clarified
Just check for the file's existence, or use a preference if you want this to persist across multiple launches.

Set a flag as a NSUserDefaults key and check for this NSUserDefault value in your downloadCovers method. If it is already set, do nothing, else download files and set the flag to true.
Like so:
-(void) downloadCovers {
BOOL downloaded = [[NSUserDefaults standardUserDefaults] boolForKey: #"downloaded"];
if (!downloaded) {
//download code here
[[NSUserDefaults standardUserDefaults] setBool:YES forKey: #"downloaded"];
}
}
Cheers

- (void)buttonsBGImage {
if (!mag1.backgroundImage){
UIImage * bgMag1 = [self loadImage:#"mag1" ofType:#"png" inDirectory:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
[mag1 setBackgroundImage:bgMag1 forState:UIControlStateNormal];
NSLog(#"BG IS SET");
}
}

Related

Saving in NSDocumentDirectory or NSCachesDirectory

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];
}

I'm having file saving/loading issue in ios 5?

I have an app that is supposed to save to a file and later on load it. Now, I have not had ANY problems what so ever on ios 4, so this is perplexing. This has happened on all my apps saving and loading.
Heres the code:
- (NSString *)pathOfFile{
NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsFolder = [paths objectAtIndex:0];
return [documentsFolder stringByAppendingFormat:#"awesome.plist"];
}
Later in in the app...
[array writeToFile:[self pathOfFile] atomically:YES];
And then when I attempt to load it...
if ([[NSFileManager defaultManager] fileExistsAtPath:[self pathOfFile]]) {
NSMutableArray *array = [[NSMutableArray alloc] initWithContentsOfFile:filepath];
achi.text = [array objectAtIndex:0];
}
My app actually just skips over the if statement (Meaning that it can't find the file I think).
Please help, and if you have different methods of saving files, I would be glad to hear to hear them.
Your - (NSString *)pathOfFile method is wrong. It should be:
- (NSString *)pathOfFile
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsFolder = [paths objectAtIndex:0];
return [documentsFolder stringByAppendingPathComponent:#"awesome.plist"];
}
In your -(NSString *)pathOfFile method, don't use stringByAppendingFormat:. When working with file paths, you should instead use stringByAppendingPathComponent:, as it will ensure that the appropriate slash characters are added (or removed, if there are too many):
return [documentsFolder stringByAppendingPathComponent:#"awesome.plist"];
The comment to my question was what solved the problem, but as I can't give him the correct answer, I'll just write paste his answer here:
Did you make sure the directory is there? Sometimes that Documents directory must be created.

NSMutableArray memory leaks problems

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

Can I write an array of UILocalNotifications to disk?

I'm trying to use the following code to persist the current list of local notifications. NSArray explicitly lists the kind of objects it will work with, which implies I can not use this with an array full of UILocalNotification objects. However, UILocalNotifications does implement NSCoding, which led me to believe there must be an easy way to serialize/deserialize this list of objects. Do I need to do the encoding and file persistence myself? Also, is there a way to get more information about why the write failed?
- (NSString*)getSavedNotifsPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
return [documentsDirectory stringByAppendingString:#"saved_notifs.plist"];
}
- (void)prepareToHide {
UIApplication* app = [UIApplication sharedApplication];
NSArray *existingNotifications = [app scheduledLocalNotifications];
if (! [existingNotifications writeToFile:[self getSavedNotifsPath] atomically:NO] ) {
// alert
[self showSomething:#"write failed"];
}
}
First, change the code
return [documentsDirectory stringByAppendingString:#"saved_notifs.plist"];
to
return [documentsDirectory stringByAppendingPathComponent:#"saved_notifs.plist"];
stringByAppendingPathComponent: will ensure a slash (/) is included, if needed, before the file name.
NSArray can only save property list objects, which UILocalNotification is not. Instead, try using NSKeyedArchiver. For example:
- (void)prepareToHide {
UIApplication* app = [UIApplication sharedApplication];
NSArray *existingNotifications = [app scheduledLocalNotifications];
NSString *path = [self getSavedNotifsPath];
BOOL success = [NSKeyedArchiver archiveRootObject:existingNotifications toFile:path];
if (! success ) {
// alert
[self showSomething:#"write failed"];
}
}
Use NSKeyedUnarchiver to retrieve the array from the saved file.
Note: I have not actually tested this so I'm not 100% sure it will work. But give it a try and see what happens.

can't figure out why archiveRootObject is failing to write file

this seems to work fine in the simulator but on the device the files are not being written.
here's the code.
-(void)saveOld{
NSArray *saveState = [NSArray arrayWithObjects:headArray,dropQArray,[NSNumber numberWithInt:dropLimit],[NSNumber numberWithInt:dropCount],[NSNumber numberWithInt:score],[NSNumber numberWithInt:level],[NSNumber numberWithInt:maxChain],nil];
NSMutableString *path = [[NSHomeDirectory() mutableCopy]autorelease];
[path appendString:#"/saveState"];
BOOL saved = [NSKeyedArchiver archiveRootObject:saveState toFile:path];
NSLog(#"did save state %d",saved);
path = [[NSHomeDirectory() mutableCopy]autorelease];
[path appendString:#"/isSaveState"];
saved = [NSKeyedArchiver archiveRootObject:[NSNumber numberWithBool:1] toFile:path];
NSLog(#"did save state %d",saved);
}
There is no home directory on the iPhone :D
You should use this instead:
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
This will give you the basic documents directory, append strings to it then.