iOS开发之基础篇(6)—— NSFileManager文件管理器

版本

Xcode 8.2.1

一、NSHomeDirectory()

目录(Directories)在现在的操作系统里就是文件夹,而路径(Path)则是包括盘符及一个或多个文件夹。

iOS每个APP都会有一个沙盒(Sandbox),用于存储该APP所产生的资料内容,如图片、视频、文件夹、plist文件等等。而这个沙盒的路径可由NSHomeDirectory()得到,也叫APP的根目录。根目录下有四个文件/文件夹:

  • Documents 目录:Apple官方建议将APP的重要数据保存到这个目录下。因为iTunes备份时包括此目录。
  • MyApp.app 目录:这是APP本身。一般不要对其更改,否则启动时容易崩溃。
  • Library 目录:该目录下有两个子目录:Preferences和Caches;
    • Preferences:包含APP的偏好设置。可用NSUserDefaults类来获取或设置;
    • Caches:APP专用的支持文件,保存APP再次启动过程中需要的信息。
  • tmp 目录:存放临时文件,APP关闭后,该目录下文件将被清除。

接下来我们小试一把,通过NSHomeDirectory()获取APP的根目录,并对其目录路径搞点小动作。

int main(int argc, char * argv[]) {

    //获取根目录
    NSString *userPath = NSHomeDirectory();
    NSLog(@"path = %@",userPath);

    //拼接路径
    NSString *newPath = [userPath stringByAppendingFormat:@"%@",@"/test"];
    NSLog(@"newPath = %@",newPath);

    //添加子路径
    NSString *newPath2 = [userPath stringByAppendingPathComponent:@"test"];
    NSLog(@"newPath2 = %@",newPath2);

    //切割路径x
    NSArray *resultPath = [userPath pathComponents];
    NSLog(@"resultPath = %@",resultPath);

    //再次拼接路径
    NSString *newPath1 = [userPath stringByAppendingFormat:@"%@",@"/test/lalala.jpg"];
    NSLog(@"newPath1 = %@",newPath1);

    //获取文件后缀名
    NSString *resultStr = [newPath1 pathExtension];
    NSLog(@"resultStr = %@",resultStr);

    //删除后缀名
    NSString *resultPath1 = [newPath1 stringByDeletingPathExtension];
    NSLog(@"resultPath1 = %@",resultPath1);

    //删除最后一个子路径
    NSString *resultPath2 = [newPath1 stringByDeletingLastPathComponent];
    NSLog(@"resultPath2 = %@",resultPath2);
}

收获如下:

获取目录路径的方法小结:

    // 获取根目录
    NSString *homeDir = NSHomeDirectory();
    NSLog(@"homeDir=%@", homeDir);
    // homeDir=/var/mobile/Containers/Data/Application/D3765F06-44E4-4B04-8CB0-2E92B7F07997
    
    // 获取Documents目录
    NSArray *docPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *docDir = [docPaths objectAtIndex:0];
    NSLog(@"docDir=%@", docDir);
    // docDir=/var/mobile/Containers/Data/Application/D3765F06-44E4-4B04-8CB0-2E92B7F07997/Documents

    // 获取Caches目录
    NSArray *cachePaths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachesDir = [cachePaths objectAtIndex:0];
    NSLog(@"cachesDir=%@", cachesDir);
    // cachesDir=/var/mobile/Containers/Data/Application/D3765F06-44E4-4B04-8CB0-2E92B7F07997/Library/Caches
    
    // 获取tmp目录
    NSString *tmpDir = NSTemporaryDirectory();
    NSLog(@"tmpDir=%@", tmpDir);
    // tmpDir=/private/var/mobile/Containers/Data/Application/D3765F06-44E4-4B04-8CB0-2E92B7F07997/tmp/

注意:
使用NSTemporaryDirectory()方法获取tmp目录的时候, 后面多了一个/, 后面多了一个/, 后面多了一个/.

二、NSFileManager

对于这些文件路径的操作(移动、复制、连接或者删除等等),往往会很杂乱,有时甚至会操作出错。Apple为我们提供一个类,用于管理这些文件(路径),是为——NSFileManager文件管理器。

NSFileManager类支持NSString和NSURL(往后再介绍)作为文件路径。它还拥有一个代理协议NSFileManagerDelegate用来接收各种文件操作的通知,典型功用是当错误发生时可决定是否继续。

NSFileManager可以在多个线程中安全调用,但问题是,如果创建了不一样的NSFileManager实例对象,那么代理方法由谁来实现?别担心,苹果早就想好了——NSFileManager类的实例对象为单例。

所谓单例,即,不管你用这个类创建了多少个实例对象,这些对象实际上都是同一个(名称虽不同,指针却一样)!下面举例论证:

int main(int argc, char * argv[]) {
    //获取文件管理器,单例对象:在整个应用程序当中,只会实例化一个这种类型的对象
    NSFileManager *manager = [NSFileManager defaultManager];
    NSLog(@"manager指针:%p",manager);
    //manager和manager1指针一样,指向同一个对象
    NSFileManager *manager1 = [NSFileManager defaultManager];
    NSLog(@"manager1指针:%p",manager1);
}

我们不一样?有啥不一样:

前面介绍了获取APP本身的一些目录,下面为了方便查看结果,拷贝操作部分,我们将对桌面目录试验NSFileManager。先在桌面放置一个plist文件,然后右键点“显示简介”,在“位置”那里得到路径。续上代码:

int main(int argc, char * argv[]) {
    //获取文件管理器,单例对象:在整个应用程序当中,只会实例化一个这种类型的对象
    NSFileManager *manager = [NSFileManager defaultManager];
    NSLog(@"manager指针:%p",manager);
    //manager和manager1指针一样,指向同一个对象
    NSFileManager *manager1 = [NSFileManager defaultManager];
    NSLog(@"manager1指针:%p",manager1);

    //获取tmp目录
    NSString *TmpDir = NSTemporaryDirectory();
    //拼接路径
    NSString *TestDir = [TmpDir stringByAppendingFormat:@"%@",@"/test"];
    //fileExistsAtPath判断此路径是否已经存在
    if(![manager fileExistsAtPath:TestDir]) {
        //先声明一个NSError指针
        NSError *error = nil;
        //创建目录----createDirectoryAtPath
        [manager createDirectoryAtPath:TestDir      //参数1: 创建的目录路径
           withIntermediateDirectories:YES          //参数2: 是否自动添加缺失的路径
                            attributes:nil          //参数3: 创建文件的附带信息,一般为nil
                                 error:&error];     //参数4: 错误信息;二级指针
        //如果存在error,就打印错误信息
        if(error) {
            NSLog(@"error = %@",error);
        }
    }

    //浅层遍历(遍历根目录第一层文件和文件夹)
    NSString *HomeDir = NSHomeDirectory();
    NSArray *resultArray = [manager contentsOfDirectoryAtPath:HomeDir error:nil];
    for(id obj in resultArray) {
        NSLog(@"浅层遍历obj = %@",obj);
    }

    //深层遍历(遍历tmp路径下的所有文件夹和文件)
    NSArray *resultArr1 = [manager subpathsOfDirectoryAtPath:HomeDir error:nil];
    for(id obj1 in resultArr1) {
        NSLog(@"深层遍历obj = %@",obj1);
    }

    //创建一个桌面文件夹test
    NSString *desDir = @"/Users/tailor/Desktop/test";
    if(![manager fileExistsAtPath:desDir]) {
        NSError *error = nil;
        [manager createDirectoryAtPath:desDir
           withIntermediateDirectories:YES
                            attributes:nil
                                 error:&error];
        if(error) {
            NSLog(@"error = %@",error);
        }
    }

    //拷贝文件目录
    NSString *srcPath = @"/Users/tailor/Desktop/Info.plist";
    NSString *dstPath1 = @"/Users/tailor/Desktop/Info1.plist";
    if(![manager copyItemAtPath:srcPath toPath:dstPath1 error:nil]) {
        NSLog(@"拷贝失败");
    }

    //移动/剪切
    NSString *dstPath2 = @"/Users/tailor/Desktop/test/Info2.plist";     //test必须存在,Info2.plist不存在(还没创建)
    if(![manager moveItemAtPath:dstPath1 toPath:dstPath2 error:nil]) {
        NSLog(@"移动失败");
    }

    //删除
    if(![manager removeItemAtPath:srcPath error:nil]) {
        NSLog(@"删除失败");
    }
}

结果如下:

对于copyItemAtPath: toPath:和moveItemAtPath: toPath:方法的一些操作,本人之前踩了许多坑,现在把它铺平。先来看看官方文档:

Discussion
If srcPath is a file, the method creates a file at dstPath that holds the exact contents of the original file (this includes BSD special files). If srcPath is a directory, the method creates a new directory at dstPath and recursively populates it with duplicates of the files and directories contained in srcPath, preserving all links. The file specified in srcPath must exist, while dstPath must not exist prior to the operation. When a file is being copied, the destination path must end in a filename—there is no implicit adoption of the source filename. Symbolic links are not traversed but are themselves copied. File or directory attributes—that is, metadata such as owner and group numbers, file permissions, and modification date—are also copied.

需要注意的信息有(无聊数了下有5个必须):

  1. 如果源路径(srcPath)是文件,则目标路径(dstPath)必须是文件(且后缀名一致);
  2. 如果源路径是文件夹,则目标路径必须是文件夹,将会复制源文件夹下所有文件(夹);
  3. 方法调用前,源路径必须存在,目标路径必须不存在(注意:目 标文件不存在,但目标文件所在文件夹必须存在);
  4. 方法的一些错误提示等。

三、后续

前面讨论了使用NSFileManager创建目录(文件夹), 其中withIntermediateDirectories:YES, 可以自动添加缺失的路径. 例如, 我们创建一个tmp/aaa/bbb目录, 假如那个参数设置为NO, 则创建失败, 因为没有aaa这个文件夹; 而如果设置为YES, 则系统自动添加缺失的aaa文件夹路径, 最终创建成功.
那么怎样创建文件呢?
使用以下方法:

    NSString *filePath = [NSString stringWithFormat:@"%@%@", NSTemporaryDirectory(), @"123456.avi"];
    // 移除之前的
    [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
    // 创建文件
    if(![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
        BOOL success = [[NSFileManager defaultManager] createFileAtPath:filePath    // 文件路径
                                                               contents:nil         // 初始化的内容
                                                             attributes:nil];       // 附加信息
        NSLog(@"success:%@, filePath=%@", success ? @"YES" : @"NO", filePath);
    }

注意:
创建文件方法和创建文件夹方法一大不同之处在于, 创建文件方法不能跨路径创建文件. 比如说, 将上面的文件路径改为"aaa/123456.avi", 由于多了aaa这个文件夹, 导致创建失败. 也就是说, createFileAtPath:方法不会自动添加缺失的文件夹路径.

创建文件的正确姿势

    NSFileManager *manager = [NSFileManager defaultManager];

    //获取tmp目录
    NSString *TmpDir = NSTemporaryDirectory();

    NSString *filePath = [NSString stringWithFormat:@"%@%@", TmpDir, @"abc/123456.avi"];
    // 移除之前的filePath
    [[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
    // 创建文件夹
    NSString *folderPath = [filePath stringByDeletingLastPathComponent];    // 去除最后的组成部分 (/123456.avi)
    if(![manager fileExistsAtPath:folderPath]) {
        BOOL success = [manager createDirectoryAtPath:folderPath    //参数1: 创建的目录路径
                          withIntermediateDirectories:YES           //参数2: 是否自动添加缺失的路径
                                           attributes:nil           //参数3: 创建文件的附带信息
                                                error:nil];         //参数4: 错误信息
        NSLog(@"创建文件夹 success:%@, folderPath:%@", success ? @"YES" : @"NO", folderPath);
    }
    // 创建文件
    if(![manager fileExistsAtPath:filePath]) {
        BOOL success = [manager createFileAtPath:filePath    // 文件路径
                                        contents:nil         // 初始化的内容
                                      attributes:nil];       // 附加信息
        NSLog(@"success:%@, filePath=%@", success ? @"YES" : @"NO", filePath);
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,599评论 18 139
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,125评论 29 470
  • 一、iOS中的沙盒机制 iOS应用程序只能对自己创建的文件系统读取文件,这个独立、封闭、安全的空间,叫做沙盒。它一...
    1d5cb7cff98d阅读 1,764评论 0 0
  • 岁月留给女人的,不应该仅仅是数不清的皱纹和蜡黄的肤色,异或一张无所顾忌的嘴…… 不知道为什么,因为一...
    一生女子阅读 1,263评论 0 0
  • 在多线程情况下:多个线程要访问同一块资源时,容易引发数据混乱出错 和线程安全等等问题。因此需要给线程加上互斥锁。 ...
    summer_code阅读 277评论 0 0