本文主要介绍以下几部分:
一、NSFileManager
NSFileManager 是用来管理文件系统的,可以进行常见的文件/文件夹操作(拷贝、剪切、创建等)。
NSFileManager使用了单例模式(singleton),可以使用 NSFileManager 的类方法获得那个默认的单例对象:
+ (NSFileManager *)defaultManager;
1、常见操作:
基本操作:
- (BOOL)fileExistsAtPath:(NSString *)path;
//判断在路径 path 的文件或文件夹是否存。存在则返回YES,不存在或者路径不确定则返回NO
- (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory;
//判断在路径path的文件或文件夹是否存在,并判断是否为文件夹
//形参isDirectory是BOOL指针,可传进一个BOOL变量的地址,如果判断结果path 是文件夹,则该BOOL变量为YES,否则为NO
- (BOOL)isReadableFileAtPath:(NSString *)path;
// 判断文件(文件夹)是否可读
- (BOOL)isWritableFileAtPath:(NSString *)path;
// 判断文件(文件夹)是否可写
- (BOOL)isExecutableFileAtPath:(NSString *)path;
// 判断文件(文件夹)是否可执行
- (BOOL)isDeletableFileAtPath:(NSString *)path;
// 判断文件(文件夹)是否可删除
深入了解:
- (NSDictionary *)attributesOfItemAtPath:(NSString *)path error:(NSError **)error NS_AVAILABLE(10_5, 2_0);
// 获取文件(文件夹)的属性,返回的是 NSDictionary,其中的键值对存放了文件(文件夹)的属性
- (NSArray *)subpathsAtPath:(NSString *)path;
// 获取指定路径下所有的子路径,返回一个数组,每个元素都是表示子项目的路径的字符串。
//该方法会深度寻找,不限于当前层,也会查找package(如app,nib文件,RTFD文件)的内容,但非常好内存,一般避免使用。
- (NSArray *)subpathsOfDirectoryAtPath:(NSString *)path error:(NSError **)error NS_AVAILABLE(10_5, 2_0);
// 获取path路径下所有的子路径。与上一方法相同。
- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error NS_AVAILABLE(10_5, 2_0);
// 获取path的当前子路径(path下的所有直接子路径,不包括子路径的子路径)。path必须是一个目录。
- (NSData *)contentsAtPath:(NSString *)path;
// 获取文件内容,返回的是NSData类型的对象
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error NS_AVAILABLE(10_5, 2_0);
// 只能创建文件夹,参数:path是要创建的文件夹路径,createIntermediates 为 YES 则表示允许系统自动创建中间的文件夹。
// 当指定路径中的文件夹都存在或者创建成功时,返回YES;否则返回NO
// 代码说明中说“指定的文件夹在调用这方法前不能已经存在”,但在Xcode6.1中测试中如果已经存在则不重复创建但也不报错?
- (BOOL)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error NS_AVAILABLE(10_5, 2_0);
// 拷贝文件,srcPath 为要拷贝的文件路径,dstPath 为目标文件路径。
// 如果目标目录已经存在同名文件,则无法拷贝。
- (BOOL)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error NS_AVAILABLE(10_5, 2_0);
// 移动文件(剪切)。如果目标目录已经存在同名文件,则无法移动。
- (BOOL)removeItemAtPath:(NSString *)path error:(NSError **)error NS_AVAILABLE(10_5, 2_0);
// 删除文件
- (BOOL)createFileAtPath:(NSString *)path contents:(NSData *)data attributes:(NSDictionary *)attr;
// 在指定路径path(应该是个文件)创建文件,内容为data。
// NSData 是用来存储二进制字节数据的
2、以上方法实例操作如下:
// 定义文件路径和文件夹路径
NSString *path = @"/Users/yons/Desktop/f.txt";
NSString *pathDir = @"/Users/yons/Desktop/";
// 获取文件单例对象
NSFileManager *fileManager = [NSFileManager defaultManager];
// 判断文件(或文件夹)是否存在
BOOL isExist = [fileManager fileExistsAtPath:path];
// isExist == YES
// 定义 isDir 来存储是否为文件夹(开始默认值为NO)
BOOL isDir;
// 判断是否存在
BOOL isExist_2 = [fileManager fileExistsAtPath:pathDir isDirectory:&isDir];
// isExist_2 == YES, isDir == YES
// 判断是否可读
BOOL isRead = [fileManager isReadableFileAtPath:pathDir];
// isRead == YES
// 判断是否可写
BOOL isWrite = [fileManager isWritableFileAtPath:@"/usr/"];
// isWrite == NO
// 判断是否可执行
BOOL isExe = [fileManager isExecutableFileAtPath:pathDir];
// isExe == YES
// 判断是否可删
BOOL isDeletable = [fileManager isDeletableFileAtPath:path];
// isDelete == YES
// 获取属性信息
NSDictionary *dict = [fileManager attributesOfItemAtPath:path error:nil];
//NSLog(@"%@", dict);
// 输出结果:
// {
// (摘取部分)
// NSFileCreationDate = "2015-11-18 11:04:06 +0000"; //创建时间
// NSFileExtendedAttributes = {
// "com.apple.TextEncoding" = <7574662d 383b3133 34323137 393834>;
// };
// NSFileExtensionHidden = 0;
// NSFileSize = 7; // 文件大小
// NSFileType = NSFileTypeRegular; // 文件类型
// ......
// }
// 查找指定路径下的所有子路径
NSArray *pathArr = [fileManager subpathsAtPath:pathDir];
// 输出结果: pathArr = (
// "C:OC\U8f85\U52a9\U56fe/0305.png", //表示该路径下某图片的路径
// "C:OC\U8f85\U52a9\U56fe/0306.png",
// "C:OC\U8f85\U52a9\U56fe/0307.png",
// "C:OC\U8f85\U52a9\U56fe/0401.png",
// "C:OC\U8f85\U52a9\U56fe/0402.png",
// "C:OC\U8f85\U52a9\U56fe/0403.png",
// "C:OC\U8f85\U52a9\U56fe/0404.png",
// )
// 获取path的当前子路径
NSArray *conOfDir = [fileManager contentsOfDirectoryAtPath:path error:nil];
// 获取文件内容
NSData *conAtPath = [fileManager contentsAtPath:path];
NSLog(@"pathArr = %@", conAtPath);
// 输出结果:pathArr = <77656c63 6f6d65> (原文件文本是“welcome”)
// 依指定路径,创建文件夹
NSString *newDir = @"/Users/yons/Desktop/newDir/test/";
BOOL isCreateDir = [fileManager createDirectoryAtPath:newDir withIntermediateDirectories:YES attributes:nil error:nil];
// isCreateDir == YES。在桌面文件夹下创建了newDir文件夹,该文件夹下又创建了test文件夹
// 拷贝文件
// 要拷贝的文件路径:
NSString *sourcePath = @"/Users/yons/Desktop/f.txt";
// 目标文件路径:
NSString *goalPath = @"/Users/yons/Desktop/newDir/f_2.txt";
BOOL isCopy = [fileManager copyItemAtPath:sourcePath toPath:goalPath error:nil];
// isCopy == YES. 把桌面的 f.txt 文件 拷贝一份到 桌面的 newDir 文件夹中去,文件名为 f_2.txt
// 移动文件(剪切)
NSString *moveToPath = @"/Users/yons/Desktop/newDir/f_1.txt";
BOOL isMove = [fileManager moveItemAtPath:sourcePath toPath:moveToPath error:nil];
// isMove == YES。把桌面的 f.txt 文件移动到桌面的 newDir 文件夹中去,文件名为 f_1.txt
// 删除文件
BOOL isDelete = [fileManager removeItemAtPath:goalPath error:nil];
// isDelete == YES. 把桌面的 newDir 文件夹中的 f_2.txt 文件删除了
// 创建文件
// 创建文件内容
NSString *textOfFile = @"Hello World";
// 把字符串转换为 NSData
NSData *dataOfFile = [textOfFile dataUsingEncoding:NSUTF8StringEncoding];
BOOL isWriteText = [fileManager createFileAtPath:path contents:textOfFile attributes:nil];
// isWriteText == YES。在桌面新建了一个文件,文件名为 f.txt,内容为"Hello World"
3、NSFileManager文件下载思路:
二、copy & mutableCopy
copy,即复制或拷贝,是利用一个源对象产生一个副本对象。目的是:要使用某个对象的数据,但又要在修改对象的时候不影响原来的对象内容。
特点:修改源文件(或副本文件)的内容,不会影响副本文件(或源文件)。
复制分为:
浅拷贝:拷贝引用对象的指针。
深拷贝:拷贝引用对象的内容。
使用:用对象方法 copy 或 mutableCopy
copy方法: 创建的是不可变副本(如 NSString, NSArray, NSDictionary)
mutableCopy方法: 创建的是可变副本(如 NSMutableString, NSMutableArray, NSMutableDictionary)
使用copy方法的前提:需要遵守NSCopying协议、实现copyWithZone:方法
@protocol NSCopying
// NSCopying协议也只有这一个方法
- (id)copyWithZone:(NSZone *)zone;
@end
使用mutableCopy方法的前提:需要遵守NSMutableCopying协议、实现mutableCopyWithZone:方法
@protocol NSMutableCopying
//NSMutableCopying协议也只有这一个方法
- (id)mutableCopyWithZone:(NSZone *)zone;
@end
1、copy使用:
以NSString为例:
NSString *str = [NSString stringWithFormat:@"year is %d", 2015];
// str 地址:0x100114540. 堆区,不可变
NSString *str_copy_s = [str copy];
// str_copy_s 地址:0x100114540,与 str 相同,浅拷贝,不可变
NSMutableString *str_copy_m = [str copy];
// str_copy_m 地址:0x100114540,与 str 相同,浅拷贝,不可变
NSString *str_mcopy_s = [str mutableCopy];
// str_mcopy_s 地址:0x100114730,与 str 不相同,深拷贝,不可变
NSMutableString *str_mcopy_m = [str mutableCopy];
// str_mcopy_m 地址:0x100114770,与 str 不相同,深拷贝,可变
// 引用计数器:str.retainCount == str_copy_s.retainCount == str_copy_m.retainCount == 3
// 浅复制,源对象的引用计数器 + 1,相当于做了一个 retain 操作
NSMutableString *mstr = [NSMutableString stringWithFormat:@"month is %d", 11];
// mstr 地址:0x100114810. 堆区,可变
NSString *mstr_copy_s = [mstr copy];
// mstr_copy_s 地址:0x186b255e163fdeb5,与 mstr 不相同,深拷贝,不可变
NSMutableString *mstr_copy_m = [mstr copy];
// mstr_copy_m 地址:0x186b255e163fdeb5,与 mstr 不相同,深拷贝,不可变
NSString *mstr_mcopy_s = [mstr mutableCopy];
// mstr_mcopy_s 地址:0x100114850,与 mstr 不相同,深拷贝,不可变
NSMutableString *mstr_mcopy_m = [mstr mutableCopy];
// mstr_mcopy_m 地址:0x1001148d0,与 mstr 不相同,深拷贝,可变
// 引用计数器:mstr.retainCount == mstr_mcopy_s.retainCount == mstr_mcopy_m.retainCount == 1
// 深复制,创建一个新对象,源对象、新对象的引用计数器都为1
所以 copy 用在NSString对象上,与 retain 用在其他 OC 对象上的效果类似。对比下:
使用注意:
(1)浅拷贝是复制一个对象的指针,会使计数器加1,所以也要用release将其减1
(2)深拷贝用在:要将一个对象从可变(不可变)转为不可变(可变)、或者将一个对象内容克隆一份的情况。
2、总结下 @property内存管理策略的选择:
3、 为自定义的类实现copy操作
NSObject类本身不遵守 NSCopying 或 NSMutableCopying 协议,它的子类要遵守 NSCopying 才能发送 copy消息,要遵守 NSMutableCopying 才能发送 mutableCopy 消息。
步骤:
// Book.h
#import <Foundation/Foundation.h>
@interface Book : NSObject<NSCopying> // 1.让类遵守 NSCopying 协议
// 两个实例变量
@property (nonatomic, assign) int page;
@property (nonatomic, copy) NSString *author;
@end
// Book.m
#import "Book.h"
@implementation Book
-(id)copyWithZone:(NSZone *)zone{ // 2.实现copyWithZone:方法
// zone表示空间,但它是古老的技术,现在一般忽略它。
// 创建一个新的对象,作为副本对象
Book *book_copy = [[Book alloc] init];
// 将源对象的实例变量一一赋值给副本对象的对应的实例变量
book_copy.page = self.page;
book_copy.author = self.author;
//返回副本对象
return book_copy; // 3.返回一个数据与现有对象数据一样的新对象
}
@end
// main.m
#import <Foundation/Foundation.h>
#import "Book.h"
int main(int argc, const char * argv[]) {
@autoreleasepool {
// 创建源对象book
Book *book = [Book new];
// 设置book 的实例变量
book.page = 100;
book.author = @"luxun";
// 拷贝book到副本对象 book_2
// 调用父类的 copy 方法,即调用实现了的 copyWithZone: 方法
Book *book_2 = [book copy];
// 输出源对象和副本对象的引用计数器和地址
NSLog(@"%tu, %p", book.retainCount, book);
NSLog(@"%tu, %p", book_2.retainCount, book_2);
}
return 0;
}
//输出结果:
2015-11-18 16:19:11.718 CopyTest[1605:130941] 1, 0x100206fa0
2015-11-18 16:19:11.719 CopyTest[1605:130941] 1, 0x1002070c0
//注意到源对象book与副本对象book_2的retainCount都为1,两者的地址不相同。
如果要让类能进行mutableCopy操作,则步骤类似:
@interface Book : NSObject<NSMutableCopying> // 1.让类遵守 NSMutableCopying协议
@end
@implementation Book
-(id)mutableCopyWithZone:(NSZone *)zone{ // 2.实现mutableCopyWithZone:方法
Book *book_mcopy = [[Book alloc] init];
book_mcopy.page = self.page;
book_mcopy.author = self.author;
return book_mcopy; // 3.返回一个数据与现有对象数据一样的新对象
}
@end
//使用:
Book *book = [Book new];
Book *book_2 = [book copy];
Book *book_3 = [book mutableCopy];
NSLog(@"%tu, %p", book.retainCount, book);
NSLog(@"%tu, %p", book_2.retainCount, book_2);
NSLog(@"%tu, %p", book_3.retainCount, book_3);
//输出结果:
2015-11-18 16:34:08.525 CopyTest[1647:135370] 1, 0x1001106f0
2015-11-18 16:34:08.526 CopyTest[1647:135370] 1, 0x100114ba0
2015-11-18 16:34:08.526 CopyTest[1647:135370] 1, 0x100114cc0
//源对象和两种方法copy得到的副本对象的地址都是不同的