三. 创建文件夹
问题:
你想创建文件夹到磁盘,存储一些文件到里面
解决方案:
使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:就行。