iOS - NSFIleManager - How to deal with low disk space - ios

When writing data to file (e.g. Thumbnails for caching, user data, etc.), how do you deal with the fact that the iDevice could not be able to write your data to file since the disk is full?
Will NSFileManager throw an exception in case of low disk space?
What's the designated way to deal with this and to inform my user that there's very little disk space left for his data? (I'm saving a fair amount of different data at different places in my app and searching for a common way to deal with it.)

As you mentioned in the comments that you want to save NSDictionary. If you only want to know whether the file is saved successfully or not, you can inspect the return value of the
writeToFile:atomically: function.
Return Value
YES if the file is written successfully, otherwise NO.
More information under the NSDictionary's Storing Dictionaries Section.
Alternatively,
If you want to get a more detail error message for the failure (such as out of disk space, folder not exist and etc.), then you can convert the NSDictionary to NSData before saving it.
NSDictionary to NSData:
NSData *myData = [NSKeyedArchiver archivedDataWithRootObject:myDictionary];
NSData to NSDictionary:
NSDictionary *myDictionary = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:myData];
The benifits is that you will also have access to this API -writeToFile:options:error:.
If there is an error writing out the data, upon return contains an NSError object that describes the problem.
Also more detail could be found under the Storing Data Section of NSData.
I think that's the best you can do in case there is a low disk space problem on the device.

Related

Byte Buffers NSData

I'm just confused about when to use NSData. I'm about to get some data stored in the server (I'm just using dropbox here instead for some practice.)
What I did so far is
Made some p-list storing 2 arrays, one stores string object , and the other stores string objects of the public URL for the audio data.
When I created the NSString from the plist , I didn't use NSData object
When I created the NSAudio Player with data stored in the same project folder, I didn't use NSData object
When I created the NSAudio Player with data stored in a server, I used the NSData
I just don't understand when to use NSData object properly. I checked the app document, but it says "Data objects let simple allocated buffers" What is the allocated buffers here?
Thanks,
NSData is just a wrapper for a byte array. Anywhere that you specifically need a byte array you can either use a byte array and do all manipulations manually (accessing and manipulating the data) or you can wrap it in an NSData (or NSMutableData if you need to modify the bytes) and use Apple provided functions to easily access or modify the data.
The allocated buffer is the byte array stored inside the NSData wrapper. Say you have an audio object on disk that you want to modify one byte in the middle of. You could load that data from disk into an audio element, but then you can't modify it. If you load it's byte values from disk into an NSMutableData you can have access to the bytes directly, modify whatever you would like using simple methods provided to the NSMutableData class, then same the audio element back to disk (or load that data directly into your audio element).
The best use of an NSData object is only when you need it, just like any other class. If you specifically need the functionality to run your app, then use it. Otherwise it is likely just an added step that is not required (ex data-on-disk -> audio-element vs data-on-disk -> NSData -> audio-element).

UIImage from NSInputStream

I'm downloading a 2400x1600 image from Parse and I don't want it to hold all that data in memory at once. PFFile object from Parse has a convenient method to get NSData as NSInputStream so when the data is finally downloaded I end up with a NSInputStream.
So now I want to use that NSInputStream to get my UIImage. It should work like creating a UIImage with contents of file method i.e. not the whole image is loaded into memory at once.
I think that writing to a file from NSInputStream and then use the UIImage's contents of file method should work fine in my case, but I have found no way to write to a file from a NSInputStream.
Any code example or some guideline would be really appreciated.
Thanks in advance.
To accomplish this you can set up an NSOutputStream to then stream the received data to a file. Create your output stream using initToFileAtPath:append: with YES for append. In your input stream call back, pass the data to your output stream by calling write:maxLength: (read more in the docs). Once the stream is complete, you then have the full image on file without ever having it fully in memory.
Henri's answer above is more appropriate since you're using Parse, but this is the general solution.
In the documentation on iOS/OS X, Parse brings this an example.
Retrieving the image back involves calling one of the getData variants on the PFFile. Here we retrieve the image file off another UserPhoto named anotherPhoto:
PFFile *userImageFile = anotherPhoto[#"imageFile"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error) {
UIImage *image = [UIImage imageWithData:imageData];
}
}];
Now, I don't quite see the reason for you to use NSInputStream, mainly for two reasons:
NSInputStream is supposedly meant for INPUTTING data, not taking it from somewhere
NSInputStream is meant for streaming, so for scenarios in which you want to do something with the data as it is coming in, from your description it seems as if you only ever care about the data once it has completed the download.
In short, you should be using the aforementioned way, unless you truly care about the way the data is loaded in, for example wanting to manipulate it as it comes in (highly unlikely in the case you describe).
As to having it all in memory at once, the dimensions you give are not that large, yes you could stream it into a file, but assuming you want to show it full-size in the app, the problem of memory would appear at some point nevertheless, i.e you would just be postponing the inevitable. If that is not the case (not showing full-size), then it might be a good idea to chop the source image up into tiles and use those instead, far quicker to download specific tiles and easier on memory.

Read plaintext document into NSArray in iOS?

I'm new to iOS but have plenty of experience with c++ and Python. I'm trying to figure out how to read a plaintext file I have on my computer into an NSArray in xcode. In c++ I would do this:
while(istr>>string) myArray.push_back(string);
However, I need to create a local copy to be stored on the iOS device. Is there a way I can package this data so that a local copy of JUST THE ARRAY will be stored on the device? I was thinking of maybe doing something with a JSON serialization or something.
Should I really just suck it up and do this:
NSArray myArray = [[NSArray alloc] initWithObjects: #"myInfo", nil];
I just want a more elegant way to handle this, I guess.
I think maybe you're thinking a little too C about this. In C and C++, strings are arrays of bytes. In ObjC, there's an object for that. It's called NSString, and it's probably what you should be storing plaintext in.
It even has an easy class method to help you out with this if you already have a byte array:
+(id)stringWithCString:(const char *)cString encoding:(NSStringEncoding)enc
See the NSString documentation for more details.
As to storing it on the device, there are solutions that range from the simple (NSUserDefaults) to the complex (Core Data), but pretty much anything will expect plain text be in an NSString.
EDIT:
The title of this question talks about reading the string from the filesystem. First step is to get the bytes of the file into an NSData object. The easy way:
+(id)dataWithContentsOfFile:(NSString *)path
Then make a string out of the data with this initializer of NSString:
-(id)initWithData:(NSData *)data encoding:(NSStringEncoding)encoding
I don't know if this can help you, anyway if you just need to store an array of data to filesystem and deserialize it back to NSArray, an easy way is to use plists.
It is a convenient way to store a small amount of data, without any kind of relationship (there is Core Data for that). The main advantage is that you can store in it NSArray, NSDictionary, NSNumber, NSString, NSDate and NSData (so any kind of binary information) and they get automatically serialized and deserialized through some simple methods.
You can write an NSArray to a file in this way:
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)flag
and deserialize it back with this:
+ (id)arrayWithContentsOfFile:(NSString *)aPath
If you just want to provide some initial data to your app, and it is for example an array of strings or something similar, you can manually add a plist to your project by going to File->New->File and choosing Resources->Property list, and fill it by hand.
You can read more at https://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/PropertyLists/Introduction/Introduction.html

iOS - Strategy for saving and persisting JSON results with AFNetworking

I'm writing an iPhone app that loads Menu data from a database in the cloud. I am using AFNetworking (specifically AFJSONRequestOperation) to download the data in appDelegate.
Everything works fine up until this point, but I also need the app to be able to load the menu when the app is offline. To handle this, I want to save the returned JSON data to disc after it is retrieved by the AFJSONRequestOperation call.
My initial strategy was to save the returned JSON as a string, but I can't find a way to get the string response from an AFJSONRequestOperation. It seems silly to make two calls to the web service (one to return a JSON object and the other to return text) although it would be straightforward. I'd like to know if there is a better or more efficient way to do this. I could skip the AFJSONRequest and go for a plain AFHttpRequest but then I would need to construct the JSON object manually.
Is there a better option than either of the two I've thought up? In my opinion, the right answer would involve a single call that constructs the JSON object and also allows me to have access to the original text response, but I am open to hearing alternatives.
Don't save it as the JSON string level. AFNetworking will return the JSON as either an NSArray or an NSDictionary depending on your JSON structure.
You can just save the array or dictionary as a plist.
see -[NSDictionary writeToURL:atomically:] or -[NSArray writeToURL:atomically:]
I would try NSJSONSerialization to save NSData to disk.
Example:
NSData *dataToSave = [NSJSONSerialization dataWithJSONObject:responseObject options:nil error:nil];
id jsonObject = [NSJSONSerialization JSONObjectWithData:dataToSave options:nil error:nil];
Another option would be to take your responseObject from AFNetworking. Presumably a well-formed NSDictionary and simply place it in NSUserDefaults for semi-persistent storage. You need to make sure you don't have any null values in your dictionary as NSUserDefaults does not play nicely with null values.

Core Data and textfiles in iOS applications

I'm creating a simple iOS application consisting of a few UITableViewControllers. The information displayed in the view controllers will come from a text file (that I'll include in the project's Resources). The text file's contents will come from a spreadsheet.
Since this is my first time working with Core Data I have a few questions:
What format is most common for the text file? CSV, XML or something else?
What's the easiest way to import the data?
A few notes:
The data is static. Ideally the app will load the data into "Core Data" just once (1st time the app is run).
Each additional run of the app will just pull data from some Core Data source (that I'm not completely familiar w/ yet) instead of re-loading it from the textfile.
If the data is structured in a relational way then XML or JSON allows that structure to be easily preserved and then easily parsed and saved in your Core Data store. You'll need to use an XML or JSON parser, which will turn your data into an array of dictionaries (or multiple levels thereof if your data structure requires it). You'll simply iterate through the array and dig into the dictionaries (and sub-arrays and sub-dictionaries, if appropriate) and add objects to your store as you go.
If it's flat data, a simple single table that will become a single entity in Core Data, then tab-delimited or CSV text files are fine (and tab-delimited is even easier to parse if there wouldn't be any tabs within the data itself). You can then grab individual rows, break the rows down into an array of data bits (this is where tab delimiting makes is super-simple), create a new object for each row, set its properties to the array elements, and save the context.
The XML/JSON version is more complex than is worth writing out here -- search SO and you'll find lots of examples -- but here's the tab-delimited version (this assumes you don't have a gigantic ball of data that can't reasonably be held in memory):
// Standard Core Data setup here, grabbing the managedObjectContext,
// which is what I'll call it
// Then parse your text
NSString *path = [[NSBundle mainBundle] pathForResource:#"YourTextFileName" ofType:#"txt"];
NSString *content = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:NULL];
NSArray *rows = [content componentsSeparatedByString:#"\n"];
// Now that we have rows we can start creating objects
YourManagedObject *yourManagedObject = nil;
for (NSString *row in rows) {
NSArray *elements = [row componentsSeparatedByString:#"\t"];
YourManagedObject *yourManagedObject = (YourManagedObject *)[NSEntityDescription insertNewObjectForEntityForName:#"YourManagedObject" inManagedObjectContext:managedObjectContext;
[YourManagedObject setName:[elements objectAtIndex:0]];
[YourManagedObject setCountry:[elements objectAtIndex:1]];
// Etc. You may need an NSNumberFormatter and/or an NSDateFormatter to turn
// your strings into dates and numbers, depending on your data types
[managedObjectContext save];
}
Poof, all done.
If the data doesn't change, why bother including the text file in the app? Instead, create Core Data file on your Mac and include that as a resource in the app. I presume it's a lot of data that'll take a while to parse, so there's no sense in making your users each wait for that to happen when you could do the parsing once and distribute the result.
To make that happen, take the data model and the parsing code from your app and use them to build a small command-line app that just reads the text file, writes the Core Data file, and exits.

Resources