ios数据持久化(转)
- 文件系统
- 归档和序列化
- 数据库
1.文件系统
不管是Mac OS X 还是iOS的文件系统都是建立在UNIX文件系统基础之上的。
1.1 沙盒模型
在iOS中,一个App的读写权限只局限于自己的沙盒目录中。
沙盒模型到底有哪些好处呢?
安全:别的App无法修改你的程序或数据
保护隐私:别的App无法读取你的程序和数据
方便删除:因为一个App所有产生的内容都在自己的沙盒中,所以删除App只需要将沙盒删除就可以彻底删除程序了
iOS App沙盒中的目录
- App Bundle ,如xxx.app 其实是一个目录,里面有app本身的二进制数据以及资源文件
- Documents, 存放程序产生的文档数据
- Library , 下面默认包含下面两个目录 Caches Preferences
- tmp, 临时文件目录
如果我们想在程序中获取上面某个目录的路径,应该如何实现呢? 下面就讲讲路径的获取, 通过NSPathUtilities.h中的NSSearchPathForDirectoriesInDomains函数,我们便可以获取我们想要的路径。 此函数具体声明如下:
NSArray *NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory directory, NSSearchPathDomainMask domainMask, BOOL expandTilde);
directory 目录类型 比如Documents目录 就是NSDocumentDirectory
domainMask 在iOS的程序中这个取NSUserDomainMask
expandTilde YES,表示将~展开成完整路径
注意函数返回的类型为数组,在iOS中一般这个数组中只包含一个元素,所以直接取lastObject即可。
1.2 NSFileManager
NSFileManager提供一个类方法获得一个单例。
/* Returns the default singleton instance.*/ + (NSFileManager *)defaultManager;
下面罗列了NSFileManager的常用方法
- 新建目录
- (BOOL)createDirectoryAtPath:(NSString *)path withIntermediateDirectories:(BOOL)createIntermediates attributes:(NSDictionary *)attributes error:(NSError **)error;
createIntermediates这个参数一般为YES,表示如果目录路径中间的某个目录不存在则创建之,如果是NO的话,则要保证所创建目录的父目录都必须已经存在
- 获取目录下的所有文件
- (NSArray *)contentsOfDirectoryAtPath:(NSString *)path error:(NSError **)error;
如果目录为空,则返回空数组
- 其他的一些方法
- 1234
- (
BOOL
)copyItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (
BOOL
)moveItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (
BOOL
)linkItemAtPath:(NSString *)srcPath toPath:(NSString *)dstPath error:(NSError **)error;
- (
BOOL
)removeItemAtPath:(NSString *)path error:(NSError **)error;
更多的可以查看文档 NSFileManager Class Reference。
在实际项目中,我们一般会写一个工具类来负责项目中所有的路径操作。
2. 归档(Archives) 和 序列化(Serializations)
我们经常听到“序列化”,“反序列化”这样的字眼,其实“序列化”的意思就是将对象转换成字节流以便保存或传输,“反序列化”便是一个相反的过程,从字节流转到对象。
在这节中涉及到一种文件类型plist,plist就是Property List 的缩写,即所谓的属性列表,属性列表有两种数据格式,一种是XML的,方便阅读和编辑;另一种是二进制的,节省存储空间,以及提高效率。
在Objective-C中这个对象和字节流的互转分成两类:
- 归档 普通自定义对象和字节流之间的转换
- 序列化 某些特定类型(NSDictionary, NSArray, NSString, NSDate, NSNumber,NSData)的数据和字节流之间(通常将其保存为plist文件)的转换
不过本质上讲上述两种都是对象图(Object Graph)和字节流之间的转换. Apple关于序列化和归档的编程指南: Archives and Serializations Programming Guide 。
2.1 归档
如果我们需要将自定义的一个对象保存到文件,应该如何做呢?
这里引入两个东西:一个是NSCoding协议 ;另一个是NSKeyedArchiver,NSKeyedArchiver其实继承于NSCoder,可以以键值对的方式将对象的属性进行序列化和反序列化。
具体的过程可以这样描述 通过NSKeyedArchiver 可以将实现了NSCoding协议的对象 和 字节流 相互转换 。
像一些框架中的数据类型如NSDictionary,NSArray,NSString... 都已经实现了NSCoding协议,所以可以直接对他们进行归档操作。
这里来一个比较完整的例子,一个Address类,一个User类,User类下有个Address类型的属性。
Address类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
<strong>@interface Address : NSObject<NSCoding>{ NSString *country; NSString *city; } @property(nonatomic,copy) NSString *country; @property(nonatomic,copy) NSString *city; @end ////////////////////////////////////////////////////// #import "Address.h" @implementation Address @synthesize country; @synthesize city; - ( void )encodeWithCoder:(NSCoder *)aCoder{ [aCoder encodeObject:country forKey:@ "country" ]; [aCoder encodeObject:city forKey:@ "city" ]; } - (id)initWithCoder:(NSCoder *)aDecoder{ if (self = [super init]) { [self setCountry:[aDecoder decodeObjectForKey:@ "country" ]]; [self setCity:[aDecoder decodeObjectForKey:@ "city" ]]; } return self; } @end</strong> |
User类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
<strong>#import <Foundation/Foundation.h> #import "Address.h" @interface User : NSObject<NSCoding>{ NSString *_name; NSString *_password; Address *_address; } @property(nonatomic,copy) NSString *name; @property(nonatomic,copy) NSString *password; @property(nonatomic,retain) Address *address; @end ///////////////////////////////////////////////////////// #import "User.h" @implementation User @synthesize name = _name; @synthesize password = _password; @synthesize address = _address; - ( void )encodeWithCoder:(NSCoder *)aCoder{ [aCoder encodeObject:_name forKey:@ "name" ]; [aCoder encodeObject:_password forKey:@ "password" ]; [aCoder encodeObject:_address forKey:@ "address" ]; } - (id)initWithCoder:(NSCoder *)aDecoder{ if (self = [super init]) { [self setName:[aDecoder decodeObjectForKey:@ "name" ]]; [self setPassword:[aDecoder decodeObjectForKey:@ "password" ]]; [self setAddress:[aDecoder decodeObjectForKey:@ "address" ]]; } return self; } @end </strong> |
使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<strong>Address *myAddress = [[[Address alloc] init] autorelease]; myAddress.country = @ "中国" ; myAddress.city = @ "杭州" ; User *user = [[[User alloc] init] autorelease]; user.name = @ "卢克" ; user.password = @ "lukejin" ; user.address = myAddress; [NSKeyedArchiver archiveRootObject:user toFile:@ "/Users/Luke/Desktop/user" ]; id object = [NSKeyedUnarchiver unarchiveObjectWithFile:@ "/Users/Luke/Desktop/user" ]; NSLog(@ "Object Class : %@" ,[object class ]);</strong> |
通过查看文件内容可以发现,保存的是plist的二进制数据格式。 转成XML可以看到如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
|
<?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>$archiver</key> <string>NSKeyedArchiver</string> <key>$objects</key> <array> <string>$null</string> <dict> <key>$ class </key> <dict> <key>CF$UID</key> <integer>8</integer> </dict> <key>address</key> <dict> <key>CF$UID</key> <integer>4</integer> </dict> <key>name</key> <dict> <key>CF$UID</key> <integer>2</integer> </dict> <key>password</key> <dict> <key>CF$UID</key> <integer>3</integer> </dict> </dict> <string>卢克</string> <string>lukejin</string> <dict> <key>$ class </key> <dict> <key>CF$UID</key> <integer>7</integer> </dict> <key>city</key> <dict> <key>CF$UID</key> <integer>6</integer> </dict> <key>country</key> <dict> <key>CF$UID</key> <integer>5</integer> </dict> </dict> <string>中国</string> <string>杭州</string> <dict> <key>$classes</key> <array> <string>Address</string> <string>NSObject</string> </array> <key>$classname</key> <string>Address</string> </dict> <dict> <key>$classes</key> <array> <string>User</string> <string>NSObject</string> </array> <key>$classname</key> <string>User</string> </dict> </array> <key>$top</key> <dict> <key>root</key> <dict> <key>CF$UID</key> <integer>1</integer> </dict> </dict> <key>$version</key> <integer>100000</integer> </dict> </plist> |
2.2 序列化
在实际的项目中,我们一般是将NSDictionary或NSArray的对象保存到文件或者从文件读取成对象。 当然这种只是适用于数据量不是很大的应用场景。 NSDictionary和NSArray 都有一个写入文件的方法
- (BOOL)writeToFile:(NSString *)path atomically:(BOOL)useAuxiliaryFile;
NSDictionary和NSArray会直接写成plist文件。
2.2.1 序列化的方式
序列化可以通过两种途径来进行
使用数据对象自带的方法
写文件
1
2
3
4
|
NSMutableDictionary *dataDictionary = [[[NSMutableDictionary alloc] init] autorelease]; [dataDictionary setValue:[NSNumber numberWithInt:222] forKey:@ "intNumber" ]; [dataDictionary setValue:[NSArray arrayWithObjects:@ "1" ,@ "2" , nil] forKey:@ "testArray" ]; [dataDictionary writeToFile:@ "/Users/Luke/Desktop/test.plist" atomically:YES]; |
写完的文件内容如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
<?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>intNumber</key> <integer>222</integer> <key>testArray</key> <array> <string>1</string> <string>2</string> </array> </dict> </plist> |
从文件读取
NSDictionary *dictionaryFromFile = [NSDictionary dictionaryWithContentsOfFile:@"/Users/Luke/Desktop/test.plist"];
使用NSPropertyListSerialization类
通过NSPropertyListSerialization类可以将数据对象直接转成NSData或者直接写到文件或者流中去.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
NSMutableDictionary *dataDictionary = [[[NSMutableDictionary alloc] init] autorelease]; [dataDictionary setValue:[NSNumber numberWithInt:222] forKey:@ "intNumber" ]; [dataDictionary setValue:[NSArray arrayWithObjects:@ "1" ,@ "2" , nil] forKey:@ "testArray" ]; NSString *error; NSData *xmlData = [NSPropertyListSerialization dataFromPropertyList:dataDictionary format:NSPropertyListXMLFormat_v1_0 errorDescription:&error]; if (xmlData) { NSLog(@ "No error creating XML data." ); [xmlData writeToFile:@ "/Users/Luke/Desktop/test2.plist" atomically:YES]; } else { if (error) { NSLog(@ "error:%@" , error); [error release]; } } |
读取
1
2
3
4
5
|
NSDictionary *dictionaryFromFile = (NSDictionary *)[NSPropertyListSerialization propertyListWithData:[NSData dataWithContentsOfFile:@ "/Users/Luke/Desktop/test2.plist" ] options:0 format:NULL error:&error]; |
2.2.2 User Defaults
User Defaults 顾名思义就是一个用户为系统以及程序设置的默认值。每个用户都有自己的一套数据,用户和用户之间没法共享的。
我们都知道每一个程序都会保存一些设置数据,比如记住上次窗口的位置和大小,记住是否弹出某些提示信息等。苹果提供了一个统一的解决方案,就是每一个app都有一个plist文件专门用以保存偏好设置数据。 plist文件名默认是程序Bundle identifier,扩展名为plist.
除了程序自己的设置外,系统还有一些全局的或者其它的一些设置,也属于User Defaults的范畴,User Defaults的持久化数据都保存在 ~/Library/Preferences 目录中.
这里有一点简要的说一下,User Defaults 中存放的key value分放在多个Domain中,取的时候按一定的次序取查找,次序如下:
- The Argument Domain 程序启动的时候以参数的方式传入的
- The Application Domain 通过NSUserDefaults往里面写数据的时候默认就是写到这个Domain的,通过Bundle identifier来标识
- The Global Domain 用户的全局的设置(系统的偏好设置)会放在这个Domain下,比如用户的语言设置,滚动条的设置等,里面的设置会对所有的程序起作用。
- The Languages Domains
- The Registration Domain 这个domain里面的key value是提供默认值的,一般会在程序启动的设置进行设置,他们都不会被持久化到文件的。当某个key对应的值在上面的那些domain中都不存在的时候,就到这里找。
Mac系统还为user defaults提供了很好的命令行工具,defaults 你可以通过下面的方式查看具体使用方式
man defaults
可以通过defaults domains查看当前用户的所有的domain,通过 defaults read NSGlobalDomain 读取 The Global Domain 中的所有值。
NSUserDefaults 类来读写Preferences设置,而无需考虑文件位置等细节问题。
NSUserDefaults 用起来和 NSDictionary 很相似,多了一个Domain的概念在里面。NSUserDefaults 一样提供了一个获取单例的方法.
+ (NSUserDefaults *)standardUserDefaults
NSUserDefaults提供了一系列的接口来根据key获取对应的value,搜索的次序按照上面提及到的次序在各个Domain中进行查找。还提供了一系列的 Setting Default Values的方法,这些设置的值都是在 The Application Domain 下的.当然也提供了修改其他Domain下的值的方法,只是需要整体的设置。
3.数据库
Mac上自带安装了SQLite3 ,如果你之前接触过关系型数据库,你可以通过命令行来对SQLite进行初步的认识
$ sqlite3 test.db
SQLite version 3.7.5
Enter ".help" for instructions
Enter SQL statements terminated with a ";" sqlite>create table if not exists names(id integer primary key asc, name text);
sqlite> insert into names(name) values('Luke');
sqlite> select * from names;
1|Luke
sqlite>
那如果在代码中使用SQLite呢?
- 添加sqlite的动态链接库 libsqlite3.0.dylib
- 引入头文件 #import "sqlite3.h"
这样之后你便可以通过C的接口来操作数据库了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
qlite3 *database; //sqlite3的类型其实只是一个结构体struct NSArray *documentsPaths=NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask , YES); NSString *databaseFilePath=[[documentsPaths objectAtIndex:0] stringByAppendingPathComponent:@ "luke.db" ]; //打开数据库 if (sqlite3_open([databaseFilePath UTF8String], &database)==SQLITE_OK) { NSLog(@ "open sqlite db ok." ); char *errorMsg; const char *createSql= "create table if not exists names (id integer primary key asc,name text)" ; //创建表 if (sqlite3_exec(database, createSql, NULL, NULL, &errorMsg)==SQLITE_OK) { NSLog(@ "create ok." ); } else { NSLog(@ "error: %s" ,errorMsg); sqlite3_free(errorMsg); } //插入数据 const char *insertSql= "insert into names (name) values(\"Luke\")" ; if (sqlite3_exec(database, insertSql, NULL, NULL, &errorMsg) == SQLITE_OK) { NSLog(@ "insert ok." ); } else { NSLog(@ "error: %s" ,errorMsg); sqlite3_free(errorMsg); } const char *selectSql= "select id,name from names" ; sqlite3_stmt *statement; if (sqlite3_prepare_v2(database, selectSql, -1, &statement, nil) == SQLITE_OK) { NSLog(@ "select ok." ); } while (sqlite3_step(statement)==SQLITE_ROW) { int _id=sqlite3_column_int(statement, 0); char *name=( char *)sqlite3_column_text(statement, 1); NSString *nameString = [NSString stringWithCString:name encoding:NSUTF8StringEncoding]; NSLog(@ "row>>id %i, name %@" ,_id,nameString); } sqlite3_finalize(statement); } sqlite3_close(database); |
你会发现这完全是C语言编程,和Objective-C的代码混在一起格格不入,也很不方便,所以便有人开发了开源的sqlite c接口的wrapper
- FMDB https://github.com/ccgus/fmdb
- EGODatabase https://github.com/enormego/egodatabase (部分代码来自FMDB,thread safe)
具体的使用方法,各自的文档都写的比较清楚。 FMDB不支持多线程同时使用同一个数据库连接进行操作,否则会有线程安全问题,有可能导致数据库文件损坏。EGODatabase则引入了多线程的支持,部分代码借鉴了FMDB,两者在使用上非常的相似。另EGODatabase提供了异步数据库操作的支持,将数据库操作封装成数据库请求(其继承于NSOperation),数据库请求创建好了,丢到一个OperationQueue中被异步的进行执行,当请求数据完成之后 ,相应的delegate方法会被调用,然后你可以在主线程更新显示了.
ios数据持久化(转)的更多相关文章
- iOS 数据持久化(扩展知识:模糊背景效果和密码保护功能)
本篇随笔除了介绍 iOS 数据持久化知识之外,还贯穿了以下内容: (1)自定义 TableView,结合 block 从 ViewController 中分离出 View,轻 ViewControll ...
- iOS开发笔记-swift实现iOS数据持久化之归档NSKeyedArchiver
IOS数据持久化的方式分为三种: 属性列表 (plist.NSUserDefaults) 归档 (NSKeyedArchiver) 数据库 (SQLite.Core Data.第三方类库等 归档(又名 ...
- IOS数据持久化之归档NSKeyedArchiver
IOS数据持久化的方式分为三种: 属性列表 (自定义的Property List .NSUserDefaults) 归档 (NSKeyedArchiver) 数据库 (SQLite.Core Data ...
- iOS -数据持久化方式-以真实项目讲解
前面已经讲解了SQLite,FMDB以及CoreData的基本操作和代码讲解(CoreData也在不断学习中,上篇博客也会不断更新中).本篇我们将讲述在实际开发中,所使用的iOS数据持久化的方式以及怎 ...
- iOS数据持久化方式及class_copyIvarList与class_copyPropertyList的区别
iOS数据持久化方式:plist文件(属性列表)preference(偏好设置)NSKeyedArchiver(归档)SQLite3CoreData沙盒:iOS程序默认情况下只能访问自己的程序目录,这 ...
- iOS数据持久化-OC
沙盒详解 1.IOS沙盒机制 IOS应用程序只能在为该改程序创建的文件系统中读取文件,不可以去其它地方访问,此区域被成为沙盒,所以所有的非代码文件都要保存在此,例如图像,图标,声音,映像,属性列表,文 ...
- iOS数据持久化
在iOS中,实现数据持久化一般分为4大种: 1.属性列表 2.对象归档 3.SQLite 4.Core Data 一.属性列表 NSUserDefaults类的使用和NSKeyedArchiver有很 ...
- iOS数据持久化存储:归档
在平时的iOS开发中,我们经常用到的数据持久化存储方式大概主要有:NSUserDefaults(plist),文件,数据库,归档..前三种比较经常用到,第四种归档我个人感觉用的还是比较少的,恰恰因为用 ...
- 转载 -- iOS数据持久化存储
作者:@翁呀伟呀 授权本站转载 概论 所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据.在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方 ...
- iOS 数据持久化(1):属性列表与对象归档
@import url(http://i.cnblogs.com/Load.ashx?type=style&file=SyntaxHighlighter.css); @import url(/ ...
随机推荐
- C++ 实验2:函数重载、函数模板、简单类的定义和实现
1.函数重载编程 编写重载函数add(),实现对int型,double型,Complex型数据的加法.在main()函数中定义不同类型数据,调用测试. #include <iostream> ...
- MAC OS 英语朗读功能
哈哈哈,太神奇了 在命令行中敲say + word ,系统能够自己讲word读出来. 如果是敲的是 say +中文, 就不知道再读什么啦 哈哈哈哈---- 此外,在对应网站选中内容后还可以右击,用sp ...
- 已使用 163 邮箱测试通过,且支持 SSL 连接。 发送邮件
示例:Jack 发送一封邮件给 Rose. public class SendMail { public static void main(String[] args) { b ...
- 30分钟带你了解Docker
最近一直在忙项目,不知不觉2个多月没有更新博客了.正好自学了几天docker就干脆总结一下,也顺带增加一篇<30分钟入门系列>.网上能够查到的对于docker的定义我就不再重复了,说说我自 ...
- 算法笔记--树的直径 && 树形dp && 虚树 && 树分治 && 树上差分 && 树链剖分
树的直径: 利用了树的直径的一个性质:距某个点最远的叶子节点一定是树的某一条直径的端点. 先从任意一顶点a出发,bfs找到离它最远的一个叶子顶点b,然后再从b出发bfs找到离b最远的顶点c,那么b和c ...
- 关于Dos命令中存在中文的解决方法
问题:当我们直接打开dos窗口并在里面写入中文时是没有问题的,但是当我们把这些命令放在bat文件中执行时就出问题了解决:1.首先可以通过pause命令来查看错误原因2.记事本默认是UTF-8格式的,而 ...
- java后台读取/解析 excel表格
需求描述 前台需要上传excel表格,提交到后台,后台解析并返回给前台,展示在前台页面上! 前台部分代码与界面 <th style="padding: 7px 1px;width:15 ...
- 雷林鹏分享:Ruby 类和对象
Ruby 类和对象 Ruby 是一种完美的面向对象编程语言.面向对象编程语言的特性包括: 数据封装 数据抽象 多态性 继承 这些特性将在 面向对象的 Ruby 中进行讨论. 一个面向对象的程序,涉及到 ...
- WPF如何给窗口Window增加阴影效果
https://blog.csdn.net/w_sx12553/article/details/45072861
- 20170814xlVBA PowerPoint分类插图加说明
Public Sub AddPictures() Dim ppApp As PowerPoint.Application Set ppApp = New PowerPoint.Application ...