Files & Archiving Lecture 8
Persistent Data
NSUserDefaults Dead simple to use Just one big file Only supports property list types What if you want more features?
File Tasks Finding the file path User selected Internal application data Saving or loading file data Text files, property lists, custom objects
Finding the File NSOpenPanel
Finding the File NSOpenPanel NSOpenPanel *panel = [NSOpenPanel openpanel]; [panel beginsheetfordirectory:nil file:nil types:[nsarray arraywithobject:@"jpg"] modalforwindow:window modaldelegate:self didendselector:@selector(openpaneldidend:returncode:contextinfo:) contextinfo:null]; - (void)openpaneldidend:(nssavepanel *)sheet { } returncode:(int)returncode contextinfo:(void *)contextinfo if (returncode == NSOKButton) [self openfrompath:[sheet filename]];
Finding the File NSSavePanel
Finding the File NSSavePanel NSSavePanel *panel = [NSSavePanel savepanel]; [panel setallowedfiletypes:[nsarray arraywithobject:@"txt"]]; [panel beginsheetfordirectory:nil file:nil modalforwindow:window modaldelegate:self didendselector:@selector(savepaneldidend:returncode:contextinfo:) contextinfo:null]; - (void)savepaneldidend:(nssavepanel *)sheet { } returncode:(int)returncode contextinfo:(void *)contextinfo if (returncode == NSOKButton) [self savetopath:[sheet filename]];
Finding the File Application Support Any other files associated with your application should go in ~/Library/ Application Support/YourApp Use the C function NSSearchPathForDirectoriesInDomains to find it Use NSFileManager to create your subdirectory
NSFileManager Creates, deletes, and moves files and directories Sets attributes and makes symbolic links Supports iterating through the files in a directory with contentsofdirectoryatpath:error:
Application Support & NSFileManager NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); if ([paths count] == 0) { // handle error } NSString *applicationsupport = [paths objectatindex:0]; NSString *appdir = [applicationsupport stringbyappendingpathcomponent:@"myappname"]; if (![[NSFileManager defaultmanager] fileexistsatpath:appdir]) [[NSFileManager defaultmanager] createdirectoryatpath:appdir attributes:nil];
NSDocument Used to create document-based applications Handles saving and loading, undo, printing, and some window management
Saving & Loading File Data
Text Files To initialize from a file, [[NSString alloc] initwithcontentsoffile:@"/a/b.txt" encoding:nsutf8stringencoding error:&error] To save to a file, [string writetofile:@"/a/b.txt" atomically:yes encoding:enc error:&error]
Collections of Property List Objects If you have an NSArray or NSDictionary of property list objects (instances of NSData, NSDate, NSNumber, NSString, NSArray, or NSDictionary), you can use writetofile:atomically: and initwithcontentsoffile: to easily read and write the collection See also: NSPropertyListSerialization
Other Classes Various other classes support saving and loading Look for initwithcontentsoffile: and writetofile: methods Some classes (NSImage, for example) use NSData for saving and loading
Reading NSData Represents a chunk of binary data Load from a file with initwithcontentsoffile:@"/a/b.txt" Load from a URL with initwithcontentsofurl:[nsurl URLWithString:@"http://google.com/"] Load from a C array with initwithbytes:arr length:len
Loading NSData Many Cocoa classes support loading from NSData Images [[NSImage alloc] initwithdata:data] Sounds [[NSSound alloc] initwithdata:data] PDFs [[PDFDocument alloc] initwithdata:data]
Generating NSData Many Cocoa classes can also be turned into NSData Attributed text to RTF data [attributedstring RTFFromRange:attributes:] PDF documents [pdfdocument datarepresentation] Images [image TIFFRepresentation]
Writing NSData Once you have an NSData object, you can write it to a file with [data writetofile:@"/a/b.ext" atomically:yes] NSData objects are also property list objects, meaning you can read and write them using NSUserDefaults, NSDictionary, or NSArray
What about custom objects?
NSCoding NSCoding is a protocol your class can implement to enable archiving and unarchiving A protocol specifies a set of methods your class must implement Theyʼre like Java interfaces, if youʼve seen those
NSCoding @interface MyObject : NSObject <NSCoding> { NSString *name; int age; } @property(nonatomic, retain) NSString *name; @property(nonatomic) int age; - (id)initwithname:(nsstring *)name age:(int)age; - (id)initwithcoder:(nscoder *)coder; - (void)encodewithcoder:(nscoder *)coder; @end
encodewithcoder: For each variable you want to save, call [coder encodetype:var forkey:@"key"] Each object you encode with encodeobject:forkey: must implement NSCoding as well If the superclass implements NSCoding, call [super encodewithcoder:coder]
encodewithcoder: - (void)encodewithcoder:(nscoder *)coder { [coder encodeobject:author forkey:@"author"]; [coder encodeobject:title forkey:@"title"]; [coder encodeobject:chapters forkey:@"chapters"]; [coder encodeint:callnumber forkey:@"callnumber"]; } An encodewithcoder: implementation for a Book class
initwithcoder: The inverse of encodewithcoder: for each encoded key, call [coder decodetypeforkey:@"key"] If itʼs an object, remember to retain it This is still an init method donʼt forget [super init] and return self
initwithcoder: - (id)initwithcoder:(nscoder *)coder { if (![super init]) { return nil; } author = [[coder decodeobjectforkey:@"author"] retain]; title = [[coder decodeobjectforkey:@"title"] retain]; chapters = [[coder decodeobjectforkey:@"chapters"] retain]; callnumber = [coder decodeintforkey:@"callnumber"]; return self; } An initwithcoder: implementation for the same Book class
Saving and Loading NSCoding Objects
NSKeyedArchiver & NSKeyedUnarchiver Handles reading and writing (to files or NSData) for NSCoding objects NSKeyedArchiver archiveddatawithrootobject: archiverootobject:tofile: NSKeyedUnarchiver unarchiveobjectwithdata: unarchiveobjectwithfile:
NSKeyedArchiver & NSKeyedUnarchiver MyObject *x = [[MyObject alloc] init], *y; NSString *f = @"/Path/to/file.ext"; [NSKeyedArchiver archiverootobject:x tofile:f]; y = [NSKeyedUnarchiver unarchiveobjectwithfile:f]; // x and y should now have the same data