iOSDataStorage

We know that there are many storage solutions in iOS programming. Today I’d like to introduce some of them.

Preferences

If you like to store some preferences and settings, which is normally very light-weight, I think the best way to do that is to use NSUserDefaults.

Sample Code

1
2
3
4
5
6
7
8
9
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setBool:YES forKey: @"isTurnedOn"];
[userDefaults setInteger:121 forKey:@"testInt"];
[userDefaults setObject:@"Fowafolo" forKey:@"God's Name"];
[userDefaults synchronize];
NSString *objectResult = [userDefaults objectForKey:@"God's Name"];
BOOL boolResult = [userDefaults boolForKey:@"isTurnedOn"];
NSInteger intResult = [userDefaults integerForKey:@"testInt"];
NSLog(@"%@, %d, %ld", objectResult, boolResult, intResult);

Explaination

In the code block above, we can conclude that the first thing we need to do is initialize an userDefault. And we can handle with many types of data like Bool, Integer, Object and etc using setForKey and ForKey to set and get data. It’s very easy to understand.

Moreover, if you like to synchronize immediately, please call [userDefaults synchronize];

Plist

First, you need to learn something about iOS document path.

Of course, there are some differences among different paths, you can google it.

Path

  1. mainBundle Path

    1
    2
    NSString *mainBundle = [[NSBundle mainBundle] bundlePath];
    NSLog(@"%@", mainBundle);
  2. document Path

    1
    2
    NSString *documentPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSLog(@"%@", documentPath);
  3. library Path

    1
    2
    NSString *libraryPath = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
    NSLog(@"%@", libraryPath);
  4. preference Path

    1
    2
    NSString *preferencePath = NSSearchPathForDirectoriesInDomains(NSPreferencePanesDirectory, NSUserDomainMask, YES).firstObject;
    NSLog(@"%@", preferencePath);
  5. temp Path

    1
    2
    NSString *tempPath = NSTemporaryDirectory();
    NSLog(@"%@", tempPath);

Sample Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
NSString *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
NSString *fileName = [path stringByAppendingPathComponent:@"123.plist"];

NSDictionary *dict = @{@"first": @"Mike", @"second": @"Bob"};

NSArray *array = @[@"123", @"456", @"789"];
[array writeToFile:fileName atomically:YES];
[dict writeToFile:fileName atomically:YES];


NSArray *resultArr = [NSArray arrayWithContentsOfFile:fileName];
NSLog(@"%@", resultArr);

NSDictionary *resultDic = [NSDictionary dictionaryWithContentsOfFile:fileName];
NSLog(@"%@", resultDic);

Explaination

In the code block above, we can conclude that we need to firstly give a specified name of the file and then use that file to call [NSArray arrayWithContentsOfFile:fileName] and [dict writeToFile:fileName atomically:YES](dict is an initialization).

NSKeyedArchiver

Sample Code

Firstly, I have a class named Person like:

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
//Person.h
@interface Person : NSObject <NSCoding>

@property (strong, nonatomic) UIImage *avator;
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger age;

@end

//Person.m
@implementation Person

/**
* 解档
*/

- (id)initWithCoder:(NSCoder *)aDecoder {
if ([super init]) {
self.avator = [aDecoder decodeObjectForKey:@"avator"];
self.name = [aDecoder decodeObjectForKey:@"name"];
self.age = [aDecoder decodeIntegerForKey:@"age"];
}

return self;
}

/**
* 归档
*/

- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.avator forKey:@"avator"];
[aCoder encodeObject:self.name forKey:@"name"];
[aCoder encodeInteger:self.age forKey:@"age"];
}

@end

Then we can use this class to make sense of NSKeyedArchiver.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	//write
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingString:@"person.data"];
Person *person = [[Person alloc]init];
person.avator = [UIImage imageNamed:@"avator"];
person.name = @"Fowafolo";
person.age = 22;

[NSKeyedArchiver archiveRootObject:person toFile:file];

//read
NSString *file = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingString:@"person.data"];

Person *person = [NSKeyedUnarchiver unarchiveObjectWithFile:file];
if (person) {
NSLog(@"%@, %@, %ld", person.name, person.avator, (long)person.age);
}

Explaination

If you like to use NSKeyedArchiver to read or write, you must have a model which follows the protocal <NSCoding> and implement the initWithCoder and encodeWithCoder functions.

Then, an initialization of that class could be written or read using [NSKeyedArchiver archiveRootObject:person toFile:file] and [NSKeyedUnarchiver unarchiveObjectWithFile:file].

SQLite3

Prerequisite

To use SQLite, you need firstly add the framework libsqlite3.tbd into your project’s Link Binary With Libraries in Build Phases.

Sample Code

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
static sqlite3 *_db;

- (void)initSQLiteDB {
//1. set the file name
NSString *fileName = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject stringByAppendingString:@"person.db"];

//2. open the database
const char *cfilename = fileName.UTF8String;
// 1.打开数据库(如果数据库文件不存在,sqlite3_open函数会自动创建数据库文件)
int result = sqlite3_open(cfilename, &_db);
if (result == SQLITE_OK) { // 打开成功
NSLog(@"成功打开数据库");

// 2.创表
const char *sql = "CREATE TABLE IF NOT EXISTS t_person (id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL, age integer NOT NULL);";
char *erroMsg = NULL;
result = sqlite3_exec(_db, sql, NULL, NULL, &erroMsg);
if (result == SQLITE_OK) {
NSLog(@"成功创表");
} else {
NSLog(@"创表失败--%s", erroMsg);
}
} else {
NSLog(@"打开数据库失败");
}
}

- (void)SQLiteInsert {
Person * person = [[Person alloc]init];
person.name = @"Fowafolo";
person.age = 20;
person.avator = [UIImage imageNamed:@"avator"];

//1. set sql
NSString *sql = [NSString stringWithFormat:@"INSERT INTO t_person(name, age) VALUES ('%@', %ld)", person.name, (long)person.age];

//2. execute sql
char *errorMsg = NULL;
sqlite3_exec(_db, sql.UTF8String, NULL, NULL, &errorMsg);
if (errorMsg) {
NSLog(@"插入数据失败--%s", errorMsg);
} else {
NSLog(@"成功插入数据");
}
}

- (void)readFromSQLiteWithCondition: (NSString*)condition {
NSMutableArray *persons = nil;

NSString *sql = [NSString stringWithFormat:@"SELECT id, name, age FROM t_person WHERE name like '%%%@%%' ORDER BY age ASC;", condition];
sqlite3_stmt *stmt = NULL;
if (sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL) == SQLITE_OK) { // SQL语句没有问题
NSLog(@"查询语句没有问题");

// 创建数组
persons = [NSMutableArray array];

// 每调一次sqlite3_step函数,stmt就会指向下一条记录
while (sqlite3_step(stmt) == SQLITE_ROW) { // 找到一条记录
// 取出数据

// 取出第0列字段的值(int类型的值)
int ID = sqlite3_column_int(stmt, 0);

// 取出第1列字段的值(tex类型的值)
const unsigned char *name = sqlite3_column_text(stmt, 1);

// 取出第2列字段的值(int类型的值)
int age = sqlite3_column_int(stmt, 2);

// 创建JPPerson对象
Person *p = [[Person alloc] init];

p.name = [NSString stringWithUTF8String:(const char *)name];
p.age = age;
[persons addObject:p];
}
NSLog(@"%@", persons);
} else {
NSLog(@"查询语句有问题");
}
}

Explaination

Firstly you need a sqlite3 item to use the database.

Core Data

Core Data, in some degree, is the most complicated but important solution on iOS platform. It provides us the ORM function and change an OC item into Data or something on the contrary.

Prerequisite

To use Core Data, you must create Core Data Data Model by pressing command + n and selecting Data Model.

Next, you need to edit the .ecdatamodeld file to add you entry and models.

Then, add mapping files.(Editor-Create NSManagedObject Subclass)

Now you are able to use the core data model.

Sample Code

I have such model and mapping files:

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

// Car.h
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

NS_ASSUME_NONNULL_BEGIN

@interface Car : NSManagedObject

// Insert code here to declare functionality of your managed object subclass

@end

NS_ASSUME_NONNULL_END

#import "Car+CoreDataProperties.h"




// Car.m
@implementation Car

// Insert code here to add functionality to your managed object subclass

@end





// Car+CoreDataProperties.h
#import "Car.h"

NS_ASSUME_NONNULL_BEGIN

@interface Car (CoreDataProperties)

@property (nullable, nonatomic, retain) NSNumber *id;
@property (nullable, nonatomic, retain) NSString *name;
@property (nullable, nonatomic, retain) NSNumber *price;
@property (nullable, nonatomic, retain) NSDate *createdDate;

@end

NS_ASSUME_NONNULL_END




// Car+CoreDataProperties.m
#import "Car+CoreDataProperties.h"

@implementation Car (CoreDataProperties)

@dynamic id;
@dynamic name;
@dynamic price;
@dynamic createdDate;

@end

And here is the sample source code:

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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
- (void)initCoreData {
//1.初始化上下文
NSManagedObjectContext *context = [[NSManagedObjectContext alloc] init];

//2.添加持久化存储
//2.1模型文件 描述表结构的文件 也就是(Company.xcdatamodeld)这个文件
#warning 补充 ,如bundles传nil 会从主bundle加载所有的模型文件,把里面表结构都放在一个数据库文件
NSManagedObjectModel *carModel = [NSManagedObjectModel mergedModelFromBundles:nil];

//2.2持久化存储调用器
NSPersistentStoreCoordinator *store = [[NSPersistentStoreCoordinator alloc]initWithManagedObjectModel:carModel];

NSString *doc = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];

//数据库的完整文件路径
NSString *sqlitePath = [doc stringByAppendingString:@"car.sqlite"];

//保存一个sqite文件的话,必要知道表结构和sqlite的文件路径
//2.3 告诉coredate数据存储在一个sqlite文件
NSError *error = nil;
[store addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:sqlitePath] options:nil error:&error];

if (error) {
NSLog(@"%@", error);
}

context.persistentStoreCoordinator = store;
_context = context;
}

- (void)addCar {
Car *car = [NSEntityDescription insertNewObjectForEntityForName:@"Car" inManagedObjectContext:_context];
car.name = @"奔驰";
car.price = @(200000);
car.createdDate = [NSDate date];

//保存
NSError *error = nil;
[_context save:&error];
if (error) {
NSLog(@"%@", error);
}else {
NSLog(@"成功保存一条数据");
}
}

- (void)findCar {
//查找数据
//1.创建请求对象,指定要查找的表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Car"];

//2.时间排序
NSSortDescriptor *dateSort = [NSSortDescriptor sortDescriptorWithKey:@"createdDate" ascending:YES]; //升序
request.sortDescriptors = @[dateSort];

//3.过滤条件
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name=%@", @"宝马"];
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name LIKE[c] '*宝*'"]; //注:[c]不区分大小写 , [d]不区分发音符号即没有重音符号 , [cd]既不区分大小写,也不区分发音符号。
// NSString *attributeValue = @"宝";
// NSPredicate *pre = [NSPredicate predicateWithFormat:@"name CONTAINS %@", attributeValue];
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name like[c] %@", @"*宝*"];


request.predicate = pre;

//分页查询
// request.fetchOffset = 5;
// request.fetchLimit = 5;

//4.执行请求
NSError *error = nil;
NSArray *allCars = [_context executeFetchRequest:request error:&error];
if (error) {
NSLog(@"%@",error);
}

for (Car *car in allCars) {
NSLog(@"%@ , %@, %@", car.name, car.price, car.createdDate);
}
}

- (void)updateCar {
//查找数据
//1.创建请求对象,指定要查找的表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Car"];

NSString *attributeValue = @"宝";
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name CONTAINS %@", attributeValue];
request.predicate = pre;

//2.执行
NSArray *allBMW = [_context executeFetchRequest:request error:nil];
NSLog(@"%@", allBMW);
for (Car *car in allBMW) {
car.name = @"大宝马";
}

//3.保存
[_context save:nil];
}

- (void)deleteCar {
//查找数据
//1.创建请求对象,指定要查找的表
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Car"];
NSString *attributeValue = @"奔";
NSPredicate *pre = [NSPredicate predicateWithFormat:@"name CONTAINS %@", attributeValue];
request.predicate = pre;

//2.执行
NSArray *allBenz = [_context executeFetchRequest:request error:nil];
for (Car *car in allBenz) {
[_context deleteObject:car];
}
}

Explaination

Remember: one db, one context.

文章目录
  1. 1. Preferences
    1. 1.1. Sample Code
    2. 1.2. Explaination
  2. 2. Plist
    1. 2.1. Path
    2. 2.2. Sample Code
      1. 2.2.1. Explaination
  3. 3. NSKeyedArchiver
    1. 3.1. Sample Code
    2. 3.2. Explaination
  4. 4. SQLite3
    1. 4.1. Prerequisite
    2. 4.2. Sample Code
    3. 4.3. Explaination
  5. 5. Core Data
    1. 5.1. Prerequisite
    2. 5.2. Sample Code
    3. 5.3. Explaination
,