三. 创建文件夹

问题:

你想创建文件夹到磁盘,存储一些文件到里面

 

解决方案:

使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  
  • 是否可读    
  • 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                                      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:就行。