iOS 文件操作简介

级别: ★★☆☆☆
标签:「iOS 文件操作」「SandBox」「QiFileManager」
作者: dac_1033
审校: QiShare团队

在iOS开发过程中,网络出错没有返回正确数据时有发生,这时可以读取本地数据展示给用户来优化用户体验,并且在网络请求正常时及时更新这些本地数据,这些本地数据一般都存在沙盒目录的文件中,下面我们就来简单介绍一下iOS开发中的文件操作。附:苹果官方文件系统编程指南。

一、 关于iOS文件系统

1.1 沙盒(SandBox)

iOS中每个app都有个独立、封闭、安全的目录,叫做沙盒,它一般存放着程序包文件(可执行文件)、图片、音频、视频、plist文件、SQLite数据库以及其他文件。每个app的沙盒都是独立的,应用程序之间是不可以直接互相访问的。苹果官网将沙盒结构划分为三类(Bundle ContainerData ContaineriClound Container),如下图:

app沙盒的结构

上图中的沙盒结构其实是与小编的理解有出入的,具体的沙盒结构我们暂且不做研究。在开发iOS应用程序过程中,我们只能看到沙盒中的根目录及默认生成的三个文件夹DocumentsLibraryTmp,这些才是我们关注的重点, 关于常用目录的描述如下:

目录 描述
AppName.app 应用程序的程序包目录,包含应用程序的本身。由于应用程序必须经过签名,所以不能在运行时对这个目录中的内容进行修改,否则会导致应用程序无法启动。
Documents/ 保存应用程序的重要数据文件和用户数据文件等。用户数据基本上都放在这个位置(例如从网上下载的图片或音乐文件),该文件夹在应用程序更新时会自动备份,在连接iTunes时也可以自动同步备份其中的数据;
该目录的内容被iTunes和iCloud备份。
Library/ 这个目录下有两个子目录,可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份;
该目录的内容被iTunes和iCloud备份
Library/Caches 保存应用程序使用时产生的支持文件和缓存文件(保存应用程序再次启动过程中需要的信息),还有日志文件最好也放在这个目录;
iTunes 不会备份该目录,并且该目录下数据可能被其他工具清理掉。
Library/Preferences 保存应用程序的偏好设置文件。NSUserDefaults类创建的数据和plist文件都放在这里;
该目录的内容被iTunes和iCloud备份。
Tmp/ 使用此目录可以编写在应用程序启动之间不需要保留的临时文件,您的应用程序应在不再需要时删除此目录中的文件,但是,当您的应用未运行时,系统可能会清除此目录;
iTunes或iCloud不会备份此目录下的内容。
1.2 获取沙盒目录
- (void)testSandBoxDirectory {
    
    // 获取app沙盒的根目录(home)
    NSString *homePath = NSHomeDirectory();
    NSLog(@"NSHomeDirectory: %@", homePath);
    
    // 获取temp路径
    NSString *tmp = NSTemporaryDirectory( );
    NSLog(@"NSTemporaryDirectory: %@", tmp);
    
    // 获取Document目录
    NSArray  *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *docPath = [paths lastObject];
    NSLog(@"NSDocumentDirectory: %@", docPath);
    
    // 获取Library目录
    paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
    NSString *libPath = [paths lastObject];
    NSLog(@"NSLibraryDirectory: %@", libPath);
    
    // 获取Library中的Cache
    paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *cachesPath = [paths lastObject];
    NSLog(@"NSCachesDirectory: %@", cachesPath);
}
1.3 常用的路径处理方法

系统在类NSPathUtilities中实现了对NSString的扩展NSString (NSStringPathExtensions),其中定义了很多方法专门用于处理路径:

- (NSArray *)pathComponents;
- (NSString *)lastPathComponent;
- (NSString *)stringByDeletingLastPathCpmponent;
- (NSString *)stringByAppendingPathConmponent:(NSString *)str;
- (NSString *)pathExtension;
- (NSString *)stringByDeletingPathExtension;
- (NSString *)stringByAppendingPathExtension:(NSString *)str;
1.4 iOS工程中的NSBundle
工程中的bundle文件

如图,我们自己在工程中创建了一个Test.bundlebundle是一个目录,其中包含程序会使用到的资源(如图像、声音、代码文件、nib文件等)。在系统中app本身和其他文件没有什么区别,app包中实际上包含了nib文件、编译连接过的代码和其他资源的目录,我们把app包这个目录叫做该app的main bundle。在iOS开发中可以使用NSBundle类来操作bundle及其中的资源。NSBundle的官方文档描述
使用NSBundle的示例代码如下:

// 获取main bundle
NSBundle *mainBundle = [NSBundle mainBundle];

// 放在app mainBundle中的自定义Test.bundle
NSString *testBundlePath = [mainBundle pathForResource:@"Test" ofType:@"bundle"];
NSBundle *testBundle = [NSBundle bundleWithPath:testBundlePath];

// 获取Test.bundle中资源
NSString *resPath = [testBundle pathForResource:@"sound02" ofType:@"wav"];
NSLog(@"自定义bundle中资源的路径: %@", resPath);
    
// 直接根据目录获取资源
UIImage *img = [UIImage imageNamed:[NSString stringWithFormat:@"Test.bundle/%@", @"logo_img_02"]];
NSLog(@"自定义bundle中图片: %@", img);

二、文件操作

2.1 文件路径及文件
// 获取沙盒根路径
+ (NSString *)getHomePath {
    
    return NSHomeDirectory();
}

// 获取tmp路径
+ (NSString *)getTmpPath {
    
    return NSTemporaryDirectory();
}

// 获取Documents路径
+ (NSString *)getDocumentsPath {
    
    NSArray *pathArr = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *path = [pathArr firstObject];
    return path;
}

// 获取Library路径
+ (NSString *)getLibraryPath {
    
    NSArray *pathArr = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
    NSString *path = [pathArr firstObject];
    return path;
}

// 获取LibraryCache路径
+ (NSString *)getLibraryCachePath {
    
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *path = [paths firstObject];
    return path;
}

// 检查文件、文件夹是否存在
+ (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDir {
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL exist = [fileManager fileExistsAtPath:path isDirectory:isDir];
    return exist;
}

// 创建路径
+ (void)createDirectory:(NSString *)path {
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL isDir;
    BOOL exist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
    if (!isDir) {
        [fileManager removeItemAtPath:path error:nil];
        exist = NO;
    }
    if (!exist) {
        // 注:直接创建不会覆盖原文件夹
        [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
    }
}

// 创建文件
+ (NSString *)createFile:(NSString *)filePath fileName:(NSString *)fileName {
    
    // 先创建路径
    [self createDirectory:filePath];
    
    // 再创建路径上的文件
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSString *path = [filePath stringByAppendingPathComponent:fileName];
    BOOL isDir;
    BOOL exist = [fileManager fileExistsAtPath:path isDirectory:&isDir];
    if (isDir) {
        [fileManager removeItemAtPath:path error:nil];
        exist = NO;
    }
    if (!exist) {
        // 注:直接创建会被覆盖原文件
        [fileManager createFileAtPath:path contents:nil attributes:nil];
    }
    return path;
}

// 复制 文件or文件夹
+ (void)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath {
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;
    BOOL result = [fileManager copyItemAtPath:srcPath toPath:dstPath error:&error];
    if (!result && error) {
        NSLog(@"copyItem Err : %@", error.description);
    }
}

// 移动 文件or文件夹
+ (void)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath {
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;
    BOOL result = [fileManager moveItemAtPath:srcPath toPath:dstPath error:&error];
    if (!result && error) {
        NSLog(@"moveItem Err : %@", error.description);
    }
}

// 删除 文件or文件夹
+ (void)removeItemAtPath:(NSString *)path {
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;
    BOOL result = [fileManager removeItemAtPath:path error:&error];
    if (!result && error) {
        NSLog(@"removeItem Err : %@", error.description);
    }
}

// 获取目录下所有内容
+ (NSArray *)getContentsOfDirectoryAtPath:(NSString *)docPath {
    
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;
    NSArray *contentArr = [fileManager contentsOfDirectoryAtPath:docPath error:&error];
    if (!contentArr.count && error) {
        NSLog(@"ContentsOfDirectory Err : %@", error.description);
    }
    return contentArr;
}
2.2 能直接进行文件读写的数据类型

iOS中有四种简单类型能够直接进行文件读写:字符串NSString/NSMutableString、数组NSArray/NSMutableArray、字典NSDictionary/NSMutableDictionary、二进制数据NSData/NSMutableData
代码示例如下:

NSString *string = @"QiShare test string ...";
[string writeToFile:path11 atomically:YES encoding:NSUTF8StringEncoding error:nil];
NSString *readStr = [NSString stringWithContentsOfFile:path11 encoding:NSUTF8StringEncoding error:nil];
NSLog(@"读取文件-字符串: %@", readStr);
    
NSArray *array = @[@"Q", @"i", @"S", @"h", @"a", @"r", @"e"];
[array writeToFile:path33 atomically:YES];
NSArray *readArr = [NSArray arrayWithContentsOfFile:path33];
NSLog(@"读取文件-数组: %@", readArr);
    
NSDictionary *dict = @{@"en":@"QiShare", @"ch":[[FileUtil alloc] init]};
[dict writeToFile:path34 atomically:YES];
NSDictionary *readDict = [NSDictionary dictionaryWithContentsOfFile:path34];
NSLog(@"读取文件-字典: %@", readDict);
    
NSData *data = [@"QiShare test data ..." dataUsingEncoding:NSUTF8StringEncoding];
[data writeToFile:path11 atomically:YES];
NSData *readData = [NSData dataWithContentsOfFile:path11];
NSLog(@"读取文件-二进制: %@", readData);
  • 其中数组和字典中的元素对象的类型也必须是上述四种,否则不能直接写入文件;
  • 每次调用writeToFile:方法写入文件时,都会覆盖文件原有内容。

三、文件内容操作

iOS开发在涉及到文件操作的过程中,进行一些细粒度的文件内容操作时会用到NSFileHandle。一般用NSFileHandle修改文件内容有三个步骤:打开文件,获取一个NSFileHandle对象;对打开文件执行相关操作;关闭文件。NSFileHandle具体功能概括如下:

  • 打开一个文件,执行读、写或更新操作;
  • 在文件中查找指定位置;
  • 从文件中读取特定数目的字节,或将特定数目的字节写入文件中;
  • NSFileHandle类提供的方法也可以用于各种设备或套接字。

NSFileHandle操作文件内容示例代码如下:

// NSFileHandle操作文件内容
- (void)testFileHandle {
    
    NSString *docPath = [FileUtil getDocumentsPath];
    NSString *readPath = [docPath stringByAppendingPathComponent:@"read.txt"];
    NSString *writePath = [docPath stringByAppendingPathComponent:@"write.txt"];
    NSData *data = [@"abcdefghijklmnopqrstuvwxyz" dataUsingEncoding:NSUTF8StringEncoding];
    
    NSFileManager *manager=[NSFileManager defaultManager];
    [manager createFileAtPath:readPath contents:data attributes:nil];
    [manager createFileAtPath:writePath contents:nil attributes:nil];
    [data writeToFile:readPath atomically:YES];
    
    // 打开文件 读
    NSFileHandle *readHandle = [NSFileHandle fileHandleForReadingAtPath:readPath];
    NSData *readData = [readHandle readDataToEndOfFile];
    
    // 读取文件中指定位置/指定长度的内容
    [readHandle seekToFileOffset:10];
    readData = [readHandle readDataToEndOfFile];
    NSLog(@"seekToFileOffset:10 = %@", [[NSString alloc] initWithData:readData encoding:NSUTF8StringEncoding]);
    
    [readHandle seekToFileOffset:10];
    readData = [readHandle readDataOfLength:5];
    NSLog(@"seekToFileOffset:10 = %@",[[NSString alloc]initWithData:readData encoding:NSUTF8StringEncoding]);
    [readHandle closeFile];
    
    // 打开文件 写
    NSFileHandle *writeHandle = [NSFileHandle fileHandleForWritingAtPath:writePath];
    // 注:直接覆盖文件原有内容
    [writeHandle writeData:data];
    
    // 注:覆盖了指定位置/指定长度的内容
    [writeHandle seekToFileOffset:2];
    [writeHandle writeData:[@"CDEFG" dataUsingEncoding:NSUTF8StringEncoding]];
    
    [writeHandle seekToEndOfFile];
    [writeHandle writeData:[@"一二三四五六" dataUsingEncoding:NSUTF8StringEncoding]];
    [writeHandle closeFile];
}

工程源码地址:GitHub地址


推荐文章:
iOS 关键帧动画
iOS 获取设备当前语言和地区
iOS 编写高质量Objective-C代码(七)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343