美文网首页
iOS本地数据存储方案

iOS本地数据存储方案

作者: TigerManBoy | 来源:发表于2018-08-01 15:22 被阅读0次

    移动开发,数据存储是一定而且经常遇到的,存储的数据有大有小,大数据和小数据的存储方式也不一样,存储数据也要考虑性能,针对iOS的数据存储,有以下几种存储方式。

    常见的存储方式

    • plist 格式文件存储
    • NSUserDefaults 沙盒存储(个人偏好设置)
    • 文件读写存储
    • 解归档存储
    • 数据库存储

    了解缓存,先要了解iOS中沙盒机制这个概念

    沙盒其实质就是在iOS系统下,每个应用在内存中对应的存储空间。
    每个iOS应用都有自己的应用沙盒(文件系统目录),与其他文件系统隔离,各个沙盒之间相互独立,而且不能相互访问(手机没有越狱情况下),各个应用程序的沙盒相互独立的,在系统内存消耗过高时,系统会收到内存警告并自动退出软件。这就保证了系统的数据的安全性及系统的稳定性。

    一个沙盒目录如下:


    屏幕快照 2018-08-01 下午2.35.24.png 屏幕快照 2018-08-01 下午2.35.55.png 屏幕快照 2018-08-01 下午2.36.06.png 屏幕快照 2018-08-01 下午2.36.15.png

    Documents 应用程序在运行时生成的一些需要长久保存的数据。
    Library/Caches 储存应用程序网络请求的数据信息(音视频与图片等的缓存)。此目录下的数据不会自动删除,需要程序员手动清除该目录下的数据。主要用于保存应用在运行时生成的需要长期使用的数据.一般用于存储体积较大数据。
    Library/Preferences 设置应用的一些功能会在该目录中查找相应设置的信息,该目录由系统自动管理,通常用来储存一些基本的应用配置信息,比如账号密码,自动登录等。
    tmp 保存应用运行时产生的一些临时数据;应用程序退出、系统空间不够、手机重启等情况下都会自动清除该目录的数据。无需程序员手动清除该目录中的数据。

    plist 格式文件存储

    • plist文件 即为属性列表文件
    • 可以存储的类型有NSString,NSDictionary,NSArray,NSNumber,Boolean,NSDate,NSData等基本类型
    • 常用于存储用户的设置,或存储项目中经常用到又不经常改变的数据
    • 创建.plist可以用xcode工具,也可以用代码
    • 不适合存储大量数据,而且只能存储基本类型
    • 可以实现:增,删,改,查等操作,但数据的存取是一次性的全部操作,所以性能方向表现并不好
    • NSDate,BOOL,Int,Float,NSNumber,NSData的数据存储都是转换成NSDictionary的Key-Value形式之后,通过NSDictionary存储方式存储的。
    字符串存储:
    NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
    NSString *filePath = [[paths firstObject] stringByAppendingPathComponent:@"test.plist"];
    BOOL isSave = [string writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"存储是否成功---%d",isSave);
        
    NSString *testString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    NSLog(@"存储的字符串----%@",testString);
    
    NSArray存储
    NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
    NSString *filePath = [[paths firstObject] stringByAppendingPathComponent:@"test.plist"];
    [array writeToFile:filePath atomically:YES];
    NSArray *mArray = [NSArray arrayWithContentsOfFile:filePath];
    NSLog(@"存储的Array-----%@",mArray);
    
    NSDictionary存储
    NSArray *paths = NSSearchPathForDirectoriesInDomains(searchPath, NSUserDomainMask, YES);
    NSString *filePath = [[paths firstObject] stringByAppendingPathComponent:@"test.plist"];
    [dic writeToFile:filePath atomically:YES];   
    NSDictionary *mDic = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSLog(@"存储的Dic------%@",mDic);
    

    NSUserDefaults 沙盒存储(个人偏好设置)

    • NSUserDefaults 沙盒存储(个人偏好存储) 是个单例类,用于存储少量数据,例如登录后的用户名,密码等。
    • 应用程序启动后,会在沙盒路径Library -> Preferences 下默认生成以工程bundle为名的.plist文件,用NSUserDefaults存储的数据都是存储在该.plist文件中。
    • 这种方式本质是操作plist文件,所以性能方面的考虑同plist文件数据储存
    [[NSUserDefaults standardUserDefaults] setBool:bol forKey:key];  //BOOL存储
    [[NSUserDefaults standardUserDefaults] setInteger:i forKey:key];  //NSInteger存储
    [[NSUserDefaults standardUserDefaults] setObject:obj forKey:key];  //Object存储
    [[NSUserDefaults standardUserDefaults] setURL:url forKey:key];  //NSURL存储
    [[NSUserDefaults standardUserDefaults] setFloat:f forKey:key];  //Float存储
    [[NSUserDefaults standardUserDefaults] setDouble:d forKey:key];  //Double存储
    [[NSUserDefaults standardUserDefaults] URLForKey:key];  //URL的数据获取
    [[NSUserDefaults standardUserDefaults] objectForKey:key];  //Object或者基本类型的获取
    

    文件读写存储(NSFileManager)

    • 文件操作可通过单例 NSFileManager 处理。文件存储的路径可以代码设置。
    • 可以存储大量数据,对数据格式没有限制。
    • 但由于数据的存取必须是一次性全部操作,所以在频繁操作数据方面性能欠缺。
    创建文件夹
    /**
     创建文件夹
    
     @param dirName 文件夹名称
     @param dirPath 文件夹所在路径
     @return 创建结果YES/NO
     */
    + (BOOL)creatDir:(NSString *)dirName dirPath:(NSString *)dirPath {
        
        dirPath = [dirPath stringByAppendingPathComponent:dirName];
        if ([FileManager fileExistsAtPath:dirPath]) {
            NSLog(@"创建失败,目录已存在");
        } else {
            BOOL isCreat = [FileManager createDirectoryAtPath:dirPath withIntermediateDirectories:YES attributes:nil error:nil];
            if (isCreat) {
                NSLog(@"创建成功");
                return YES;
            } else {
                NSLog(@"创建失败,请检查路径");
                return NO;
            }
        }
        return NO;
    }
    
    创建文件
    /**
     创建文件
    
     @param fileName 文件名称
     @param dirPath 文件所在的文件夹路径
     @return 创建结果YES/NO
     */
    + (BOOL)creatFile:(NSString *)fileName dirPath:(NSString *)dirPath {
    
        NSString *filePath = [dirPath stringByAppendingPathComponent:fileName];
        BOOL isDir = NO;
        BOOL isFileExist = [FileManager fileExistsAtPath:filePath isDirectory:&isDir];
        //目录是否存在
        if (!(isFileExist && isDir)) {
            BOOL isCreat = [FileManager createFileAtPath:filePath contents:nil attributes:nil];
            if (isCreat) {
                NSLog(@"创建成功");
                return YES;
            } else {
                NSLog(@"创建失败");
                return NO;
            }
        } else {
            NSLog(@"创建失败,文件已存在");
            return NO;
        }
        return NO;
    }
    
    String写入
    /**
     String写入
    
     @param content 写入的字符串内容
     @param filePath 写入文件的路径
     @return 写入结果YES/NO
     */
    + (BOOL)writeString:(NSString *)content filePath:(NSString *)filePath {
        
        BOOL isFileExist = [FileManager fileExistsAtPath:filePath];
        if (isFileExist) {
            BOOL isWrite = [content writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
            if (isWrite) {
                NSLog(@"写入成功");
                return YES;
            } else {
                NSLog(@"写入失败");
                return NO;
            }
        } else {
            NSLog(@"写入失败,文件不存在");
            return NO;
        }
        return NO;
    }
    
    Array写入
    /**
     Array写入
    
     @param array 要写入的array
     @param filePath 文件路径
     @return 写入结果YES/NO
     */
    + (BOOL)writeArray:(NSArray *)array filePath:(NSString *)filePath {
    
        BOOL isFileExist = [FileManager fileExistsAtPath:filePath];
        if (isFileExist) {
            BOOL isCreat = [array writeToFile:filePath atomically:YES];
            if (isCreat) {
                NSLog(@"写入成功");
                return YES;
            } else {
                NSLog(@"写入失败");
                return NO;
            }
        } else {
            NSLog(@"写入失败,文件不存在");
            return NO;
        }
        return NO;
    }
    
    Dictionary写入
    /**
     Dictionary写入
    
     @param dic 要写入的dictionary
     @param filePath 文件路径
     @return 写入结果YES/NO
     */
    + (BOOL)writeDictionary:(NSDictionary *)dic filePath:(NSString *)filePath {
    
        BOOL isFileExist = [FileManager fileExistsAtPath:filePath];
        if (isFileExist) {
            BOOL isCreat = [dic writeToFile:filePath atomically:YES];
            if (isCreat) {
                NSLog(@"写入成功");
                return YES;
            } else {
                NSLog(@"写入失败");
                return NO;
            }
        } else {
            NSLog(@"写入失败,文件不存在");
            return NO;
        }
        return NO;
    }
    
    读取存储的String
    /**
     读取存储的String
    
     @param filePath 文件路径
     @return 存储的字符串
     */
    + (NSString *)readFileWithFilePath:(NSString *)filePath {
    
        NSString *str = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
        return str;
    }
    
    读取存储的array
    /**
     读取存储的array
    
     @param filePath 文件路径
     @return 存储的array
     */
    + (NSArray *)readArrayWithFilePath:(NSString *)filePath {
        
        NSArray *array = [NSArray arrayWithContentsOfFile:filePath];
        return array;
    }
    
    读取存储的dictionary
    /**
     读取存储的dictionary
    
     @param filePath 文件路径
     @return 存储的dictionary
     */
    + (NSDictionary *)readDictionaryWithFilePath:(NSString *)filePath {
    
        NSDictionary *dic = [NSDictionary dictionaryWithContentsOfFile:filePath];
        return dic;
    }
    
    读取文件夹中所有文件
    /**
     读取文件夹中所有文件
    
     @param dirPath 文件夹路径
     @return 文件夹中所有的文件
     */
    + (NSArray *)readAllFileWithDirPath:(NSString *)dirPath {
    
        NSArray *array = [FileManager contentsOfDirectoryAtPath:dirPath error:nil];
        return array;
    }
    
    判断文件是否存在
    /**
     判断文件是否存在
    
     @param filePath 文件路径
     @return 文件是否存在
     */
    + (BOOL)fileIsExistFilePath:(NSString *)filePath {
    
        BOOL isFileExist = [FileManager fileExistsAtPath:filePath];
        return isFileExist;
    }
    
    文件大小
    /**
     文件大小
    
     @param filePath 文件路径
     @return 文件大小
     */
    + (unsigned long long)computerFileSizeWithFilePath:(NSString *)filePath {
    
        BOOL isDir;
        BOOL isFile = [FileManager fileExistsAtPath:filePath isDirectory:&isDir];
        if (!isDir) {
            if (!isFile) {
                NSLog(@"文件不存在");
                return 0;
                
            }else {
                unsigned long long fileSize = [FileManager attributesOfItemAtPath:filePath error:nil].fileSize;
                return fileSize;
            }
        }  else {
            NSLog( @"该文件是一个目录");
            return 0;
        }
        return 0;
    }
    
    文件夹中的所有文件大小
    /**
     文件夹中的所有文件大小
    
     @param dirPath 文件夹路径
     @return 所有文件大小
     */
    + (unsigned long long)computerDirSizeWithDirPath:(NSString *)dirPath {
    
        BOOL isExist = [FileManager fileExistsAtPath:dirPath];
        if (isExist) {
            
            NSEnumerator *childFilesEnumerator = [[FileManager subpathsAtPath:dirPath] objectEnumerator];
            NSString* fileName;
            long long folderSize = 0;
            while ((fileName = [childFilesEnumerator nextObject]) != nil){
                NSString* fileAbsolutePath = [dirPath stringByAppendingPathComponent:fileName];
                folderSize += [FileManager attributesOfItemAtPath:fileAbsolutePath error:nil].fileSize;
            }
            return folderSize;
            
        } else {
            NSLog(@"目录不存在");
            return 0;
        }
        return 0;
    }
    
    移除文件
    /**
     移除文件
    
     @param filePath 文件路径
     @return 移除结果YES/NO
     */
    + (BOOL)removeFileWithFilePath:(NSString *)filePath {
    
        if ([FileManager fileExistsAtPath:filePath]) {
            BOOL isRemove = [FileManager removeItemAtPath:filePath error:nil];
            if (!isRemove) {
                NSLog(@"移除失败");
                return NO;
            } else {
                NSLog(@"移除成功");
                return YES;
            }
        } else {
            NSLog(@"文件不存在");
            return NO;
        }
        return NO;
    }
    
    移动文件
    /**
     移动文件
    
     @param filePath 要移动的文件路径
     @param toDirPath 文件要移动到的文件夹路径
     @param newFileName 移动文件的新名称
     @return 是否移动成功
     */
    + (BOOL)moveFileWithFilePath:(NSString *)filePath toDirPath:(NSString *)toDirPath newFileName:(NSString *)newFileName {
    
        NSString *des = [toDirPath stringByAppendingPathComponent:newFileName];
    
        if (![FileManager fileExistsAtPath:filePath]) {
            NSLog(@"文件不存在");
            return NO;
        }else {
            if (![FileManager fileExistsAtPath:toDirPath]) {
                NSLog(@"目标路径不存在");
                return NO;
            } else {
                BOOL move = [FileManager moveItemAtPath:filePath toPath:des error:nil];
                if (move) {
                    NSLog( @"移动成功");
                    return YES;
                } else {
                    NSLog(@"移动失败");
                    return NO;
                }
            }
        }
        return NO;
    }
    

    解归档存储

    • plist 与 NSUserDefaults(个人偏好设置)两种类型的储存只适用于系统自带的一些常用类型,而且前者必须拿到文件路径,后者也只能储存应用的主要信息。
    • 对于开发中自定义的数据模型的储存,我们可以考虑使用归档储存方案。
    • 归档保存数据,文件格式自己可以任意,没有要求 ; 即便设置为常用的数据格式(如:.c .txt .plist 等)要么不能打开,要么打开之后乱码显示。
    • 值得注意的是使用归档保存的自定义模型需要实现NSCoding协议下的两个方法。
    • 不适合存储大量数据,可以存储自定义的数据模型。
    • 虽然归档可以存储自定义的数据结构,但在大批量处理数据时,性能上仍有所欠缺。
    YJArchiveModel.h
    @interface YJArchiveModel : NSObject <NSCoding>
    
    @property (nonatomic, strong) NSString *name;
    @property (nonatomic, assign) BOOL sex;
    @property (nonatomic, assign) NSInteger age;
    @property (nonatomic, assign) double price;
    
    @end
    
    YJArchiveModel.m
    @implementation YJArchiveModel
    
    - (void)encodeWithCoder:(NSCoder *)aCoder {
        [aCoder encodeObject:_name forKey:@"name"];
        [aCoder encodeInteger:_age forKey:@"age"];
        [aCoder encodeBool:_sex forKey:@"sex"];
        [aCoder encodeDouble:_price forKey:@"price"];
    }
    
    - (instancetype)initWithCoder:(NSCoder *)aDecoder {
        self = [super init];
        if (self) {
            _name = [aDecoder decodeObjectForKey:@"name"];
            _age = [aDecoder decodeIntegerForKey:@"age"];
            _sex = [aDecoder decodeBoolForKey:@"sex"];
            _price = [aDecoder decodeDoubleForKey:@"price"];
        }
        return self;
    }
    
    @end
    

    数据库存储

    • SQLite : 它是一款轻型的嵌入式数据库,安卓和ios开发使用的都是SQLite数据库;占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了;而且它的处理速度比Mysql、PostgreSQL这两款著名的数据库都还快。
    • FMDB 正是基于 SQLite 开发的一套开源库。使用时,需要自己写一些简单的SQLite语句
    • CoreData 是苹果给出的一套基于 SQLite 的数据存储方案;而且不需要自己写任何SQLite语句。该功能依赖于 CoreData.framework 框架,该框架已经很好地将数据库表和字段封装成了对象和属性,表之间的一对多、多对多关系则封装成了对象之间的包含关系
    • Core Data的强大之处就在于这种关系可以在一个对象更新时,其关联的对象也会随着更新,相当于你更新一张表的时候,其关联的其他表也会随着更新。Core Data的另外一个特点就是提供了更简单的性能管理机制,仅提供几个类就可以管理整个数据库。由于直接使用苹果提供的CoreData容易出错,这里提供一个很好的三方库 MagicalRecord

    缓存系统

    对大多数 APP 而言,都是 Hybrid 开发,Web 页与原生同时存在,其中 Web 页可能是 UIWeb 也可能是 WKWeb 。所以与之相应的缓存系统,应该包括 Web 缓存与 原生接口数据缓存两部分。

    原生接口部分的数据缓存

    存储方式:主要采用文件读写、归档、个人偏好设置(NSUserDefaults) 。

    具体说明:大部分接口数据解析之后写入文件保存(读写操作最好 GCD 子线程操作);整个应用需要用到的重要数据模型可以考虑采用归档方式(标记状态的数据模型);与用户相关的信息、单个标记标识等采用个人偏好设置。

    补充: 原生接口数据存储方式以上三种方式就已够用;当然对于一些涉及查询、删除、更新等操作的数据模型,就需要使用数据库操作。这里推荐使用 CoreData 的封装库 MagicalRecord 。

    关于存储的使用,我写了一个简单的demol,除了数据库的,其他的存储基本上都有,有兴趣的可以看一看:YJDataStore

    相关文章

      网友评论

          本文标题:iOS本地数据存储方案

          本文链接:https://www.haomeiwen.com/subject/kbplvftx.html