I've got some simple code (copy and pasted from SO) that loads a KLM (XML based) file into iOS's documents directory. I then display the loaded data on a map.
I realise that this is not a good way of downloading and saving the file - NSUrlConnection seems to be recommended so that the loading can be managed. But I'm new to all this and I'd like to understand what is happening in this case first.
Here's the code:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [NSString stringWithFormat:#"%#/%#", [paths objectAtIndex:0],#"index.kml"];
// Download and write to file
NSURL *url = [NSURL URLWithString:#"http://www.domain.co.uk/kml-resource..."];
NSData *urlData = [NSData dataWithContentsOfURL:url];
[urlData writeToFile:filePath atomically:YES];
NSURL *fileurl = [NSURL fileURLWithPath:filePath];
kmlParser = [[KMLParser alloc] initWithURL:fileurl];
.....
My questions are:
What happens while dataWithContentsOfURL is connecting/downloading - does the application just freeze and become unresponsive?
If I run my program in aeroplane mode the second time, it still seems to work. When does it decide it's OK to skip the download and writeToFile?
Does anyone know if it uses any caching between dataWithContentsOfURL and the server? ie. can I be sure that if I get a response, it is fresh data and hasn't just been sitting in safari/iOS's cache.
Many thanks
dataWithContentsOfURL is blocking method, so yeah, you shouldn't run that on main thread.
probably has internal timeout, but that is private... maybe 60 sec.
documentation doesn't say anything about caching, so I assume it doesn't cache at all.
Related
So, my app queries an Amazon Dynamo DB database and retrieves a few kilobytes worth of data. What I want the app to do is download everything the first time, and then every time after, just download a timestamp to see if it has the most recent version of the data. So that I only have to download the data every once in a while, I'm trying to use NSKeyedArchiver to archive the array that I'm downloading. I have tried this three different ways, and none of them work on an iPhone, although two of them work on the simulator.
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:#"dataArray.archive"];
This does not work on the simulator nor the actual iphone. The result of this method is NO.
The next thing I used was the full path:
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:#"Users/Corey/Desktop/.../dataArray.archive"];
And this worked on the simulator, but not on the iPhone. My guess was that when compiled, the filesystem looks different (and obviously doesn't have the same path). So next I tried:
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"dataArray" ofType:#".archive"];
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:filePath];
Once again, this works on the simulator but fails on the iphone. I have confirmed that all of the data is in self.dataArray before writing to the archive, and confirmed that the array is nil after writing back to the archive (in the iphone version). Any ideas what's going on? Is there a better way to do the filepath?
This is what I tracked down:
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent: #"dataArray.archive"];
[NSKeyedArchiver archiveRootObject:your_object toFile:filePath];
and it worked perfectly on both the simulator and the iPhone!
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:#"dataArray.archive"];
You have to provide a full path.
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:#"Users/Corey/Desktop/.../dataArray.archive"];
That is not a full path. A full path begins with / and does not have /../ anywhere.
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"dataArray" ofType:#".archive"];
You do not have permission to write inside the mainBundle, it is read only.
Also, in general you shouldn't use file paths, you should use URLs. Some APIs (including this one) requires a path but URLs are the recommended approach these days.
Here's the proper way to write the file to disk:
NSURL *applicationSupportUrl = [[NSFileManager defaultManager] URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask][0];
applicationSupportUrl = [applicationSupportUrl URLByAppendingPathComponent:#"My App"]; // replace with your app name
if (![applicationSupportUrl checkResourceIsReachableAndReturnError:NULL]) {
[[NSFileManager defaultManager] createDirectoryAtURL:applicationSupportUrl withIntermediateDirectories:YES attributes:#{} error:NULL];
}
NSURL *archiveUrl = [applicationSupportUrl URLByAppendingPathComponent:#"foo.archive"];
[NSKeyedArchiver archiveRootObject:self.dataArray toFile:archiveUrl.path];
I have developed an iOS7 app. I use the UIImagePickerController to get a UIImage. During the usage of the app an image
is stored to the local app directory and loaded. The file will be overwritten several times. I use UIImagePNGRepresentation and NSData storeToFile to store
the picture. In order to load the image I use UIImage initWithContentsOfFile.
It works fine for some store and load processes. With the Debugger I can confirm that the picture is stored and loaded appropriately.
But after some time the stored picture does not contain a picture anymore. When I look at the load procedure with the debugger I can see that
a picture is loaded but it seems to be completely transparent with no other information.
This phenomenon only occurs on a device and not during a simulation with the simulator. Furthermore, the effect seems to occur only when the app is kicked out of the memory (double tap of the home button)
I debugged the app for hours but I cannot imagine the reasons for this behaviour. Additionally it is difficult to debug due to the complete termination of the app. Does anybody know a solution or has been faced with the same problem? Could anybody give me a hint what to debug to track down the problem?
Thanks in advance. Any help is appreciated. :)
Thanks for your answers. Here is a code excerpt:
Code to store image:
Generate file path in documents directory
NSString *name = [NSString stringWithFormat:#"ImageforButton%i.png",i];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *file = [[path objectAtIndex:0] stringByAppendingPathComponent:name];
Create NSData from UIImage (image is a UIImage that always has the correct image data)
NSData *picData = UIImagePNGRepresentation(image);
Write NSData to file
[picData writeToFile:file atomically:true];
Code to load image:
Generate file path
NSString *name = [NSString stringWithFormat:#"ImageforButton%i.png",i];
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *file = [[path objectAtIndex:0] stringByAppendingPathComponent:name];
Load picture
UIImage *img;
if ([[NSFileManager defaultManager] fileExistsAtPath:file isDirectory:false] == true)
{
img = [UIImage imageWithContentsOfFile:[self saveFilePath:name]];
}
img does not always contain the right data. Before the app is completely terminated img
contains the expected picture. After the app was completely terminated a valid
UIImage can be loaded to img but it seems only to contain a transparent picture.
All in all it does not contain the right picture anymore.
We have a web services function to which we are uploading an audio file into to be saved in the server. The code that I am using to create a data buffer (to pass to the web services) of the contents of the audio file is :
NSURL *soundFileURL = [[NSURL alloc] initFileURLWithPath:fnWithSlash];
audioData = [NSData dataWithContentsOfURL:soundFileURL];
NSLog(#"audiodata%d",audioData.length);
Here fnWithSlash contains the full path of the file. However audioData.length is always 0.
What am i missing?
Thanks in advance for your response.
Try using NSData's dataWithContentsOfFile: instead..If the file is in your bundle it will look like the example below, otherwise construct the filePath of the sound file to your desired location..
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"yourSound" ofType:#"wav"];
audioData = [NSData dataWithContentsOfFile:filePath];
NSLog(#"audiodata%d",audioData.length);
I am currently using a function in my app's didFinishLaunchingWithOptions that retrieves a file, saves it to the application directory.
I have found that when there is a weak connection the app will crash when this is happening. I read that there is a 20 second time limit Apple allows before crashing the app. Is this correct? If so, I believe this is causing my issue as the app works flawlessly with the exception of being on a very weak connection.
How could I modify my logic below to try and compensate for this?
- (void)writeJsonToFile
{
//applications Documents dirctory path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//live json data url
NSString *stringURL = #"http://link-to-my-data.json";
NSURL *url = [NSURL URLWithString:stringURL];
NSData *urlData = [NSData dataWithContentsOfURL:url];
//attempt to download live data
if (urlData)
{
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory,#"data.json"];
[urlData writeToFile:filePath atomically:YES];
}
//copy data from initial package into the applications Documents folder
else
{
//file to write to
NSString *filePath = [NSString stringWithFormat:#"%#/%#", documentsDirectory,#"data.json"];
//file to copy from
NSString *json = [ [NSBundle mainBundle] pathForResource:#"data" ofType:#"json" inDirectory:#"html/data" ];
NSData *jsonData = [NSData dataWithContentsOfFile:json options:kNilOptions error:nil];
//write file to device
[jsonData writeToFile:filePath atomically:YES];
}
}
It's a very bad idea to run this sort of thing on the main thread: I assume you are - basically, you'll block the entire UI while you wait for the network operation to complete.
dataWithContentsOfURL is not a good idea for this sort of thing. It will be much better to use NSURLConnection or one of the wrapper libraries like AFNetworking, because you can handle cases like when the connection times out gracefully.
These libraries also have built-in methods to asynchronously download the data, which prevents the main UI thread from being locked.
When is this downloaded data needed?
Depending on the answer, maybe you can call the method inside a thread. This will prevent the main thread from blocking.
Even if the data is needed from the beginning, you can just create a loader and download the file in the background, then make the app active after the file is downloaded.
I think to be more independent from internal implementation of NSData *urlData = [NSData dataWithContentsOfURL:url]; you should implement you own download class based on NSURLConnection.
The links to read:
URL Loading System Programming Guide
NSURLConnection Class Reference
NSURLConnectionDelegate Protocol Reference
So you can catch all connection errors by your code and implement right behavior in this case.
I have a Data plist (conveniently named Data.plist) that is updated on launch of the app:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Determile cache file path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *filePath = [NSString stringWithFormat:#"%#/%#", [paths objectAtIndex:0],#"Data.plist"];
NSString *dataURLString = #"http://link/to/Data.plist";
NSURL *dataURL = [[NSURL alloc] initWithString:dataURLString];
NSData *plistData = [NSData dataWithContentsOfURL:dataURL];
[plistData writeToFile:filePath atomically:YES];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
NSLog(#"The bundle is %#", filePath);
self.data = dict;
// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
return YES;
}
I'd like to be able to have some way of checking the saved plist against the server plist - I've seen some implementations that use external libraries but there has to be something in the original iOS SDK. Any ideas? I've read whatever code I do end up using needs to be implemented in viewWillAppear but I'm not sure what that code is exactly.
Two things... first, dataWithContentsOfURL: and generally any of Apple's (temptingly convenient) <anything>WithContentsOfURL: methods are extremely unsafe in the real world. It's blocking which means that no other code will execute until your request succeeds or fails. That means that if the server isn't available or your device doesn't have internet or for some other reason cannot retrieve your data, your phone will sit there until either the iOS watchdog process kills your app for freezing for too long, or it just fails. Then the rest of your app that is expecting data will freak out because suddenly you have no data when your code assumes you should. This is one of many problems with synchronous requests.
I won't go into how to implement asynchronous requests, but head over to Apple's documentation or you can use a wrapper framework like http://allseeing-i.com/ASIHTTPRequest/ that does it for you. Also have a look at http://www.cocoabyss.com/foundation/nsurlconnection-synchronous-asynchronous/
To answer your actual question, you could have a tiny text file on your server with a version number or time stamp and download that along with your plist. on subsequent launches, you can pull down the time stamp/version number and compare it against the one you've got stored, and if the version on the server is more recent, then you pull it and save the new time stamp/version number.