iOS开发中的4种数据持久化方式【二、数据库 SQLite3、Core Data 的运用】

** 在上文,我们介绍了ios开发中的其中2种数据持久化方式:属性列表、归档解档
本节将继续介绍另外2种iOS持久化数据的方法:数据库 SQLite3、Core Data 的运用;**
在本节,将通过对4个文本框内容的创建、修改,退出后台,再重新回到后台,来认识这两种持久化数据的方式。效果图如下【图1】:


【图1 GUI界面效果图】
【本次开发环境: Xcode:7.2 iOS Simulator:iphone6 By:啊左】
(本节2个项目demo的下载:数据库SQLite3的运用DemoCore Data 的运用Demo

一、数据库SQLite3
SQLite(Strutcured Query Language,结构化查询语言),是iOS的嵌入式SQL数据库,在存储和检索大量数据方面非常有效,属于轻量级数据库,但是功能很强大。 安卓和iOS开发使用的都是SQLite数据库。 而另一种持久化数据方式,Core Data是对SQLite的封装,因为iOS中使用的SQLite是纯C语言的。

1、链接到SQLite3库
在Xcode中,使用Single View Application模板创建一个新项目,命名为persistence3。
新建项目选中项目导航列表(最左边)的顶部然后在主区域的TARGETS部分选中persistence3,注意要从TARGETS选中而不是从PROJECT部分。
选中后,点击“Build Phases”,打开在第三栏,按“+”添加“libsqlite3.0.tbd”【注意:Xcode7后dylib后缀改成tbd,如果仍要添加.bylib为后缀的链接,在添加framework那个对话框,最下面有个 "add other..." 点开之后, 按command+shift+G , 路径输入 /usr/lib/ ,然后 找到你需要的lib文件 就ok了。。 好吧,我还是习惯添加.dyib...】


【图2 链接SQLite3.dyib】
3是版本号,是SQLite的第三个版本。它是始终指向最新版本的SQLite3库的;

在“Main.storyboard”中拖入4个标签、4个文本框控件,拖动并对齐标签与文本框,并依次修改标签文本如【图1】,“ViewController.h”中添加一个装载4个文本框的数组“lineFields”:

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (nonatomic,strong)IBOutletCollection(UITextField) 
NSArray *lineFields;   //存储4个文本框字段的数组
@end

然后打开辅助编辑器,通过control键将4个文本框连接到 lineFields 这个数组,确保连接顺序为从顶部到底部!

在项目导航面板中,点击"ViewController.m" ,将以下2段代码添加到@implementation@end 的中间,与上文相同,这个方法在后面会一直调用:

#import "ViewController.h"
#import <sqlite3.h>   //导入SQLite3,注意是扩折号
//SQLite 是不区分大小写的
@implementation ViewController{ 
sqlite3 *sqlite; //数据库
}

//懒加载
-(NSString *)datafilePath{ 
NSArray *array = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
NSString *path = [array objectAtIndex:0]; 
return [path stringByAppendingPathComponent:@"data.sqlite"];
}

在这里,我们介绍一下iOS9的一个新的变化:UIAlertController。小小地在这里运用一下:

//警告提示框,为后面的操作向用户提示信息
-(void)alert:(NSString *)mes{ 
/*知识点:ios 9.0 后,简单的UIAlertView已经不能用了。 UIAlertController代替了UIAlertView弹框 和 UIActionSheet下弹框 */ 
//UIAlertControllerStyleAlert:中间; UIAlertControllerStyleActionSheet:显示在屏幕底部; 
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"警告" message:mes preferredStyle:(UIAlertControllerStyleAlert)]; 
UIAlertAction *cancel = [UIAlertAction actionWithTitle:@"取消" style:(UIAlertActionStyleCancel) handler:nil]; 
UIAlertAction *defult = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:nil]; 
[alert addAction:cancel]; 
[alert addAction:defult];
[self presentViewController:alert animated:YES completion:nil]; 
//呈现
}
  • (void)viewDidLoad 以及通知的方法代码:
 1 - (void)viewDidLoad { 
2  [super viewDidLoad]; 
3 int result = sqlite3_open([[self datafilePath]UTF8String], &sqlite); 
4 //不等于SQLITE_OK,则表示打开数据库的时候遇到问题 
5 if(result != SQLITE_OK) 
6  { 
7  sqlite3_close(sqlite); 
8 [self alert:@"数据库打开失败"]; 
9  }
10 
11 //定义一个语句,其中if not exists表示:如果不存在数据表,则新建一个。 若存在,则此命令自动退出. 所以这个语句可以在每次启动时调用
12 NSString *createSql = @"CREATE TABLE IF NOT EXISTS 'wenbenkuang'(id INTEGER PRIMARY KEY,datatext TEXT NOT NULL)";
13 char * error;
14 int ret = sqlite3_exec(sqlite,[createSql UTF8String], NULL, NULL, &error); //SQLite是纯C语言的。SQL语句需要使用“UTF8String”方法把NSString转换为char.
15 if(ret != SQLITE_OK)
16  {
17 [self alert:[NSString stringWithFormat:@"数据表创建失败%s",error]];
18  }
19 
20 //使用select语句加载数据,并要求数据库按行号准备排序,以便我们以相同的顺序获取,否则将使用sqlite3内部存储顺序
21 NSString *preSql = @"SELECT id,datatext FROM 'wenbenkuang'ORDER BY id";
22 sqlite3_stmt *statmt;
23 if(sqlite3_prepare_v2(sqlite,[preSql UTF8String], -1, &statmt, nil) == SQLITE_OK) //SQLITE_OK表成功加载
24  {
25 while (sqlite3_step(statmt) == SQLITE_ROW) 
26  {
27 int row = sqlite3_column_int(statmt, 0); //获取行号
28 char *rowData = (char *)sqlite3_column_text(statmt, 1); //获取该行数据
29 NSString *dataString = [[NSString alloc]initWithUTF8String:rowData];
30 UITextField *textfield = self.lineFields[row];
31 textfield.text = dataString;
32  }
33 //完成陈述
34  sqlite3_finalize(statmt);
35  }
36 //关闭数据库
37  sqlite3_close(sqlite);
38 
39 //注册一个观测者,进入后台时发送通知;
40 UIApplication *app = [UIApplication sharedApplication];
41 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:)  name:UIApplicationWillResignActiveNotification object:app];
42 }
43 -(void)applicationWillResignActiveNotification:(NSNotification *)notification
44 {
45 int result = sqlite3_open([[self datafilePath] UTF8String], &sqlite);
46 if (result != SQLITE_OK) {
47 [self alert:@"数据库打开失败"];
48  sqlite3_close(sqlite);
49  }
50 for(int i=0;i<4;i++)
51  {
52 UITextField *tetxField = self.lineFields[i];
53 char *updataSql = "INSERT OR REPLACE INTO 'wenbenkuang'(id,datatext) VALUES(?,?);";
54 sqlite3_stmt *stmt;
55 if(sqlite3_prepare_v2(sqlite, updataSql, -1, &stmt, nil) == SQLITE_OK)
56  {
57 sqlite3_bind_int(stmt, 1, i);
58 sqlite3_bind_text(stmt, 2, [tetxField.text UTF8String], -1, NULL);
59  }
60 if(sqlite3_step(stmt) != SQLITE_DONE)
61  {
62 [self alert:@"数据更新失败"];
63  }
64  sqlite3_finalize(stmt);
65  }
66  sqlite3_close(sqlite);
67 }

在这段的viewDidLoad代码中:
(行号为3-9)我们首先创建或者打开数据库,如果打开时遇到问题,则抛出警告框。
(行号为11-18)数据库将所有的数据存储在表中。因此,创建一个名为“wenbenkuang”数据表,包含一个标识为“id”的键,与一个名为"datatext"的不为空的文本项,如果已存在相同名称的表,则退出创建,不执行操作,所以该数据库语句可以在每次启动时调用一次,而不会影响到现有的数据库;【SQLite是纯C语言的。SQL语句需要使用“UTF8String”方法把NSString转换为char.】

(行号为20-23)加载数据,使用select语句加载数据,并要求数据库按行号准备排序,以便我们以相同的顺序获取,否则将使用sqlite3内部存储顺序;

(行号为25-32)遍历返回各行,定义一个int和char获取数据,然后,我们通过从数据库获取的数据设置我们的文本字段。

最后,关闭数据库,操作结束;

在 “applicationWillResignActiveNotification:” 方法中,我们也是首先打开数据库,创建一个字段名称,以便检测到输出,然后设计一条带2个绑定变量的INSERT OR REPLACE的SQL语句,第一变量表行,第二个表存储的实际字段值,接下来声明一个指向语句的指针,为语句添加绑定变量,并将值绑定到2个绑定标量中,
通过调用sqlite3_step来执行更新,循环执行该语句。
完成后,关闭操作数据库;接下来我们运行调试一下。

按“command+R”运行程序,为4个文本框输入字段,关闭Xcode,或按下“command+shift”,点击2次“H”键,退出该应用,然后再次打开,
看看是是否文本框中保留有原来自己的输入的字段。

二、Core Data的运用

首先,我们需要注意的是,Core Data是iOS上一个能够提高效率的数据库框架,但Core Data不是一种数据库,它的底层还是利用Sqlite3来存储数据的。
同样的,这次我们依然通过创建一个简单的persistence应用,来展示如何通过苹果自带的Core Data框架来实现持久化。
第一步,我们应该很熟悉了。在Xcode中,使用Single View Application模板创建一个新项目,命名为persistence4。
如【图3】,先别按Next,勾选“Use Core Data”,其中“Devices”为设备选项,你可以选择“iphone”或者“ipad”,这里我们选择通用“Universal”。点击Next!


【图3 创建persistence4】
这次在敲入代码前,我们还需要进行一系列的讨论,好吧。。。“Core Data”确实麻烦些。
1、Core Data的数据模型
之前,如上文的iOS开发中的4种数据持久化方式【一、属性列表与归档解档】所介绍的,在使用Core Data之前我们创建数据模型的方式便是创建一个NSObject的子类并让它们遵循NSCoding和NSCopying协议,以便能够对它们进行归档解档。但是,在Core Data中,我们使用了不同方式,不需要创建一个新子类,而是先在数据模型中创建实体(Entity),然后在代码中为这些实体创建托管对象(Managed Object)
实体,就是对对象的描述; 托管对象,表示在运行的过程该实体的具体实例;
(可以这么理解:实体、托管对象之间的关系类似于类与类的对象;)

2、键-值编码形式
在我们使用NSDictionary的时候,就已经使用到这种编码的形式了,但与NSDictionary相比,Core Data会复杂一些,只是基本概念是相同的。具体的操作方法如下:
首先定义一个托管对象: NSManagedObject *managedObject;
那么,就可以通过相应的方法获取name特性的数据值了:NSString *nameData = [managedObject valueForKey:@"name"];
为name特性设置新的属性值:[managedObject setValue:@"newNameData" forKey:@"name"];

3、动手:模型的创建。
在左边的项目导航面板上面单击“persistence4.xcdatamodeld”文件,此时打开了Xcode的数据模型编辑器,编辑器的面板中已经列出了数据模型中的所有实体、获取请求和配置。
由于我们还未创建数据模型,因此列表是空的,单击实体面板左下方的加号图标(Add Entity),此时创建了一个名为:“Entity”的实体:



(提醒一下的是,右下角的“Editor Style”选项:table视图和graph视图。这两种视图在数据模型上没有区别,只是显示的方式不同而已。如果你的模型里面包含多个实体,那么graph视图的显示方式会非常有用,它以图形化的方式呈现了所有实体之间的关系;由于table视图显示了当前实体更为详细的信息,因为我们在创建这个实体的时候,还是选用默认的table实体)

然后点击该实体,在右边的数据模型编辑器上面,把第一个name字段改为我们接下来使用的“Line”字段,所以我们这样就算创建了一个名为“Line”的实体了。


接下来就为“Line”实体添加新特性,单击并按住右下角的加号图标“Add Attribute”,当然添加特性的话也可以直接点击“+”图标就可以了,这里只是为了方便读者看到该选项所表达的意思。


点击“+”后,可以看到新增了一个名为“attribute”的特性,把它修改为“lineNumber”,修改Type为“Interger16”,并把右边圈红的Optional取消选中状态。


再次单击“+”,把新特性修改为“lineText”,修改Type为“String”,这里的Optional默认勾选(该选项用于防止我们创建的“lineText”文本在用户给定的字段为空,而“lineNumber”表行号,行号不会出现为空的情况,)


下面是代码部分,与SQLite的创建类似:
在“Main.storyboard”中拖入2个标签、2个文本框控件,拖动并对齐标签与文本框,并依次修改标签文本如【图1】,“ViewController.h”中添加一个装载2个文本框的数组“lineFields”:

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (nonatomic,strong)IBOutletCollection(UITextField) 
NSArray *lineFields; //存储2个文本框字段的数组
@end

然后打开辅助编辑器,通过control键将2个文本框连接到 lineFields 这个数组,确保连接顺序为从顶部到底部!
单击“AppDelegate.h”,我们能够已经包含了数据类型定义需要的代码了,我们做一些注释:

//COREDATA托管上下文
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
//托管模型
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
//存储时(持久化)协调者
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;
//保存托管上下文
- (void)saveContext;
//取得当前应用程序文档路径
- (NSURL *)applicationDocumentsDirectory;

点击“ViewController.m”,下面,我们进行一连串的代码:

 1 #import "ViewController.h" 
2 #import "AppDelegate.h" //需要导入app代理类 
3  
4 static NSString *const Zline = @"Line"; 
5 static NSString *const ZlineNumber = @"lineNumber"; 
6 static NSString *const ZlineText = @"lineText"; 
7  
8 @implementation ViewController 
9 
10 - (void)viewDidLoad {
11  [super viewDidLoad];
12 //获取应用程序代理
13 AppDelegate *appDe = [UIApplication sharedApplication].delegate;
14 //获取托管对象上下文(此时如果数据库不存在就不会创建数据库)
15 NSManagedObjectContext *context = [appDe managedObjectContext];
16 //创建请求(并传递实体描述“line”给它)
17 NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:Zline];
18 //通过上下文context执行请求request,获取记录对象的数组
19 NSArray *objetcs = [context executeFetchRequest:request error:nil];
20 if(objetcs == nil) //确保返回的是有效的数组
21  {
22 NSLog(@"数组创建失败");
23  }
24 //分别提取每个托管对象保存的数据
25 for(NSManagedObject *managed in objetcs)
26  {
27 //获取行号(注意转换为int)
28 int lineNum = [[managed valueForKey:ZlineNumber] intValue];
29 //获取文本
30 NSString *lineText = [managed valueForKey:ZlineText];
31 
32 UITextField *textField = self.lineFields[lineNum];
33 textField.text = lineText;
34  }
35 
36 //后台处理
37 UIApplication *app = [UIApplication sharedApplication];
38 [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(applicationWillResignActiveNotification:) name: UIApplicationWillResignActiveNotification object:app];
39 }
40 
41 -(void)applicationWillResignActiveNotification:(NSNotification *)notification
42 {
43 AppDelegate *appDe = [UIApplication sharedApplication].delegate;
44 NSManagedObjectContext *context = appDe.managedObjectContext;
45 
46 for(int i =0;i<2;i++)
47  {
48 NSFetchRequest *request = [[NSFetchRequest alloc]initWithEntityName:Zline];
49 //predicate谓语,创建请求,但是为确认存储中是否已有一个与字段对应的托管对象,创建谓语,是为了给字段标识正确的对象。
50 request.predicate = [NSPredicate predicateWithFormat:@"(lineNumber=%d)",i]; //注意谓语的拼写:@"(lineNumber = %d)",i
51 NSArray *objects = [context executeFetchRequest:request error:nil];
52 if(objects ==nil)
53  {
54 NSLog(@"无法获得请求数据%");
55  }
56 
57 //因为我们不知道是要从存储中加载托管对象,还是创建新的托管对象,因此先创建空的托管对象
58 NSManagedObject *managed = nil;
59 if([objects count]>0) //检查返回有效的对象,因此加载
60  {
61 managed = [objects objectAtIndex:0];
62  }
63 else //检查到无有效对象,因此创建新的托管对象
64  {
65 //创建实体、插入托管对象到获取的上下文,我们直接用下面这句代码,省去很多流程
66 managed = [NSEntityDescription insertNewObjectForEntityForName:Zline inManagedObjectContext:context];
67  }
68 UITextField *textField = self.lineFields[i];
69 //使用键-值码方式更新设置行号和文本:
70  [managed setValue:[NSNumber numberWithInt:i] forKey:ZlineNumber];
71  [managed setValue:textField.text forKey:ZlineText];
72  }
73 //完成循环。
74 //最后一步:持久化数据:
75  [appDe saveContext];
76 }
77
78 @end

我们解析一下上面的代码,首先需要导入我们创建Core Data模型时Xcode创建的已有代码的“AppDelegate.h”。
(4-6行) :定义包括实体“Line”、行号“lineNumber”、文本“lineText”等的字符段,方便我们后面的代码编写;(记得别拼错,别问我为什么怎么说...我就是刚刚在演示的时候一直报错,才发现原来只是字符串拼错了...555..心疼啊左);
(12-23行): 通过上下文context,执行请求request获取记录对象的数组。并确认数组有效。
(24-34行):分别提取每个托管对象保存的数据,并赋值给对应行号的文本框文本字段;
(36-39行):后台处理
(48-55行):执行带有谓语的请求。
(57-71行):分托管对象是否已经存在2种情况,进行编码设置行号和文本
(75行): 持久化数据(记得带上这行代码)

好了,关于Core Data数据模型和GUI界面,以及代码我们已经设计编写完毕。
按“command+R”运行程序,为2个文本框输入字段,关闭Xcode,或按下“command+shift”,点击2次“H”键,退出该应用,然后再次打开,
如果运行没有发生错误的话,那么我们已经设计了一个简单的Core Data储存数据应用“persistence4”了。

至此,我们包括上文iOS开发中的4种数据持久化方式【一、属性列表与归档解档】的演练,已经为大家展示了有关iOS开发中的4种数据持久化方式。
希望能够分享与读者。
只是,对于这两篇关于iOS开发数据持久化的所搭建的应用"persistence"例子,也只是一个简单的应用,具体的应用设计大家可以发挥创造力,让数据持久化在移动开发应用过程中更加便捷。
当然,更多更深入的数据持久化、数据处理,我们只懂得这些是远远不够的,需要我们通过网络上共享的内容以及自己工作学习中不断积累,才能使这些知识点更加深入,更加适应iOS开发工作。


(转载请标明原文出处,谢谢支持 ~ - ~)
 by:啊左~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容