首页 > 代码库 > [iOS翻译]《iOS 7 Programming Cookbook》:iOS文件与文件夹管理(下)

[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:YES10                                     attributes:nil11                                          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  
      • 是否可读    
    • NSURLCreationDateKey  
      • 创建日期    
    • NSURLContentAccessDateKey  
      •  访问日期    
    • 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                                      NSURLContentModificationDateKey10                                      ];11 12      NSError *error = nil;13         NSArray *result = [manager contentsOfDirectoryAtURL:bundleDir14                                  includingPropertiesForKeys:propertiesToGet15                                                     options:016                                   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 =http://www.mamicode.com/ 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:path10                            atomically:YES11                              encoding:NSUTF8StringEncoding12                                 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 = [paramPath12                       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 @end6 7 @implementation AppDelegate8 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 name29          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:就行。

 

 

[iOS翻译]《iOS 7 Programming Cookbook》:iOS文件与文件夹管理(下)