[iOS翻译]《iOS 7 Programming Cookbook》:iOS文件与文件夹管理(下)
三. 创建文件夹
问题:
你想创建文件夹到磁盘,存储一些文件到里面
解决方案:
使NSFileManager类的实例方法createDirectoryAtPath:withIntermediateDirectories:attributes:error:,代码如下:
1 - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ 2 NSFileManager *fileManager = [[NSFileManager alloc] init]; 3 4 NSString *tempDir = NSTemporaryDirectory(); 5 NSString *imagesDir = [tempDir stringByAppendingPathComponent:@"images"]; 6 7 NSError *error = nil; 8 if ([fileManager createDirectoryAtPath:imagesDir 9 withIntermediateDirectories:YES 10 attributes:nil 11 error:&error]){ 12 NSLog(@"Successfully created the directory."); 13 14 } else { 15 NSLog(@"Failed to create the directory. Error = %@", error); 16 } 17 18 self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 19 self.window.backgroundColor = [UIColor whiteColor]; 20 [self.window makeKeyAndVisible]; 21 return YES; 22 }
讨论
NSFileManager如此简洁易用,仅用几行就搞定了文件夹创建,下面是方法参数:
- createDirectoryAtPath
- 创建文件夹的路径
- withIntermediateDirectories
- BOOL类型。如果设为YES,将会自动补全最终文件夹之前的中间目录
- 例如,如果你想在tmp/data目录创建一个images文件夹,但data文件夹并不存在,怎么办?只需要把withIntermediateDirectories参数设为YES,系统就会自动创建目录tmp/data/images/
- attributes
- 通常设为nil
- error
- 接受一个指针指向NSError对象
四. 枚举文件/文件夹
问题:
你想在一个文件夹里枚举文件/文件夹列表,这个枚举动作意味着你想找到所有文件/文件夹
解决方案:
使用NSFileManager类的实例方法contentsOfDirectoryAtPath:error:。例如我们想要在bundle文件夹下枚举所有文件/文件夹,代码如下:
1 - (BOOL) application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ 2 NSFileManager *fileManager = [[NSFileManager alloc] init]; 3 NSString *bundleDir = [[NSBundle mainBundle] bundlePath]; 4 NSError *error = nil; 5 NSArray *bundleContents = [fileManager 6 contentsOfDirectoryAtPath:bundleDir 7 error:&error]; 8 9 if ([bundleContents count] > 0 && error == nil){ 10 NSLog(@"Contents of the app bundle = %@", bundleContents); 11 } 12 else if ([bundleContents count] == 0 && error == nil){ 13 NSLog(@"Call the police! The app bundle is empty."); 14 } 15 else { 16 NSLog(@"An error happened = %@", error); 17 } 18 19 self.window = [[UIWindow alloc] 20 initWithFrame:[[UIScreen mainScreen] bounds]]; 21 self.window.backgroundColor = [UIColor whiteColor]; 22 [self.window makeKeyAndVisible]; 23 return YES; 24 }
讨论
在一些APP里,你可能需要枚举一个文件夹的内容,让我们来看一个例子吧。
想象一下,用户从网络下载了10张图片并缓存到APP里,这时你需要存储它们,也就是说,你要手动创建tmp/images/目录。现在用户关闭后又打开了APP,在界面上,你想要在一个table view里展示下载完成列表,该如何实现?
其实很简单,需要做的就是在目录里使用NSFileManager类进行内容枚举。在解决方案部分,你已经使用了NSFileManager类的实例方法contentsOfDirectoryAtPath:error:进行枚举,然而这个方法并不能指出哪个是文件,哪个是文件夹等等。想要从文件管理里获取更多细节,调用方法contentsOfDirectoryAtURL:includingPropertiesForKeys:options:error:,参数如下:
- contentsOfDirectoryAtURL
- 想要检索的文件夹路径(NSURL类型)
- includingPropertiesForKeys
- NSArray类型,代表检索目录下个各个条目的信息
- NSURLIsDirectoryKey
- 是否是一个字典
-
- NSURLIsReadableKey
- 是否可读
- NSURLIsReadableKey
-
- NSURLCreationDateKey
- 创建日期
- NSURLCreationDateKey
-
- NSURLContentAccessDateKey
- 访问日期
- NSURLContentAccessDateKey
-
- NSURLContentModificationDateKey
- 修改日期
- 修改日期
- NSURLContentModificationDateKey
- options
- 参数只能为0或NSDirectoryEnumerationSkipsHiddenFiles,后者在枚举时会跳过隐藏内容
- error
- 接受一个指针指向NSError对象
现在我们在XXX.app目录下进行枚举,并打印各条目的名字、是否为字典、是否可读以及创建/最后修改/最后访问的日期,代码如下;
1 - (NSArray *) contentsOfAppBundle{ 2 NSFileManager *manager = [[NSFileManager alloc] init]; NSURL *bundleDir = [[NSBundle mainBundle] bundleURL]; 3 4 NSArray *propertiesToGet = @[ 5 NSURLIsDirectoryKey, 6 NSURLIsReadableKey, 7 NSURLCreationDateKey, 8 NSURLContentAccessDateKey, 9 NSURLContentModificationDateKey 10 ]; 11 12 NSError *error = nil; 13 NSArray *result = [manager contentsOfDirectoryAtURL:bundleDir 14 includingPropertiesForKeys:propertiesToGet 15 options:0 16 error:&error]; 17 18 if (error != nil){ 19 NSLog(@"An error happened = %@", error); 20 } 21 return result; 22 } 23
1 - (NSString *) stringValueOfBoolProperty:(NSString *)paramProperty ofURL:(NSURL *)paramURL{ 2 NSNumber *boolValue = nil; 3 NSError *error = nil; 4 [paramURL getResourceValue:&boolValue 5 forKey:paramProperty 6 error:&error]; 7 8 if (error != nil){ 9 NSLog(@"Failed to get property of URL. Error = %@", error); 10 } 11 return [boolValue isEqualToNumber:@YES] ? @"Yes" : @"No"; 12 }
1 - (NSString *) isURLDirectory:(NSURL *)paramURL{ 2 return [self stringValueOfBoolProperty:NSURLIsDirectoryKey ofURL:paramURL]; 3 } 4 5 - (NSString *) isURLReadable:(NSURL *)paramURL{ 6 return [self stringValueOfBoolProperty:NSURLIsReadableKey ofURL:paramURL]; 7 }
1 - (NSDate *) dateOfType:(NSString *)paramType inURL:(NSURL *)paramURL{ 2 NSDate *result = nil; 3 NSError *error = nil; 4 [paramURL getResourceValue:&result 5 forKey:paramType 6 error:&error]; 7 8 if (error != nil){ 9 NSLog(@"Failed to get property of URL. Error = %@", error); 10 } 11 return result; 12 }
1 - (void) printURLPropertiesToConsole:(NSURL *)paramURL{ 2 3 NSLog(@"Item name = %@", [paramURL lastPathComponent]); 4 5 NSLog(@"Is a Directory? %@", [self isURLDirectory:paramURL]); 6 7 NSLog(@"Is Readable? %@", [self isURLReadable:paramURL]); 8 9 NSLog(@"Creation Date = %@", 10 [self dateOfType:NSURLCreationDateKey inURL:paramURL]); 11 12 NSLog(@"Access Date = %@", 13 [self dateOfType:NSURLContentAccessDateKey inURL:paramURL]); 14 15 NSLog(@"Modification Date = %@", 16 [self dateOfType:NSURLContentModificationDateKey inURL:paramURL]); 17 18 NSLog(@"-----------------------------------"); 19 }
1 - (BOOL) application:(UIApplication *)application 2 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ 3 NSArray *itemsInAppBundle = [self contentsOfAppBundle]; 4 for (NSURL *item in itemsInAppBundle){ 5 [self printURLPropertiesToConsole:item]; 6 } 7 8 self.window = [[UIWindow alloc] 9 initWithFrame:[[UIScreen mainScreen] bounds]]; 10 // Override point for customization after application launch. 11 self.window.backgroundColor = [UIColor whiteColor]; 12 [self.window makeKeyAndVisible]; 13 return YES; 14 }
输出结果类似下列信息:
Item name = Assets.car Is a Directory? No Is Readable? Yes Creation Date = 2013-06-25 16:12:53 +0000 Access Date = 2013-06-25 16:12:53 +0000 Modification Date = 2013-06-25 16:12:53 +0000 ----------------------------------- Item name = en.lproj Is a Directory? Yes Is Readable? Yes Creation Date = 2013-06-25 16:12:53 +0000 Access Date = 2013-06-25 16:15:02 +0000 Modification Date = 2013-06-25 16:12:53 +0000 ----------------------------------- Item name = Enumerating Files and Folders Is a Directory? No Is Readable? Yes Creation Date = 2013-06-25 16:15:01 +0000 Access Date = 2013-06-25 16:15:04 +0000 Modification Date = 2013-06-25 16:15:01 +0000 -----------------------------------
我们来看看这段代码的一些方法:
- contentsOfAppBundle
- 这个方法搜索.app文件夹的所有条目(文件、文件夹等等),并返回一个数组
- 数组里的所有条目都为NSURL类型,并包含了创建/最后修改日期以及其他之前提到的属性
- stringValueOfBoolProperty:ofURL:
- 返回与BOOL类型相当的NSString(YES或NO),用来判断URL的信息
- isURLDirectory:
- 是否是字典
- isURLReadable:
- 是否可读
- dateOfType:inURL:
- 日期类型
现在,你已经知道如何在一个文件夹里遍历所有条目,甚至学会了检索各个条目的不同属性。
五. 删除文件/文件夹
问题:
你想删除一些文件或文件夹
解决方案:
使用NSFileManager类的实例方法removeItemAtPath:error:(接收string)或者removeItemAtURL:error:(接收URL)
讨论
删除文件/文件夹应该是使用file manager时最简单的操作了。现在,让我们来创建5个text文件到tmp/text目录,然后进行删除操作,代码如下;
1 /* Creates a folder at a given path */ 2 - (void) createFolder:(NSString *)paramPath{ 3 NSError *error = nil; 4 if ([self.fileManager createDirectoryAtPath:paramPath 5 withIntermediateDirectories:YES 6 attributes:nil 7 error:&error] == NO){ 8 NSLog(@"Failed to create folder %@. Error = %@", 9 paramPath, 10 error); 11 } 12 }
1 /* Creates 5 .txt files in the given folder, named 1.txt, 2.txt, etc */ 2 - (void) createFilesInFolder:(NSString *)paramPath{ 3 /* Create 10 files */ 4 for (NSUInteger counter = 0; counter < 5; counter++){ 5 NSString *fileName = [NSString stringWithFormat:@"%lu.txt", (unsigned long)counter+1]; 6 NSString *path = [paramPath stringByAppendingPathComponent:fileName]; 7 NSString *fileContents = [NSString stringWithFormat:@"Some text"]; 8 NSError *error = nil; 9 if ([fileContents writeToFile:path 10 atomically:YES 11 encoding:NSUTF8StringEncoding 12 error:&error] == NO){ 13 NSLog(@"Failed to save file to %@. Error = %@", path, error); 14 } 15 } 16 }
1 /* Enumerates all files/folders at a given path */ 2 - (void) enumerateFilesInFolder:(NSString *)paramPath{ 3 NSError *error = nil; 4 NSArray *contents = [self.fileManager contentsOfDirectoryAtPath:paramPath 5 error:&error]; 6 7 if ([contents count] > 0 && error == nil){ 8 NSLog(@"Contents of path %@ = \n%@", paramPath, contents); 9 } 10 else if ([contents count] == 0 && error == nil){ 11 NSLog(@"Contents of path %@ is empty!", paramPath); 12 } 13 else { 14 NSLog(@"Failed to enumerate path %@. Error = %@", paramPath, error); 15 } 16 }
1 /* Deletes all files/folders in a given path */ 2 - (void) deleteFilesInFolder:(NSString *)paramPath{ 3 NSError *error = nil; 4 NSArray *contents = [self.fileManager contentsOfDirectoryAtPath:paramPath error:&error]; 5 6 if (error == nil){ 7 error = nil; 8 for (NSString *fileName in contents){ 9 /* We have the file name, to delete it, 10 we have to have the full path */ 11 NSString *filePath = [paramPath 12 stringByAppendingPathComponent:fileName]; 13 if ([self.fileManager removeItemAtPath:filePath error:&error] == NO){ 14 NSLog(@"Failed to remove item at path %@. Error = %@", fileName, error); 15 } 16 } 17 } else { 18 NSLog(@"Failed to enumerate path %@. Error = %@", paramPath, error); 19 } 20 }
1 /* Deletes a folder with a given path */ 2 - (void) deleteFolder:(NSString *)paramPath{ 3 NSError *error = nil; 4 if ([self.fileManager removeItemAtPath:paramPath error:&error] == NO){ 5 NSLog(@"Failed to remove path %@. Error = %@", paramPath, error); 6 } 7 }
1 #import "AppDelegate.h" 2 3 @interface AppDelegate () 4 @property (nonatomic, strong) NSFileManager *fileManager; 5 @end 6 7 @implementation AppDelegate 8 9 <# Rest of your app delegate code goes here #>
示例代码结合了这一章的很多知识,下面来看一看这些方法的步骤:
- 1.创建了tmp/txt目录。我们知道tmp文件夹在APP安装时自动创建,但txt文件夹则需要手动创建
- 2.在tmp/txt目录创建5个text文件
- 3.在tmp/txt目录枚举所有文件
- 4.在tmp/txt目录删除文件
- 5.再次在tmp/txt目录枚举剩下的文件
- 6.在tmp/txt目录删除文件夹
现在你不光学会了如何创建文件/文件夹,还学会了如何在不需要时删除它们。
六. 存储对象到文件
问题:
你添加了一些新类到项目里,这时你想把这些对象作为文件存储到磁盘里,当需要时能随时读取
解决方案:
确保你的类遵从NSCoding协议,并且实现协议必要方法
讨论
iOS SDK里有两个非常方便的类来实现这一目标,在编程世界里称为marshalling,它们是:
- NSKeyedArchiver
- 归档
- NSKeyedUnarchiver
- 解档
为了实现归档/解档工作,需要遵从NSCoding协议,现在来创建一个简单的Person类,头文件如下:
1 #import <Foundation/Foundation.h> 2 3 @interface Person : NSObject <NSCoding> 4 5 @property (nonatomic, copy) NSString *firstName; 6 @property (nonatomic, copy) NSString *lastName; 7 8 @end
协议要求必须实现两个方法:
- - (void)encodeWithCoder:(NSCoder *)aCoder
- 给你一个coder,使用方法类似字典
- - (instancetype)initWithCoder:(NSCoder *)aDecoder
- 当你试图使用NSKeyedUnarchiver进行解档时,这个方法会被调用
现在来来到实现文件,代码如下:
1 #import "Person.h" 2 3 NSString *const kFirstNameKey = @"FirstNameKey"; 4 NSString *const kLastNameKey = @"LastNameKey"; 5 6 @implementation Person 7 8 - (void)encodeWithCoder:(NSCoder *)aCoder{ 9 [aCoder encodeObject:self.firstName forKey:kFirstNameKey]; 10 [aCoder encodeObject:self.lastName forKey:kLastNameKey]; 11 } 12 13 - (instancetype)initWithCoder:(NSCoder *)aDecoder{ self = [super init]; 14 if (self != nil){ 15 _firstName = [aDecoder decodeObjectForKey:kFirstNameKey]; 16 _lastName = [aDecoder decodeObjectForKey:kLastNameKey]; 17 } 18 return self; 19 } 20 21 @end
接着在AppDelegate实现如下方法:
1 #import "AppDelegate.h" 2 #import "Person.h" 3 4 @implementation AppDelegate 5 6 - (BOOL) application:(UIApplication *)application 7 didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{ 8 /* Define the name and the last name we are going to set in the object */ 9 NSString *const kFirstName = @"Steven"; 10 NSString *const kLastName = @"Jobs"; 11 12 /* Determine where we want to archive the object */ 13 NSString *filePath = [NSTemporaryDirectory() 14 stringByAppendingPathComponent:@"steveJobs.txt"]; 15 16 /* Instantiate the object */ 17 Person *steveJobs = [[Person alloc] init]; 18 steveJobs.firstName = kFirstName; 19 steveJobs.lastName = kLastName; 20 21 /* Archive the object to the file */ 22 [NSKeyedArchiver archiveRootObject:steveJobs toFile:filePath]; 23 24 /* Now unarchive the same class into another object */ 25 Person *cloneOfSteveJobs = 26 [NSKeyedUnarchiver unarchiveObjectWithFile:filePath]; 27 28 /* Check if the unarchived object has the same first name and last name 29 as the previously-archived object */ 30 if ([cloneOfSteveJobs.firstName isEqualToString:kFirstName] 31 && [cloneOfSteveJobs.lastName isEqualToString:kLastName]){ 32 NSLog(@"Unarchiving worked"); 33 } else { 34 NSLog(@"Could not read the same values back. Oh no!"); 35 } 36 37 /* We no longer need the temp file, delete it */ 38 NSFileManager *fileManager = [[NSFileManager alloc] init]; 39 [fileManager removeItemAtPath:filePath error:nil]; 40 self.window = [[UIWindow alloc] 41 initWithFrame:[[UIScreen mainScreen] bounds]]; 42 self.window.backgroundColor = [UIColor whiteColor]; 43 [self.window makeKeyAndVisible]; 44 return YES; 45 }
可见,归档只需要使用NSKeyedArchiver类的类方法archiveRootObject:toFile就能搞定,那如何解档呢?使用另外一个类方法unarchiveObjectWithFile:就行。