ios数据存储的5种方式

ios数据存储的5种方式

  1. NSUserDefaults(Preference偏好设置)
  2. plist存储
  3. 归档
  4. SQLite3
  5. CoreData

应用沙盒

每个App的沙河目录结构, 都如下图所示:

沙盒目录

默认情况下, 每个沙盒含有1个应用程序包和三个文件夹: Documents, Library和tmp. 因为沙盒机制, 应用只能在这几个目录读写文件.

MyApp.app:

应用程序包, 这里面存放的是应用程序文件, 包括资源文件和可执行文件.
访问路径:

//object-c
NSString *budlePath = [[NSBundle mainBundle] bundlePath];
//swift
let bundlePath = Bundle.main.bundlePath

Documents:

应用程序在运行时生成的一些需要长久保存的数据(比如: 个人设置等信息), 通过iTunes, iCloud备份时,会备份这个目录下的数据, 此目录下保存相对重要的数据.

//object-c
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
//swift
let path =  NSHomeDirectory() + "/Documents"
let path2 = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]

Library/Caches

从网络上下载的文件或数据(如: 音乐缓存, 图片缓存等) , 此目录下的数据某些情况下会自动删除, 需要程序员手动清除该目录下的数据. ITunes, iCloud备份时不会备份此目录下的数据.一般用于存储体积不大, 不需要备份的非重要资源数据.

In iOS 5.0 and later, the system may delete the Caches directory on rare occasions when the system is very low on disk space. This will never occur while an app is running. However, be aware that restoring from backup is not necessarily the only condition under which the Caches directory can be erased.

//object-c
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);

NSString* cachesDirectory = [paths objectAtIndex:0];
//swift
let path =  NSHomeDirectory() + "/Library/Caches"
let path2 = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.cachesDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)[0]

Library/Preferences

保存应用程序的偏好设置文件(使用NSUerDefaults类设置是创建, 不应该手动创建), iTunes, iCloud备份时会备份此目录下的数据.该目录由系统自动管理,通常用来储存一些基本的应用配置信息,比如账号密码,自动登录等。

tmp

保存应用运行时产生的一些临时数据,应用程序退出,系统磁盘空间不够,手机重启时,都会自动清除该目录的数据。无需程序员手动清除该目录中的数据.iTunes、iCloud备份时,不会备份次目录。

//object-c
NSString *tempPath = NSTemporaryDirectory();
//swift
let path = NSTemporaryDirectory()

获取沙盒路径

Document:

//Document path    
NSString *docPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];

//Library path
NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES)[0];

//caches path
NSString *cachesPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES)[0];

//Preferences path
NSString*preferencePath = [libraryPath stringByAppendingString:@"/Preferences"];

//tem path
NSString *tmpPath = NSTemporaryDirectory();

//app path
NSString *appPath = [[NSBundle mainBundle] bundlePath];

NSuserDefault

NSuserDefault适合存储轻量级的本地数据,支持的数据类型有:NSNumber,NSString,NSDate,NSArray,NSDictionary,BOOL,NSData

沙盒路径为 Library/Preferences
文件格式为 .plist

优点:

  1. 不需要关心文件名
  2. 快速进行键值对存储
  3. 直接存储基本数据类型

缺点:

  1. 不能存存取自定义数据类型,如果需要支持,需要自定义数据转为支持的类型;比如实现NSCoding协议的encodeWithCoder、initWithCoder。
- (IBAction)userDefaultSave:(id)sender {
    NSArray *testArray = @[@"test1", @"test2", @"test3"];
    [[NSUserDefaults standardUserDefaults] setObject:testArray forKey:@"arrayKey"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}
- (IBAction)userDefaultLoad:(id)sender {
    NSArray *testArray = [[NSUserDefaults standardUserDefaults] objectForKey:@"arrayKey"];
    NSLog(@"%@", testArray);
}

plist存储

plist支持的数据类型:

NSArray;
NSMutableArray;
NSDictionary;
NSMutableDictionary;
NSData;
NSMutableData;
NSString;
NSMutableString;
NSNumber;
NSDate;
不支持BOOL
而且最外层好像要用`NSArray 或 NSDictionary,主要是使用writeToFile api写入指定目录
- (IBAction)plistSave:(id)sender {
    NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filePath = [cachePath stringByAppendingPathComponent:@"testPlist.plist"];
    
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];
    [dict setObject:@"ran" forKey:@"name"];
    [dict setObject:@"18" forKey:@"age"];
    [dict writeToFile:filePath atomically:YES];
}

- (IBAction)plistLoad:(id)sender {
    NSString *cachePath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filePath = [cachePath stringByAppendingPathComponent:@"testPlist.plist"];
    
    NSDictionary *t = [NSDictionary dictionaryWithContentsOfFile:filePath];
    NSLog(@"%@",t);
}

归档

存储自定义对象

  1. 首先新建Person类,并遵守NSCoding协议
@interface Person : NSObject<NSCoding>

@property(nonatomic, strong)NSString *name;
@property(nonatomic, strong)NSString *age;

@end
  1. 实现协议方法
@implementation Person

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super init];
    if (self) {
        _name = [coder decodeObjectForKey:@"name"];
        _age = [coder decodeObjectForKey:@"age"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{

    [coder encodeObject:self.name forKey:@"name"];
    [coder encodeObject:self.age forKey:@"age"];

}
@end
  1. 归档解档
- (IBAction)archive:(id)sender {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentFilePath = paths.firstObject;
    NSString *filePath = [documentFilePath stringByAppendingPathComponent:@"personModel"];
    
    Person *p1 = [[Person alloc] init];
    p1.name = @"ran";
    p1.age = @"18";
    
    [NSKeyedArchiver archiveRootObject:p1 toFile:filePath];


    //也可以使用这种方式写到Preferences personModel目录
    //NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    //NSData *data = [NSKeyedArchiver archivedDataWithRootObject:p1];
    //[userDefaults setObject:data forKey:@"personModel"];
    //[userDefaults synchronize];


}

- (IBAction)unarchive:(id)sender {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentFilePath = paths.firstObject  ;
    NSString *filePath = [documentFilePath stringByAppendingPathComponent:@"personModel"];
    
    Person *p1 = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath] ;
    
    NSLog(@"%@", p1.name);
    NSLog(@"%@", p1.age);

    //也可以使用这种方式取到Preferences personModel目录
    //NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
    //Person *p1 = (Person *)[NSKeyedUnarchiver unarchiveObjectWithData:[userDefaults objectForKey:@"personModel"]];
    //NSLog(@"%@", p1.name);
    //NSLog(@"%@", p1.age);
    
}

但是这种方法只能存储一个对象,存储多个对象要采用如下的方法:

- (IBAction)archiveManyObject:(id)sender {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentFilePath = paths.firstObject  ;
    NSString *filePath = [documentFilePath stringByAppendingPathComponent:@"personModel"];
    
    NSMutableData *data = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver =  [[NSKeyedArchiver alloc] initForWritingWithMutableData:data]; //将数据区连接到NSKeyedArchiver对象
    
    Person *p1 = [[Person alloc] init];
    p1.name = @"ran1";
    p1.age = @"18";
    [archiver encodeObject:p1 forKey:@"person1"];
    
    Person *p2 = [[Person alloc] init];
    p2.name = @"ran2";
    p2.age = @"19";
    [archiver encodeObject:p2 forKey:@"person2"];

    [archiver finishEncoding];
    
    [data writeToFile:filePath atomically:YES];
}

- (IBAction)unarchiveManyObject:(id)sender {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentFilePath = paths.firstObject  ;
    NSString *filePath = [documentFilePath stringByAppendingPathComponent:@"personModel"];
    NSData *data = [NSData dataWithContentsOfFile:filePath];
    
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    Person *p1 =  [unarchiver decodeObjectForKey:@"person1"];
    Person *p2 =  [unarchiver decodeObjectForKey:@"person2"];
    [unarchiver finishDecoding];
    
    NSLog(@"%@", p1.name);
    NSLog(@"%@", p2.name);
}

SQLite3

数据库(splite):
splite是一个轻量级,跨平台的小型数据库,可移植性比较高,有着和MySpl几乎相同的数据库语句,以及无需服务器即可使用的优点:

数据库的优点:

  1. 该方案可以存储大量的数据,存储和检索的速度非常快.
  2. 能对数据进行大量的聚合,这样比起使用对象来讲操作要快.

数据库的缺点:

  1. 它没有提供数据库的创建方式
  2. 它的底层是基于C语言框架设计的, 没有面向对象的API, 用起来非常麻烦
  3. 发杂的数据模型的数据建表,非常麻烦
    在实际开发中我们都是使用的是FMDB第三方开源的数据库,该数据库是基于splite封装的面向对象的框架.
#import "SqliteVC.h"
#import "Person.h"
@interface SqliteVC() {
    
    sqlite3 *_db;

}
@end

@implementation SqliteVC

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSString *fileName = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"student.sqlite"];
    NSLog(@"fileName = %@",fileName);
    
    int result = sqlite3_open(fileName.UTF8String, &_db); //创建(打开)数据库,如果数据库不存在,会自动创建  数据库文件的路径必须以C字符串(而非NSString)传入
    
    if (result == SQLITE_OK) {
        NSLog(@"成功打开数据库");
        
        char *errorMesg = NULL;
        const char *sql = "create table if not exists t_person (id integer primary key autoincrement, name text, age integer);";
        int result = sqlite3_exec(_db, sql, NULL, NULL, &errorMesg); //sqlite3_exec()可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据
        
        if (result == SQLITE_OK) {
            NSLog(@"成功创建t_person表");
        } else {
            NSLog(@"创建t_person表失败:%s",errorMesg);
        }
        
    } else {
        NSLog(@"打开数据库失败");
    }
}
- (IBAction)insert:(id)sender {
    for (int i = 0; i < 30; i++) {
        
        NSString *name = [NSString stringWithFormat:@"person-%d",arc4random()%100];
        int age = arc4random() % 100;
        
        char *errorMesg = NULL;
        NSString *sql = [NSString stringWithFormat:@"insert into t_person (name,age) values ('%@',%d);",name, age];
        int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errorMesg);
        
        if (result == SQLITE_OK) {
            NSLog(@"添加数据成功");
        } else {
            NSLog(@"添加数据失败");
        }
    }
}

- (IBAction)delete:(id)sender {
    char *errorMesg = NULL;
    NSString *sql = @"delete from t_person where age >= 0";
    int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errorMesg);
    
    if (result == SQLITE_OK) {
        NSLog(@"删除成功");
    }else {
        NSLog(@"删除失败");
    }
}

- (IBAction)query:(id)sender {
    const char *sql = "select id, name, age from t_person;";  //"select id, name, age from t_person where age >= 50;"
    sqlite3_stmt *stmt = NULL;  //定义一个stmt存放结果集
    int result = sqlite3_prepare_v2(_db, sql, -1, &stmt, NULL); //检测SQL语句的合法性
    
    if (result == SQLITE_OK) {
        NSLog(@"查询语句合法");
        
        while (sqlite3_step(stmt) == SQLITE_ROW) {
            
            int ID = sqlite3_column_int(stmt, 0);
            const unsigned char *sname = sqlite3_column_text(stmt, 1);
            NSString *name = [NSString stringWithUTF8String:(const char *)sname];
            int age = sqlite3_column_int(stmt, 2);
            
            NSLog(@"%d %@ %d",ID, name, age);
        }
    } else {
        NSLog(@"查询语句非法");
    }
}

- (IBAction)update:(id)sender {
    NSString *sql = @"update t_person set name = '哈哈' where age > 60";
    char *errorMesg = NULL;
    int result = sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errorMesg);
    
    if (result == SQLITE_OK) {
        NSLog(@"更改成功");
    }else {
        
        NSLog(@"更改失败");
    }
}


coreData

coreData是苹果官方在iOS5之后推出的综合性数据库,其使用了对象关系映射技术,将对象转换成数据,将数据存储在本地的数据库中
coreData为了提高效率,需要将数据存储在不同的数据库中,比如:在使用的时候,最好是将本地的数据保存到内存中,这样的目的是访问速度比较快.

CoreData与SQLite进行对比

SQLite
1、基于C接口,需要使用SQL语句,代码繁琐
2、在处理大量数据时,表关系更直观
3、在OC中不是可视化,不易理解


CoreData
1、可视化,且具有undo/redo能力
2、可以实现多种文件格式:
    * NSSQLiteStoreType
    * NSBinaryStoreType
    * NSInMemoryStoreType
    * NSXMLStoreTyp
3、苹果官方API支持,与iOS结合更紧密


CoreData核心类与结构

NSManagedObjectContext(数据上下文)

  • 对象管理上下文,负责数据的实际操作(重要)
  • 作用:插入数据,查询数据,删除数据,更新数据

NSPersistentStoreCoordinator(持久化存储助理)

  • 相当于数据库的连接器
  • 作用:设置数据存储的名字,位置,存储方式,和存储时机

NSManagedObjectModel(数据模型)

  • 数据库所有表格或数据结构,包含各实体的定义信息
  • 作用:添加实体的属性,建立属性之间的关系
  • 操作方法:视图编辑器,或代码

NSManagedObject(被管理的数据记录)

  • 数据库中的表格记录

NSEntityDescription(实体结构)

  • 相当于表格结构

NSFetchRequest(数据请求)

  • 相当于查询语句

后缀为.xcdatamodeld的包

  • 里面是.xcdatamodel文件,用数据模型编辑器编辑
  • 编译后为.momd或.mom文件

类关系图

开始创建coredata

步骤:
1.创建模型文件 [相当于一个数据库]
2.添加实体 [一张表]
3.创建实体类 [相当模型--表结构]
4.生成上下文 关联模型文件生成数据库
  1. 创建模型文件
    New File -> iOS -> Core Data ->Data Model
  1. 创建实体

创建实体

Codegen

  1. 创建实体类

创建实体类

创建结果如图所示:

创建结果

  1. 生成上下文 关联模型文件生成数据库,进行增删查改操作
#import "coredataVC.h"
#import <CoreData/CoreData.h>
#import "Student+CoreDataProperties.h"

@interface coredataVC ()

@property(nonatomic, strong)NSManagedObjectContext *context;

@end

@implementation coredataVC

- (void)viewDidLoad {
    [super viewDidLoad];

    //entity 记得勾选 language:objective-c 和 codegen:manual/none
    [self createSql];
}

- (void)createSql {
    //获取模型路径
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Person" withExtension:@"momd"];
    //根据模型文件创建模型对象
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    
    //利用模型对象创建持久化存储助理
    NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    
    //数据库的名称和路径
    NSString *docStr = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *sqlPath = [docStr stringByAppendingPathComponent:@"coreData.sqlite"];
    NSURL *sqlUrl = [NSURL fileURLWithPath:sqlPath];
    NSLog(@"数据库 path = %@", sqlPath);
    
    NSError *error = nil; //设置数据库相关信息 添加一个持久化存储库并设置类型和路径,NSSQLiteStoreType:SQLite作为存储库
    [store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:sqlUrl options:nil error:&error];
    
    if (error) {
        NSLog(@"添加数据库失败:%@",error);
    } else {
        NSLog(@"添加数据库成功");
    }
    
    //3、创建上下文 保存信息 对数据库进行操作 关联持久化助理
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    context.persistentStoreCoordinator = store;
    _context = context;
}

- (IBAction)insertClick:(id)sender {
    Student * student = [NSEntityDescription  insertNewObjectForEntityForName:@"Student"  inManagedObjectContext:_context];
    student.name = [NSString stringWithFormat:@"stu-%d",arc4random()%100];
    student.age = arc4random()%30;
    
    NSError *error = nil;
    if ([_context save:&error]) {
        NSLog(@"数据插入到数据库成功");
    }else{
        NSLog(@"数据插入到数据库失败");
    }
}

- (IBAction)deleteClick:(id)sender {
    //创建删除请求
    NSFetchRequest *deleRequest = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    //删除条件 没有任何条件就是读取所有的数据
    //NSPredicate *pre = [NSPredicate predicateWithFormat:@"age < %d", 10];
    //deleRequest.predicate = pre;
    
    //返回需要删除的对象数组
    NSArray *deleArray = [_context executeFetchRequest:deleRequest error:nil];
    
    //从数据库中删除
    for (Student *stu in deleArray) {
        [_context deleteObject:stu];
    }
    
    NSError *error = nil;
    if ([_context save:&error]) {
        NSLog(@"删除数据成功");
    }else{
        NSLog(@"删除数据失败, %@", error);
    }
}

- (IBAction)queryClick:(id)sender {
    //创建查询请求
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    //查询条件 没有任何条件就是读取所有的数据
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"age >= 0"];
    request.predicate = pre;
    
    // 从第几页开始显示 通过这个属性实现分页
    //request.fetchOffset = 0;
    // 每页显示多少条数据
    //request.fetchLimit = 6;
    
    //发送查询请求
    NSArray *resArray = [_context executeFetchRequest:request error:nil];
    
    //打印查询结果
    for (Student *stu in resArray) {
        NSLog(@"name=%@, age=%d",stu.name, stu.age);
    }
}

- (IBAction)updateClick:(id)sender {
    //创建查询请求
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Student"];
    
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"age >= 0"];
    request.predicate = pre;
    
    //发送请求
    NSArray *resArray = [_context executeFetchRequest:request error:nil];
    
    //修改
    for (Student *stu in resArray) {
        stu.name = @"ran";
    }
    
    NSError *error = nil;
    if ([_context save:&error]) {
        NSLog(@"更新数据成功");
    }else{
        NSLog(@"更新数据失败, %@", error);
    }
}

@end

发布了81 篇原创文章 · 获赞 68 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/li198847/article/details/102782169