转:相对复杂的App仅靠内存的数据肯定无法满足,数据写磁盘作持久化存储是几乎每个客户端软件都需要做的。简单如“是否第一次打开”的BOOL值,大 到游戏的进度和状态等数据,都需要进行本地持久化存储。这些数据的存储本质上就是写磁盘存文件,原始一点可以用iOS本身支持有 NSFileManager这样的API,或者干脆C语言fwrite/fread,Cocoa Touch本身也提供了一些存储方式,如NSUserDefaults,CoreData等。总的来说,iOS平台数据持久存储方法大致如下所列:

  • Raw File APIs
  • UserDefault
  • NSCoding => NSKeyedArchived
  • Plist File
  • SQLite(使用C语言)
  • CoreData

一、Raw File APIs

ObjC是C的一个超集,所以最笨的方法我们可以直接用C作文件读写来实现数据存储:
1. 写入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// File path
const char * pFilePath = [_path cStringUsingEncoding:NSUTF8StringEncoding];
 
// Create a new file
FILE * pFile = fopen(pFilePath, "w+");
 
if (pFile == NULL) {
    NSLog(@"Open File ERROR!");
    return;
}
 
const char * content = [_textField.text cStringUsingEncoding:NSUTF8StringEncoding];
fwrite(content, sizeof(content), 1, pFile);
fclose(pFile);

2. 读取文件

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
// File path
const char * pFilePath = [_path cStringUsingEncoding:NSUTF8StringEncoding];
 
// Create a new file
FILE * pFile = fopen(pFilePath, "r+");
 
if (pFile == NULL) {
    NSLog(@"Open File ERROR!");
    return;
}
 
int fileSize = ftell(pFile);
NSLog(@"fileSize: %d", fileSize);
 
char * content[20];
 
fread(content, 20, 20, pFile);
 
NSString * aStr = [NSString stringWithFormat:@"%s", &content];
 
if (aStr != nil && ![aStr isEqualToString:@""]) {
    _textField.text = aStr;
}
 
fclose(pFile);

二、NSUserDefaults

但是既然在iOS平台作开发,我们当然不至于要到使用C的原生文件接口这种地步,下面就介绍几种iOS开发中常用的数据本地存储方式。使用起来最简
单的大概就是Cocoa提供的NSUserDefaults了,Cocoa会为每个app自动创建一个数据库,用来存储App本身的偏好设置,如:开关音
效,音量调整之类的少量信息。NSUserDefaults是一个单例,生命后期由App掌管,使用时用 [NSUserDefaults
standardUserDefaults]
接口获取单例对象。NSUserDefaults本质上是以Key-Value形式存成plist文件,放在App的
Library/Preferences目录下,对于已越狱的机器来说,这个文件是不安全的,所以**千万不要用NSUserDefaults来存储密码之类的敏感信息**,用户名密码应该使用**KeyChains**来存储。

1.写入数据

1
2
3
4
5
6
7
// 获取一个NSUserDefaults对象
NSUserDefaults * aUserDefaults = [NSUserDefaults standardUserDefaults];
// 插入一个key-value值
[aUserDefaults setObject:_textField.text forKey:@"Text"];
 
// 这里是为了把设置及时写入文件,防止由于崩溃等情况App内存信息丢失
[aUserDefaults synchronize];

2.读取数据

1
2
3
NSUserDefaults * aUserDefaults = [NSUserDefaults standardUserDefaults];
            // 获取一个key-value值
NSString * aStr = [aUserDefaults objectForKey:@"Text"];

使用起来很简单吧,它的接口跟 NSMutableDictionary 一样,看它的头文件,事实上在内存里面也是用dictionary来存的。写数据的时候记得用 synchronize 方法写入文件,否则 crash了数据就丢了。

三、Plist

上一节提到NSUserDefaults事实上是存成Plist文件,只是Apple帮我们封装好了读写方法而已。NSUserDefaults的
缺陷是存储只能是Library/Preferences/<Application BundleIdentifier>.plist
这个文件,如果我们要自己写一个Plist文件呢?
使用NSFileManger可以很容易办到。事实上Plist文件是XML格式的,如果你存储的数据是Plist文件支持的类型,直接用
NSFileManager的writToFile接口就可以写入一个plist文件了。 ### Plist文件支持的数据格式有:
NSString, NSNumber, Boolean, NSDate, NSData, NSArray, 和NSDictionary.
其中,Boolean格式事实上以[NSNumber
numberOfBool:YES/NO];这样的形式表示。NSNumber支持float和int两种格式。

读写Plist文件

1. 首先创建plist文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      // 文件的路径
      NSString * _path = [[NSTemporaryDirectory() stringByAppendingString:@"save.plist"] retain];
      // 获取一个NSFileManger
NSFileManager * aFileManager = [NSFileManager defaultManager];
if (![aFileManager fileExistsAtPath:_path]){
      // 文件不存在,创建之
      NSMutableDictionary * aDefaultDict = [[NSMutableDictionary alloc] init];
                              // 插入一个值,此时数据仍存在内存里
      [aDefaultDict setObject:@"test" forKey:@"TestText"];
 
                              // 使用NSMutableDictionary的写文件接口自动创建一个Plist文件
      if (![aDefaultDict writeToFile:_path atomically:YES]) {
          NSLog(@"OMG!!!");
      }
 
      [aDefaultDict release];
  }

2. 写入文件

1
2
3
4
5
6
        // 写入数据
NSMutableDictionary * aDataDict = [NSMutableDictionary dictionaryWithContentsOfFile:_path];
[aDataDict setObject:_textField.text forKey:@"TestText"];
    if (![aDataDict writeToFile:_path atomically:YES]) {
        NSLog(@"OMG!!!");
    }

3. 读取文件

1
2
3
4
5
     NSMutableDictionary * aDataDict = [NSMutableDictionary dictionaryWithContentsOfFile:_path];
NSString * aStr = [aDataDict objectForKey:@"TestText"];
if (aStr != nil && aStr.length > 0) {
    _textField.text = aStr;
}

四、NSCoding + NSKeyedArchiver

上面介绍的几种方法中,直接用C语言的接口显然是最不方便的,拿出来的数据还得自己进行类型转换。NSUserDefaults和Plist文件支
持常用数据类型,但是不支持自定义的数据对象,好像Cocoa提供了NSCoding和NSKeyArchiver两个工具类,可以把我们自定义的对象编
码成二进制数据流,然后存进文件里面,下面的Sample为了简单我直接用cocoa的接口写成plist文件。
如果要使用这种方式进行存储,首先自定义的对象要继承NSCoding的delegate。

1
2
3
4
5
6
7
8
9
10
11
12
        @interface WSNSCodingData : NSObject<NSCoding>
 
然后继承两个必须实现的方法encodeWithCoder:和initWithCoder:
 
        - (void)encodeWithCoder:(NSCoder *)enoder {
            [enoder encodeObject:data forKey:kDATA_KEY];
        }
 
        - (id)initWithCoder:(NSCoder *)decoder {
            data = [[decoder decodeObjectForKey:kDATA_KEY] copy];
                        return [self init];
        }

这里data是我自己定义的WSNSCodingData这个数据对象的成员变量,由于数据在使用过程中需要持续保存在内存中,所以类型为
copy,或者retain也可以,记得在dealloc函数里面要realease。这样,我们就定义了一个可以使用NSCoding进行编码的数据对
象。

保存数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (void)saveData {
    if (aData == nil) {
        aData = [[WSNSCodingData alloc] init];
    }
 
    aData.data = _textField.text;
 
    NSLog(@"save data...%@", aData.data);
                // 这里init的NSMutableData是临时用来存储数据的
    NSMutableData   * data = [[NSMutableData alloc] init];
                // 这个NSKeyedArchiver则是进行编码用的
    NSKeyedArchiver * archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:aData forKey:DATA_KEY];
    [archiver finishEncoding];
                // 编码完成后的NSData,使用其写文件接口写入文件存起来
    [data writeToFile:_path atomically:YES];
    [archiver release];
    [data release];
 
    NSLog(@"save data: %@", aData.data);
}

读取数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)loadData {
    NSLog(@"load file: %@", _path);
    NSData * codedData = [[NSData alloc] initWithContentsOfFile:_path];
    if (codedData == nil) return;
 
                // NSKeyedUnarchiver用来解码
    NSKeyedUnarchiver * unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:codedData];
                // 解码后的数据被存在一个WSNSCodingData数据对象里面
    aData = [[unarchiver decodeObjectForKey:DATA_KEY] retain];
    [unarchiver finishDecoding];
    [unarchiver release];
 
    [codedData release];
 
    if (aData.data != nil) {
        _textField.text = aData.data;
    }
}

所以其实使用NSCoding和NSKeyedArchiver事实上也是写plist文件,只不过对复杂对象进行了编码使得plist支持更多数据类型而已。

五、 SQLite

如果App涉及到的数据多且杂,还涉及关系查询,那么毋庸置疑要使用到数据库了。Cocoa本身提供了CoreData这样比较重的数据库框架,下
一节会讲到,这一节讲一个轻量级的数据库——SQLite。
SQLite是C写的的,做iOS开发只需要在工程里面加入需要的框架和头文件就可以用了,只是我们得用C语言来进行SQLite操作。
关于SQLite的使用参考了这篇文章:http://mobile.51cto.com/iphone-288898.htm但是稍微有点不一样。

1. 在编写SQLite代码之前,我们需要引入SQLite3头文件:

1
#import <sqlite3.h>

2. 然后给工程加入 libsqlite3.0.dylib 框架。 3. 然后就可以开始使用了。首先是打开数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)openDB {
    NSArray * documentsPaths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory
                                                                , NSUserDomainMask
                                                                , YES);
    NSString * databaseFilePath = [[documentsPaths objectAtIndex:0] stringByAppendingPathComponent:@"mydb"];
 
    // SQLite存的最终还是文件,如果没有该文件则会创建一个
    if (sqlite3_open([databaseFilePath UTF8String], &_db) == SQLITE_OK) {
        NSLog(@"Successfully open database.");
        // 如果没有表则创建一个表
        [self creatTable];
    }
}

3.关闭数据库,在dealloc函数里面调用:

1
2
3
- (void)closeDB {
    sqlite3_close(_db);
}

4.创建一个表:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)creatTable {
    char * errorMsg;
    const char * createSql="create table if not exists datas (id integer primary key autoincrement,name text)";
 
    if (sqlite3_exec(_db, createSql, NULL, NULL, &errorMsg) == SQLITE_OK) {
        NSLog(@"Successfully create data table.");
    }
    else {
        NSLog(@"Error: %s",errorMsg);
        sqlite3_free(errorMsg);
    }
}

5. 写入数据库

1
2
3
4
5
6
7
8
9
10
- (void)saveData {
    char * errorMsg;
        // 向 datas 表中插入 name = _textFiled.text 的数据
    NSString * insertSQL = [NSString stringWithFormat:@"insert into datas (name) values('%@')", _textField.text];
 
        // 执行该 SQL 语句
    if (sqlite3_exec(_db, [insertSQL cStringUsingEncoding:NSUTF8StringEncoding], NULL, NULL, &errorMsg)==SQLITE_OK) {
        NSLog(@"insert ok.");
    }
}

6. 读取数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void)loadData {
    [self openDB];
 
    const char * selectSql="select id,name from datas";
    sqlite3_stmt * statement;
    if (sqlite3_prepare_v2(_db, selectSql, -1, &statement, nil)==SQLITE_OK) {
        NSLog(@"select ok.");
    }
 
    while (sqlite3_step(statement) == SQLITE_ROW) {
        int _id = sqlite3_column_int(statement, 0);
        NSString * name = [[NSString alloc] initWithCString:(char *)sqlite3_column_text(statement, 1) encoding:NSUTF8StringEncoding];
        NSLog(@"row>>id %i, name %@",_id,name);
 
        _textField.text = name;
    }
 
    sqlite3_finalize(statement);
}

五、CoreData

大型数据存储和管理。 XCode自带有图形化工具,可以自动生成数据类型的代码。 最终存储格式不一定存成SQLite,可以是XML等形式。

IOS - 本地数据持久化的更多相关文章

  1. iOS开发技术分享(1)— iOS本地数据存储

    iOS开发技术分享(1)— iOS本地数据存储 前言: 我本是一名asp.net程序员,后来加入了iOS游戏开发队伍,到现在也有一年多的时间了.这一年来,每天都干到2.3点钟才睡觉,不为别的,只为了学 ...

  2. iOS开发——数据持久化&amp;本地数据的存储(使用NSCoder将对象保存到.plist文件)

    本地数据的存储(使用NSCoder将对象保存到.plist文件)   下面通过一个例子将联系人数据保存到沙盒的“documents”目录中.(联系人是一个数组集合,内部为自定义对象).   功能如下: ...

  3. iOS开发——数据持久化&amp;使用NSUserDefaults来进行本地数据存储

    使用NSUserDefaults来进行本地数据存储   NSUserDefaults适合存储轻量级的本地客户端数据,比如记住密码功能,要保存一个系统的用户名.密码.使用NSUserDefaults是首 ...

  4. iOS - OC 数据持久化

    1.Sandbox 沙箱 iOS 为每个应用提供了独立的文件空间,一个应用只能直接访问为本应用分配的文件目录,不可以访问其他目录,每个应用自己独立的访问空间被称为该应用的沙盒.也就是说,一个应用与文件 ...

  5. iOS - Swift 数据持久化

    1.Sandbox 沙箱 iOS 为每个应用提供了独立的文件空间,一个应用只能直接访问为本应用分配的文件目录,不可以访问其他目录,每个应用自己独立的访问空间被称为该应用的沙盒.也就是说,一个应用与文件 ...

  6. QF——iOS中数据持久化的几种方式

    数据持久化的几种方式: 一.属性列表文件: .plist文件是种XML文件.数组,字典都可以和它互相转换.数组和字典可以写入本地变成plist文件.也可以读取本地plist文件,生成数组或字典. 读取 ...

  7. iOS中 数据持久化 UI高级_17

    数据持久化的本质就是把数据由内写到本地(硬盘中),在iOS指将数据写到沙盒文件夹下: 沙盒机制:指的就是采用沙盒文件夹的形式管理应用程序的本地文件,而且沙盒文件夹的名字是随机分配的,采用十六进制方法命 ...

  8. iOS之数据持久化方案

    概论 所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据.在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案: plist文件(属性列表) ...

  9. iOS的数据持久化

    所谓的持久化,就是将数据保存到硬盘中,使得在应用程序或机器重启后可以继续访问之前保存的数据.在iOS开发中,有很多数据持久化的方案,接下来我将尝试着介绍一下5种方案: plist文件(属性列表) pr ...

随机推荐

  1. 简单说说call 与apply

    Function.call() 将函数作为对象的方法调用,例如:function.call(thisobj,args,........); thisobj  调用function的对象.在函数主体中, ...

  2. 面向对象的JavaScript

    多态 多态背后的思想是将"做什么"和"谁去做,怎样去做"分离开来,也就是将"不变的事物"与"可变的事物"分离开来. 其最 ...

  3. Node.prototype.contains

    document.documentElement.contains(document.body) // true document.documentElement.compareDocumentPos ...

  4. Linux分析日志获取最多访问的前10个IP

    原文地址:http://xuqq999.blog.51cto.com/3357083/774714 apache日志分析可以获得很多有用的信息,现在来试试最基本的,获取最多访问的前10个IP地址及访问 ...

  5. 创业6&amp;7

    周末两天泡咖啡店. 起不来,只好下午去. 周六5点到9点. 周日3点到12点. 1)整理直播课程讲义.完成50%. 2)修改GMTC演讲稿.完成. 招行的单子还是拒了,目前还没准备好高可用的App服务 ...

  6. 三维高斯模型 opencv实现

    OnProbabilityModel() { int i; for(int x=0;x<workImg->height;x++) { for(int y=0;y<workImg-&g ...

  7. C++多线程编程入门之经典实例

    多线程在编程中有相当重要的地位,我们在实际开发时或者找工作面试时总能遇到多线程的问题,对多线程的理解程度从一个侧面反映了程序员的编程水平. 其实C++语言本身并没有提供多线程机制,但Windows系统 ...

  8. Asp.Net Web API 2第十一课——在Web API中使用Dependency Resolver

    前言 阅读本文之前,您也可以到Asp.Net Web API 2 系列导航进行查看 http://www.cnblogs.com/aehyok/p/3446289.html 本文主要来介绍在Asp.N ...

  9. LeetCode130:Surrounded Regions

    题目: Given a 2D board containing 'X' and 'O', capture all regions surrounded by 'X'. A region is capt ...

  10. java编程的78条黄金法则

    创建和销毁对象 1.考虑用静态工厂方法(返回类的实例的静态方法)代替构造器2.遇到多个构造器参数时要考虑用构造器3.用私有构造器或者枚举类型强化Singleton属性4.通过私有构造器强化不可实例化的 ...