Objective-C

Persist Data - 数据的持久化存储

2018-02-12  本文已影响4人  MichaelLedger

所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据。

  1. 偏好设置 (Preferences)
  2. 属性列表 (PropertyLists)
  3. 对象的归档&解档 (NSKeyedArchiver& NSKeyedUnarchiver)
  4. SQLite3 (FMDBSQLCipherWCDB)
  5. CoreData
  1. UIPasteboard
  2. Keychain
  3. iCloud
  4. 存储到服务器

...

沙盒机制

在介绍各种存储方法之前,有必要说明以下沙盒机制。
iOS应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文本文件等。


通过这张图只能从表层上理解sandbox是一种安全体系,应用程序的所有操作都要通过这个体系来执行,其中核心内容是:sandbox对应用程序执行各种操作的权限限制。

顾名思义,沙盒中不同的文件夹有不同的用途,所以在选择存放目录时,一定要认真选择适合的目录,否则会有审核被拒的可能(主要原因是ios的icloud的同步问题)。

NSSearchPathForDirectoriesInDomains方法用于查找目录,返回指定范围内的指定名称的目录的路径集合。有三个参数:
** directory ** NSSearchPathDirectory类型的enum值,表明我们要搜索的目录名称,比如这里用NSDocumentDirectory表明我们要搜索的是Documents目录。如果我们将其换成NSCachesDirectory就表示我们搜索的是Library/Caches目录。
** * domainMask* ** NSSearchPathDomainMask类型的enum值,指定搜索范围,这里的NSUserDomainMask表示搜索的范围限制于当前应用的沙盒目录。还可以写成NSLocalDomainMask(表示/Library)、NSNetworkDomainMask(表示/Network)等。
** * expandTilde* ** BOOL值,表示是否展开波浪线。我们知道在iOS中的全写形式是/User/userName,该值为YES即表示写成全写形式,为NO就表示直接写成“~”。

"应用程序包": 这里面存放的是应用程序的源文件,包括资源文件和可执行文件。

NSString *bundlePath = [[NSBundle mainBundle] bundlePath];

Documents: 苹果建议将程序中建立的或在程序中浏览到的文件数据保存在该目录下,iTunes备份和恢复的时候会包括此目录。最常用的目录,iTunes同步该应用时会同步此文件夹中的内容,适合存储重要数据。这个路径是存放用户重要的文档,并且这个路径下的文件会被苹果备份到iCloud(Apple默认是存放5年)。
这里要特别注意,如果你的APP中在这个路径下保存了“下载”文件,就会导致无法上架。所以一般会放应用程序本身生成的文件,例如用户登录之后的个人信息,或者游戏进度等内容。

NSString *docDir = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;

Library:存储程序的默认设置或其它状态信息。可创建子文件夹。可以用来放置您希望被备份但不希望被用户看到的数据。该路径下的文件夹,除Caches以外,都会被iTunes备份。

NSString *libDir = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];

Library/Caches: 存放缓存文件,iTunes不会备份此目录,此目录下文件不会在应用退出删除。iTunes不会同步此文件夹,适合存储体积大,不需要备份的非重要数据。

NSString *cachesDir = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;

Library/Preferences: iTunes同步该应用时会同步此文件夹中的内容,通常保存应用的设置信息。用于存储程序的默认设置或其它状态信息。

tmp:这个目录用于存放临时文件,保存应用程序再次启动过程中不需要的信息。iTunes不会备份此文件夹,系统的磁盘空间不够或者重新启动就会自动清空,所以此目录适合保存应用中的一些临时文件,用完即删,不需要手动删除,但是如果文件过大,建议用完手动删除。

NSString *tmpDir = NSTemporaryDirectory();

iTunes在与iPhone同步时,备份所有的Documents和除Caches以外的Library文件。
iPhone在重启时,会丢弃所有的tmp文件。


模拟器(Simulator)
bundlePath:/Users/MountainX/Library/Developer/CoreSimulator/Devices/F0A41D22-52BE-4F30-A200-F9DD0FA44D3F/data/Containers/Bundle/Application/BCF184B0-B606-4923-A57F-B9627DE0F078/Test.app
docDir:/Users/MountainX/Library/Developer/CoreSimulator/Devices/F0A41D22-52BE-4F30-A200-F9DD0FA44D3F/data/Containers/Data/Application/B5ABA2D0-C061-4278-8DDD-469FE8B6D799/Documents
libDir:/Users/MountainX/Library/Developer/CoreSimulator/Devices/F0A41D22-52BE-4F30-A200-F9DD0FA44D3F/data/Containers/Data/Application/B5ABA2D0-C061-4278-8DDD-469FE8B6D799/Library
cachesDir:/Users/MountainX/Library/Developer/CoreSimulator/Devices/F0A41D22-52BE-4F30-A200-F9DD0FA44D3F/data/Containers/Data/Application/B5ABA2D0-C061-4278-8DDD-469FE8B6D799/Library/Caches
tmpDir:/Users/MountainX/Library/Developer/CoreSimulator/Devices/F0A41D22-52BE-4F30-A200-F9DD0FA44D3F/data/Containers/Data/Application/B5ABA2D0-C061-4278-8DDD-469FE8B6D799/tmp/
真机(Device)
bundlePath:/var/containers/Bundle/Application/359CB843-BB2F-46F6-B1C0-288FE1191C9D/Test.app
docDir:/var/mobile/Containers/Data/Application/8B594896-A51B-4DDA-A69D-81D9793CF33A/Documents
libDir:/var/mobile/Containers/Data/Application/8B594896-A51B-4DDA-A69D-81D9793CF33A/Library
cachesDir:/var/mobile/Containers/Data/Application/8B594896-A51B-4DDA-A69D-81D9793CF33A/Library/Caches
tmpDir:/private/var/mobile/Containers/Data/Application/8B594896-A51B-4DDA-A69D-81D9793CF33A/tmp/
//Mac设置查看隐藏文件
//打开终端,输入命令
//显示Mac隐藏文件的命令:
defaults write com.apple.finder AppleShowAllFiles -bool true
//隐藏Mac隐藏文件的命令:
defaults write com.apple.finder AppleShowAllFiles -bool false
//输完单击Enter键,重新启动Finder就可以了
//重启Finder:鼠标单击窗口左上角的苹果标志-->强制退出-->Finder

一、偏好设置 (Preferences)

NSUserDefaults

/// -setInteger:forKey: is equivalent to -setObject:forKey: except that the value is converted from an NSInteger to an NSNumber.
- (void)setInteger:(NSInteger)value forKey:(NSString *)defaultName;
/*!
 -integerForKey: is equivalent to -objectForKey:, except that it converts the returned value to an NSInteger. If the value is an NSNumber, the result of -integerValue will be returned. If the value is an NSString, it will be converted to NSInteger if possible. If the value is a boolean, it will be converted to either 1 for YES or 0 for NO. If the value is absent or can't be converted to an integer, 0 will be returned.
 */
- (NSInteger)integerForKey:(NSString *)defaultName;
/*!
 -setObject:forKey: immediately stores a value (or removes the value if nil is passed as the value) for the provided key in the search list entry for the receiver's suite name in the current user and any host, then asynchronously stores the value persistently, where it is made available to other processes.
 */
- (void)setObject:(nullable id)value forKey:(NSString *)defaultName;

/*!
 -objectForKey: will search the receiver's search list for a default with the key 'defaultName' and return it. If another process has changed defaults in the search list, NSUserDefaults will automatically update to the latest values. If the key in question has been marked as ubiquitous via a Defaults Configuration File, the latest value may not be immediately available, and the registered value will be returned instead.
 */
- (nullable id)objectForKey:(NSString *)defaultName;

二、属性列表 (PropertyLists)

Xcode中的Info.plist的Source Code如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleDevelopmentRegion</key>
    <string>$(DEVELOPMENT_LANGUAGE)</string>
    <key>CFBundleExecutable</key>
    <string>$(EXECUTABLE_NAME)</string>
    <key>CFBundleIdentifier</key>
    <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>$(PRODUCT_NAME)</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>UILaunchStoryboardName</key>
    <string>LaunchScreen</string>
    <key>UIMainStoryboardFile</key>
    <string>Main</string>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>armv7</string>
    </array>
    <key>UISupportedInterfaceOrientations</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
</dict>
</plist>

三、对象的归档&解档 (NSKeyedArchiver& NSKeyedUnarchiver)

#import <Foundation/Foundation.h>
@interface Person : NSObject<NSCoding>
@property(nonatomic,copy)NSString *name;
@property(nonatomic,copy)NSString *phone;
@end
#import "Person.h"
@implementation Person
//编码
- (void)encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.name forKey:@"name"];
    [aCoder encodeObject:self.phone forKey:@"phone"];
}
//解码
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super init]) {
      self.name = [aDecoder decodeObjectForKey:@"name"];
      self.phone = [aDecoder decodeObjectForKey:@"phone"];
    }
    return self;
}
@end

特别提醒
如果父类使用runtime获取到property list
然后遍历property进行decode和encode操作
那么使用super的方法相当于执行了子类的方法,因为在父类的方法中执行[self class]和在子类中执行[self class]发送的消息是一样的。

#pragma mark - NSCoding
//特别提醒:仅限用于继承于NSObject的数据模型的归档&解档
-(void)encodeWithCoder:(NSCoder *)aCoder{
    unsigned int count=0;
    Ivar *ivar=class_copyIvarList([self class], &count);
    for (int i=0; i<count; i++) {
        Ivar iva=ivar[i];
        const char *name=ivar_getName(iva);
        NSString*strName=[NSString stringWithUTF8String:name];
        id value=[self valueForKey:strName];
        [aCoder encodeObject:value forKey:strName];
    }
    free(ivar);
}

-(instancetype)initWithCoder:(NSCoder *)aDecoder{
    if (self=[super init]) {
        unsigned int count=0;
        Ivar *ivar=class_copyIvarList([self class], &count);
        for (int i=0; i<count; i++) {
            Ivar iva=ivar[i];
            const char*name=ivar_getName(iva);
            NSString *strName=[NSString stringWithUTF8String:name];
            id value=[aDecoder decodeObjectForKey:strName];
            if (value) {
                [self setValue:value forKey:strName];
            }
        }
        free(ivar);
    }
    return self;
}

所以使用runtime进行编码&解码时,正确的使用姿势如下:

#pragma mark - NSCoding
-(void)encodeWithCoder:(NSCoder *)aCoder
{
    Class currentClass = self.class;
    if (currentClass == NSObject.class) {
        return;
    }
    while (currentClass && currentClass != [NSObject class])
    {
        unsigned int count = 0;
        Ivar *ivar = class_copyIvarList(currentClass, &count);
        if (count>0) {
            for (int i=0;i<count;i++) {
                NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar[i])];
                [aCoder encodeObject: [self valueForKey:key] forKey:key];
            }
        }
        currentClass = class_getSuperclass(currentClass);
        free(ivar);
    }
}

-(id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init]) {
        Class currentClass = self.class;
        while (currentClass && currentClass != [NSObject class])
        {
            unsigned int count = 0;
            Ivar *ivar = class_copyIvarList(currentClass, &count);
            if (count > 0) {
                for (int i = 0;i < count;i++) {
                    NSString *key = [NSString stringWithUTF8String:ivar_getName(ivar[i])];
                    id value = [aDecoder  decodeObjectForKey:key];
                    if (value) {
                        [self setValue:value forKey:key];
                    }
                }
            }
            currentClass = class_getSuperclass(currentClass);
            free(ivar);
        }
    }
    return  self;
}

class_copyPropertyList也是可以的,不过得encodeWithCoderinitWithCoder必须配套,因为ivar_getName得到的属性名称带下划线,而property_getName不带下划线!

#pragma mark - NSCoding
-(void)encodeWithCoder:(NSCoder *)aCoder  
{  
    Class currentClass = self.class;  
    if (currentClass == NSObject.class) {  
        return;  
    }  
    while (currentClass && currentClass != [NSObject class])  
    {  
        unsigned int count = 0;  
        objc_property_t *pList = class_copyPropertyList(currentClass, &count);  
        if (count>0) {  
            for (int i=0;i<count;i++) {  
                NSString *key = [NSString stringWithUTF8String:property_getName(pList[i])];  
                [aCoder encodeObject: [self valueForKey:key] forKey:key];  
            }  
        }  
        currentClass = class_getSuperclass(currentClass);  
        free(pList);  
    }  
}

-(id)initWithCoder:(NSCoder *)aDecoder  
{  
    Class currentClass = self.class;  
    if (currentClass == NSObject.class) {  
        return nil;  
    }  
    while (currentClass && currentClass != [NSObject class])  
    {  
        unsigned int count = 0;  
        objc_property_t *pList = class_copyPropertyList(currentClass, &count);  
        if (count > 0) {  
            for (int i = 0;i < count;i++) {  
                NSString *key = [NSString stringWithUTF8String:property_getName(pList[i])];  
                [self setValue:[aDecoder  decodeObjectForKey:key] forKey:key];  
            }  
        }  
        currentClass = class_getSuperclass(currentClass);  
        free(pList);  
    }  
    return  self;  
}  
  NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
  Person *person = [[Person alloc] init];
  person.avatar = self.avatarView.image;
  person.name = self.nameField.text;
  person.age = [self.ageField.text integerValue];
  [NSKeyedArchiver archiveRootObject:person toFile:file];
  NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.data"];
  Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
  if (person) {
     self.avatarView.image = person.avatar;
     self.nameField.text = person.name;
     self.ageField.text = [NSString stringWithFormat:@"%ld", person.age];
  }

四、SQLite3 (FMDB)
之前的所有存储方法,都是覆盖存储。如果想要增加一条数据就必须把整个文件读出来,然后修改数据后再把整个内容覆盖写入文件。所以它们都不适合存储大量的内容。

1.字段类型

表面上SQLite将数据分为以下几种类型:

实际上SQLite是无类型的。即不管你在创表时指定的字段类型是什么,存储是依然可以存储任意类型的数据。而且在创表时也可以不指定字段类型。SQLite之所以什么类型就是为了良好的编程规范和方便开发人员交流,所以平时在使用时最好设置正确的字段类型!主键必须设置成integer

  1. 准备工作

准备工作就是导入依赖库啦,在iOS中要使用SQLite3,需要添加库文件:libsqlite3.dylib并导入主头文件,这是一个C语言的库,所以直接使用SQLite3还是比较麻烦的。

3.使用

操作数据库之前必须先指定数据库文件和要操作的表,所以使用SQLite3,首先要打开数据库文件,然后指定或创建一张表。

/**
*  打开数据库并创建一个表
*/
- (void)openDatabase {
   //1.设置文件名
   NSString *filename = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"];
   //2.打开数据库文件,如果没有会自动创建一个文件
   NSInteger result = sqlite3_open(filename.UTF8String, &_sqlite3);
   if (result == SQLITE_OK) {
       NSLog(@"打开数据库成功!");
       //3.创建一个数据库表
       char *errmsg = NULL;
       sqlite3_exec(_sqlite3, "CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)", NULL, NULL, &errmsg);
       if (errmsg) {
           NSLog(@"错误:%s", errmsg);
       } else {
           NSLog(@"创表成功!");
       }
   } else {
       NSLog(@"打开数据库失败!");
   }
}

使用 sqlite3_exec() 方法可以执行任何SQL语句,比如创表、更新、插入和删除操作。但是一般不用它执行查询语句,因为它不会返回查询到的数据。

/**
*  往表中插入1000条数据
*/
- (void)insertData {
NSString *nameStr;
NSInteger age;
for (NSInteger i = 0; i < 1000; i++) {
  nameStr = [NSString stringWithFormat:@"Bourne-%d", arc4random_uniform(10000)];
  age = arc4random_uniform(80) + 20;
  NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_person (name, age) VALUES('%@', '%ld')", nameStr, age];
  char *errmsg = NULL;
  sqlite3_exec(_sqlite3, sql.UTF8String, NULL, NULL, &errmsg);
  if (errmsg) {
      NSLog(@"错误:%s", errmsg);
  }
}
NSLog(@"插入完毕!");
}

前面说过一般不使用 sqlite3_exec() 方法查询数据。因为查询数据必须要获得查询结果,所以查询相对比较麻烦。示例代码如下:

/**
*  从表中读取数据到数组中
*/
- (void)readData {
   NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1000];
   char *sql = "select name, age from t_person;";
   sqlite3_stmt *stmt;
   NSInteger result = sqlite3_prepare_v2(_sqlite3, sql, -1, &stmt, NULL);
   if (result == SQLITE_OK) {
       while (sqlite3_step(stmt) == SQLITE_ROW) {
           char *name = (char *)sqlite3_column_text(stmt, 0);
           NSInteger age = sqlite3_column_int(stmt, 1);
           //创建对象
           Person *person = [Person personWithName:[NSString stringWithUTF8String:name] Age:age];
           [mArray addObject:person];
       }
       self.dataList = mArray;
   }
   sqlite3_finalize(stmt);
}

4.总结

总得来说,SQLite3的使用还是比较麻烦的,因为都是些c语言的函数,理解起来有些困难。不过在一般开发过程中,使用的都是第三方开源库 FMDB,封装了这些基本的c语言方法,使得我们在使用时更加容易理解,提高开发效率。

FMDB

1.简介

FMDB是iOS平台的SQLite数据库框架,它是以OC的方式封装了SQLite的C语言API,它相对于cocoa自带的C语言框架有如下的优点:

使用起来更加面向对象,省去了很多麻烦、冗余的C语言代码

对比苹果自带的Core Data框架,更加轻量级和灵活

提供了多线程安全的数据库操作方法,有效地防止数据混乱

2.核心类

FMDB有三个主要的类:

一个FMDatabase对象就代表一个单独的SQLite数据库,用来执行SQL语句

使用FMDatabase执行查询后的结果集

用于在多线程中执行多个查询或更新,它是线程安全的

3.打开数据库

和c语言框架一样,FMDB通过指定SQLite数据库文件路径来创建FMDatabase对象,但FMDB更加容易理解,使用起来更容易,使用之前一样需要导入sqlite3.dylib。打开数据库方法如下:

NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingPathComponent:@"person.db"];
FMDatabase *database = [FMDatabase databaseWithPath:path];    
if (![database open]) {
    NSLog(@"数据库打开失败!");
}

值得注意的是,Path的值可以传入以下三种情况:

4.更新

在FMDB中,除查询以外的所有操作,都称为“更新”, 如:create、drop、insert、update、delete等操作,使用executeUpdate:方法执行更新:

//常用方法有以下3种:   
- (BOOL)executeUpdate:(NSString*)sql, ...
- (BOOL)executeUpdateWithFormat:(NSString*)format, ...
- (BOOL)executeUpdate:(NSString*)sql withArgumentsInArray:(NSArray *)arguments
//示例
[database executeUpdate:@"CREATE TABLE IF NOT EXISTS t_person(id integer primary key autoincrement, name text, age integer)"];   
//或者  
[database executeUpdate:@"INSERT INTO t_person(name, age) VALUES(?, ?)", @"Bourne", [NSNumber numberWithInt:42]];

5.查询

查询方法也有3种,使用起来相当简单:

- (FMResultSet *)executeQuery:(NSString*)sql, ...
- (FMResultSet *)executeQueryWithFormat:(NSString*)format, ...
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray *)arguments

查询示例:

//1.执行查询
FMResultSet *result = [database executeQuery:@"SELECT * FROM t_person"];
//2.遍历结果集
while ([result next]) {
    NSString *name = [result stringForColumn:@"name"];
    int age = [result intForColumn:@"age"];
}

6.线程安全

在多个线程中同时使用一个FMDatabase实例是不明智的。不要让多个线程分享同一个FMDatabase实例,它无法在多个线程中同时使用。 如果在多个线程中同时使用一个FMDatabase实例,会造成数据混乱等问题。所以,请使用 FMDatabaseQueue,它是线程安全的。以下是使用方法:

FMDatabaseQueue *queue = [FMDatabaseQueue databaseQueueWithPath:aPath];

[queue inDatabase:^(FMDatabase *database) {    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];      
          FMResultSet *result = [database executeQuery:@"select * from t_person"];    
         while([result next]) {   
         }    
}];

而且可以轻松地把简单任务包装到事务里:

[queue inTransaction:^(FMDatabase *database, BOOL *rollback) {    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_1", [NSNumber numberWithInt:1]];    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_2", [NSNumber numberWithInt:2]];    
          [database executeUpdate:@"INSERT INTO t_person(name, age) VALUES (?, ?)", @"Bourne_3", [NSNumber numberWithInt:3]];      
          FMResultSet *result = [database executeQuery:@"select * from t_person"];    
             while([result next]) {   
             }   
           //回滚
           *rollback = YES;  
    }];

FMDatabaseQueue 后台会建立系列化的G-C-D队列,并执行你传给G-C-D队列的块。这意味着 你从多线程同时调用调用方法,GDC也会按它接收的块的顺序来执行。


五、CoreData

  1. CoreData的简单使用

准备工作

新建文件,选择CoreData -> DataModel
添加实体(表),Add Entity
给表中添加属性,点击Attributes下方的‘+’号

新建文件,选择CoreData -> NSManaged Object subclass
根据提示,选择实体
通过代码,关联数据库和实体

- (void)viewDidLoad {
    [super viewDidLoad];

    /*
     * 关联的时候,如果本地没有数据库文件,Coreadata自己会创建
     */
    
    // 1. 上下文
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
    
    // 2. 上下文关连数据库

    // 2.1 model模型文件
    NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
    
    // 2.2 持久化存储调度器
    // 持久化,把数据保存到一个文件,而不是内存
    NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    
    // 2.3 设置CoreData数据库的名字和路径
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *sqlitePath = [doc stringByAppendingPathComponent:@"company.sqlite"];

    [store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:sqlitePath] options:nil error:nil];
    
    context.persistentStoreCoordinator = store;
    _context = context;

}

CoreData的基本操作 (CURD)

-(IBAction)addEmployee{

    // 创建一个员工对象 
    //Employee *emp = [[Employee alloc] init]; 不能用此方法创建
    Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:_context];
    emp.name = @"wangwu";
    emp.height = @1.80;
    emp.birthday = [NSDate date];
    
    // 直接保存数据库
    NSError *error = nil;
    [_context save:&error];
    
    if (error) {
        NSLog(@"%@",error);
    }
}
-(IBAction)readEmployee{
    
    // 1.FetchRequest 获取请求对象
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 2.设置过滤条件
    // 查找zhangsan
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@",
                        @"zhangsan"];
    request.predicate = pre;
    
    // 3.设置排序
    // 身高的升序排序
    NSSortDescriptor *heigtSort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:NO];
    request.sortDescriptors = @[heigtSort];
    
    // 4.执行请求
    NSError *error = nil;
    
    NSArray *emps = [_context executeFetchRequest:request error:&error];
    if (error) {
        NSLog(@"error");
    }
    
    //NSLog(@"%@",emps);
    //遍历员工
    for (Employee *emp in emps) {
        NSLog(@"名字 %@ 身高 %@ 生日 %@",emp.name,emp.height,emp.birthday);
    }
}
-(IBAction)updateEmployee{
    // 改变zhangsan的身高为2m
    
    // 1.查找到zhangsan
    // 1.1FectchRequest 抓取请求对象
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 1.2设置过滤条件
    // 查找zhangsan
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@", @"zhangsan"];
    request.predicate = pre;

    // 1.3执行请求
    NSArray *emps = [_context executeFetchRequest:request error:nil];
     
    // 2.更新身高
    for (Employee *e in emps) {
        e.height = @2.0;
    }
    
    // 3.保存
    NSError *error = nil;
    [_context save:&error];
    
    if (error) {
        NSLog(@"%@",error);
    }
}
-(IBAction)deleteEmployee{
    
    // 删除 lisi
    
    // 1.查找lisi
    // 1.1FectchRequest 抓取请求对象
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 1.2设置过滤条件
    // 查找zhangsan
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"name = %@",
                        @"lisi"];
    request.predicate = pre;
    
    // 1.3执行请求
    NSArray *emps = [_context executeFetchRequest:request error:nil];
    
    // 2.删除
    for (Employee *e in emps) {
        [_context deleteObject:e];
    }
    
    // 3.保存
    NSError *error = nil;
    [_context save:&error];
    
    if (error) {
        NSLog(@"%@",error);
    }

}
  1. CoreData的表关联

准备工作

新建文件,选择CoreData -> DataModel
添加实体(表),Add Entity , 注意:这里根据关联添加多个实体
给表中添加属性,点击Attributes下方的‘+’号

新建文件,选择CoreData -> NSManaged Object subclass
根据提示,选择实体,注意:这里先选择被关联的实体,最后添加最上层的实体
通过代码,关联数据库和实体

- (void)viewDidLoad {
    [super viewDidLoad];

    /*
     * 关联的时候,如果本地没有数据库文件,Coreadata自己会创建
     */
    
    // 1. 上下文
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
    
    // 2. 上下文关连数据库

    // 2.1 model模型文件
    NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
    
    // 2.2 持久化存储调度器
    // 持久化,把数据保存到一个文件,而不是内存
    NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    
    // 2.3 设置CoreData数据库的名字和路径
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *sqlitePath = [doc stringByAppendingPathComponent:@"company.sqlite"];

    [store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:sqlitePath] options:nil error:nil];
    
    context.persistentStoreCoordinator = store;
    _context = context;

}

基本操作

-(IBAction)addEmployee{

    // 1. 创建两个部门 ios android
    //1.1 iOS部门
    Department *iosDepart = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:_context];
    iosDepart.name = @"ios";
    iosDepart.departNo = @"0001";
    iosDepart.createDate = [NSDate date];
    
    //1.2 Android部门
    Department *andrDepart = [NSEntityDescription insertNewObjectForEntityForName:@"Department" inManagedObjectContext:_context];
    andrDepart.name = @"android";
    andrDepart.departNo = @"0002";
    andrDepart.createDate = [NSDate date];
    
    //2. 创建两个员工对象 zhangsan属于ios部门 lisi属于android部门
    //2.1 zhangsan
    Employee *zhangsan = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:_context];
    zhangsan.name = @"zhangsan";
    zhangsan.height = @(1.90);
    zhangsan.birthday = [NSDate date];
    zhangsan.depart = iosDepart;
    
    //2.2 lisi
    Employee *lisi = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:_context];
    lisi.name = @"lisi";
    lisi.height = @2.0;
    lisi.birthday = [NSDate date];
    lisi.depart = andrDepart;
    
    //3. 保存数据库
    NSError *error = nil;
    [_context save:&error];
    
    if (error) {
        NSLog(@"%@",error);
    }
}
-(IBAction)readEmployee{
    
    // 读取ios部门的员工
    
    // 1.FectchRequest 抓取请求对象
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 2.设置过滤条件
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"depart.name = %@",@"android"];
    request.predicate = pre;
    
      // 4.执行请求
    NSError *error = nil;
    
    NSArray *emps = [_context executeFetchRequest:request error:&error];
    if (error) {
        NSLog(@"error");
    }
    
    //遍历员工
    for (Employee *emp in emps) {
        NSLog(@"名字 %@ 部门 %@",emp.name,emp.depart.name);
    }
}

其他功能与前几种类似,这里不在赘述

  1. CoreData的模糊查询
-(IBAction)readEmployee{
    // 1.FectchRequest 抓取请求对象
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];

    // 2.设置排序
    // 按照身高的升序排序
    NSSortDescriptor *heigtSort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:NO];
    request.sortDescriptors = @[heigtSort];
    
    // 3.模糊查询
    // 3.1 名字以"wang"开头
//    NSPredicate *pre = [NSPredicate predicateWithFormat:@"name BEGINSWITH %@",@"wangwu1"];
//    request.predicate = pre;
    
    // 名字以"1"结尾
//    NSPredicate *pre = [NSPredicate predicateWithFormat:@"name ENDSWITH %@",@"1"];
//    request.predicate = pre;

    // 名字包含"wu1"
//    NSPredicate *pre = [NSPredicate predicateWithFormat:@"name CONTAINS %@",@"wu1"];
//    request.predicate = pre;
    
    // like 匹配
    NSPredicate *pre = [NSPredicate predicateWithFormat:@"name like %@",@"*wu12"];
    request.predicate = pre;

    // 4.执行请求
    NSError *error = nil;
    NSArray *emps = [_context executeFetchRequest:request error:&error];
    if (error) {
        NSLog(@"error");
    }
    
    //遍历员工
    for (Employee *emp in emps) {
        NSLog(@"名字 %@ 身高 %@ 生日 %@",emp.name,emp.height,emp.birthday);
    }
}
-(void)pageSeacher{
    // 1. FectchRequest 抓取请求对象
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Employee"];
    
    // 2. 设置排序
    // 身高的升序排序
    NSSortDescriptor *heigtSort = [NSSortDescriptor sortDescriptorWithKey:@"height" ascending:NO];
    request.sortDescriptors = @[heigtSort];
    
    // 3. 分页查询
    // 总有共有15数据
    // 每次获取6条数据
    // 第一页 0,6
    // 第二页 6,6
    // 第三页 12,6 3条数据
    
    // 3.1 分页的起始索引
    request.fetchOffset = 12;
    
    // 3.2 分页的条数
    request.fetchLimit = 6;
    
    // 4. 执行请求
    NSError *error = nil;
    NSArray *emps = [_context executeFetchRequest:request error:&error];
    if (error) {
        NSLog(@"error");
    }
    
    // 5. 遍历员工
    for (Employee *emp in emps) {
        NSLog(@"名字 %@ 身高 %@ 生日 %@",emp.name,emp.height,emp.birthday);
    }
}
  1. 多个数据库的使用
- (void)viewDidLoad {
    [super viewDidLoad];
    
    // 一个数据库对应一个上下文
    _companyContext = [self setupContextWithModelName:@"Company"];
    _weiboContext = [self setupContextWithModelName:@"Weibo"];
}       

/**
 *  根据模型文件,返回一个上下文
 */
-(NSManagedObjectContext *)setupContextWithModelName:(NSString *)modelName{
    
    // 1. 上下文
    NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];
    
    // 2. 上下文关连数据库
    // 2.1 model模型文件
    
    // 注意:如果使用下面的方法,如果 bundles为nil 会把bundles里面的所有模型文件的表放在一个数据库
    //NSManagedObjectModel *model = [NSManagedObjectModel mergedModelFromBundles:nil];
    
    // 改为以下的方法获取:
    NSURL *companyURL = [[NSBundle mainBundle] URLForResource:modelName withExtension:@"momd"];
    NSManagedObjectModel *model = [[NSManagedObjectModel alloc] initWithContentsOfURL:companyURL];
    
    // 2.2 持久化存储调度器
    // 持久化,把数据保存到一个文件,而不是内存
    NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    
    // 2.3 告诉Coredata数据库的名字和路径
    NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    NSString *sqliteName = [NSString stringWithFormat:@"%@.sqlite",modelName];
    NSString *sqlitePath = [doc stringByAppendingPathComponent:sqliteName];

    [store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:sqlitePath] options:nil error:nil];
    
    context.persistentStoreCoordinator = store;
    
    // 3. 返回上下文
    return context;
}
-(IBAction)addEmployee{
    // 1. 添加员工
    Employee *emp = [NSEntityDescription insertNewObjectForEntityForName:@"Employee" inManagedObjectContext:_companyContext];
    emp.name = @"zhagsan";
    emp.height = @2.3;
    emp.birthday = [NSDate date];
    
    // 直接保存数据库
    [_companyContext save:nil];
    
    // 2. 发微博
    Status *status =[NSEntityDescription insertNewObjectForEntityForName:@"Status" inManagedObjectContext:_weiboContext];
    
    status.text = @"发了一条微博!";
    status.createDate = [NSDate date];
    
    [_weiboContext save:nil];
}

参考:
iOS中几种数据持久化方案
iOS学习之iOS沙盒(sandbox)机制和文件操作
iOS 沙盒目录结构及正确使用
IOS 沙盒机制 && 关于document\library\tmp的灵活使用
iOS中几种数据持久化方案:我要永远地记住你!
我要娶你做我的CoreData!
iOS 沙盒路径详解(存储路径的选择)

上一篇 下一篇

猜你喜欢

热点阅读