iOS中四种最常用的将数据持久存储在iOS文件系统的机制

前三种机制的相同点都是需要找到沙盒里面的Documents的目录路径,附加自己相应的文件名字符串来生成需要的完整路径,再往里面创建、读取、写入文件

而第四种则是与委托有关,下面给出代码(有修改过的部分)。

这里做的示例是用四个TextField来显示内容,如图

iOS开发 数据迁移掘金 ios开发数据存储_sqlite

一、属性列表(.plist)

iOS开发 数据迁移掘金 ios开发数据存储_iOS开发 数据迁移掘金_02

iOS开发 数据迁移掘金 ios开发数据存储_数据库_03

//
//  ViewController.m
//  Persistence
//
//  Created by Kim Topley on 7/31/14.
//  Copyright (c) 2014 Apress. All rights reserved.
//

#import "ViewController.h"

@interface ViewController ()

@property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *lineFields;

@end

@implementation ViewController
            
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    NSString *filePath = [self dataFilePath];
    if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
        NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
        for (int i = 0; i < 4; i++) {
            UITextField *theField = self.lineFields[i];
            theField.text = array[i];
        }
    }
    
    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter]
        addObserver:self
        selector:@selector(applicationWillResignActive:)
        name:UIApplicationWillResignActiveNotification
        object:app];
}

- (void)applicationWillResignActive:(NSNotification *)notification {
    NSString *filePath = [self dataFilePath];
    NSArray *array = [self.lineFields valueForKey:@"text"];
    [array writeToFile:filePath atomically:YES];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (NSString *)dataFilePath
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(
               NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    return [documentsDirectory stringByAppendingPathComponent:@"data.plist"];
}
@end

ViewController.m

 

二、对象归档

1、遵循NSCoding协议

2、遵循NSCopying协议

3、对数据对象进行归档和取消归档

iOS开发 数据迁移掘金 ios开发数据存储_iOS开发 数据迁移掘金_02

iOS开发 数据迁移掘金 ios开发数据存储_数据库_03

//
//  FourLines.h
//  Persistence
//
//  Created by  Jierism on 16/7/27.
//  Copyright © 2016年  Jierism. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface FourLines : NSObject<NSCoding,NSCopying>

@property(copy,nonatomic)NSArray *lines;

@end

FourLines.h

iOS开发 数据迁移掘金 ios开发数据存储_iOS开发 数据迁移掘金_02

iOS开发 数据迁移掘金 ios开发数据存储_数据库_03

//
//  FourLines.m
//  Persistence
//
//  Created by  Jierism on 16/7/27.
//  Copyright © 2016年  Jierism. All rights reserved.
//

#import "FourLines.h"

static NSString * const kLinesKey = @"kLinesKey";

@implementation FourLines

#pragma mark - Coding
// 回复我们之前归档的对象
- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if (self) {
        self.lines = [aDecoder decodeObjectForKey:kLinesKey];
    }
    return self;
}

// 将所有实例变成编码成aCoder
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.lines forKey:kLinesKey];
}

#pragma mark - Copying

- (id)copyWithZone:(NSZone *)zone
{
    // 新建一个新的FourLines对象,并将字符串数组复制进去
    FourLines *copy = [[[self class] allocWithZone:zone] init];
    NSMutableArray *linesCopy = [NSMutableArray array];
    for (  id line in self.lines) {
        [linesCopy addObject:[line copyWithZone:zone]];
    }
    copy.lines = linesCopy;
    return copy;
}

@end

FourLines.m

iOS开发 数据迁移掘金 ios开发数据存储_iOS开发 数据迁移掘金_02

iOS开发 数据迁移掘金 ios开发数据存储_数据库_03

//
//  ViewController.m
//  Persistence
//
//  Created by  Jierism on 16/7/27.
//  Copyright © 2016年  Jierism. All rights reserved.
//

#import "ViewController.h"
#import "FourLines.h"

static NSString * const kRootKey = @"kRootKey";
@interface ViewController ()

@property(strong,nonatomic)IBOutletCollection(UITextField) NSArray *lineFields;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    

    NSString *filePath = [self dataFilePath];
    if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
        
    // 从归档中重组对象,对数据进行解码
        NSData *data = [[NSMutableData alloc] initWithContentsOfFile:filePath];
        NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
        FourLines *foueLines = [unarchiver decodeObjectForKey:kRootKey];
        [unarchiver finishDecoding];
        
        for (int i = 0; i < 4; i++) {
            UITextField *theFiled = self.lineFields[i];
            theFiled.text = foueLines.lines[i];
        }

    
    }
    
    // 订阅,获取通知
    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(applicationWillResignActive:)
                                                 name:UIApplicationWillResignActiveNotification
                                               object:app];
}

// 接收通知,告诉应用在终止运行或者进入后台之前保存数据
- (void) applicationWillResignActive:(NSNotification *)notification
{
    NSString *filePath = [self dataFilePath];
    
    // 将对象归档到data实例中
    FourLines *fourLines = [[FourLines alloc] init];
    fourLines.lines = [self.lineFields valueForKey:@"text"];
    NSMutableData *data = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
    [archiver encodeObject:fourLines forKey:kRootKey];
    [archiver finishEncoding];
    [data writeToFile:filePath atomically:YES];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

// 获取数据文件的完整路径(两步)
- (NSString *)dataFilePath
{
    //1.查找Documents目录
    NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [path objectAtIndex:0];
    //2.在后面附加数据文件的文件名
    return [documentsDirectory stringByAppendingPathComponent:@"data.archive"];
}



@end

ViewController.m

 

三、iOS的嵌入式关系数据库SQLite3

链接到数据库

在项目导航面板中顶部选中项目名称,按下图操作即可

iOS开发 数据迁移掘金 ios开发数据存储_数据库_10

iOS开发 数据迁移掘金 ios开发数据存储_sqlite_11

1、创建或打开数据库

2、绑定变量

iOS开发 数据迁移掘金 ios开发数据存储_iOS开发 数据迁移掘金_02

iOS开发 数据迁移掘金 ios开发数据存储_数据库_03

//
//  ViewController.m
//  SQLite Persistence
//
//  Created by  Jierism on 16/7/27.
//  Copyright © 2016年  Jierism. All rights reserved.
//

#import "ViewController.h"
#import <sqlite3.h>

@interface ViewController ()

@property (strong,nonatomic) IBOutletCollection(UITextField) NSArray *lineFields;

@end

@implementation ViewController

- (NSString *)dataFilePath
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    return [documentsDirectory stringByAppendingString:@"data.sqlite"];
}

// 数据库在应用打开时才打开用于加载数据,加载完毕后会关闭
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 打开数据库,如果在打开时遇到问题则关闭,并抛出断言错误
    sqlite3 *database;
    if (sqlite3_open([[self dataFilePath] UTF8String],&database) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert(0, @"Failed to open database");
    }
    
    // 建立一个表来保存我们的数据,用IF NOT可以防止数据库覆盖现有数据:如果已有相同名的表则此命令不执行操作
    NSString *createSQL = @"CREATE TABLE IF NOT EXISTS FIELDS "
    "(ROW INTEGER PRIMAY KEY,FIELD_DATA TEXT);";
    char *errorMsg;
    if (sqlite3_exec (database,[createSQL UTF8String],NULL,NULL,&errorMsg) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert(0, @"Error creating table:%s",errorMsg);
    }
    
    // 数据库中没一行包含一个整型(从0计数)和一个字符串(对应行的内容),加载内容
    NSString *query = @"SELECT ROW,FIELD_DATA FROM FIELDS ORDER BY ROW";
    sqlite3_stmt *statement;
    if (sqlite3_prepare_v2(database,[query UTF8String],-1,&statement,nil) == SQLITE_OK) {
        // 遍历返回的每一行
        while (sqlite3_step(statement) == SQLITE_ROW) {
            // 抓取行号存储在一个int变量中,抓取字段数据保存在char类型的字符串中
            int row = sqlite3_column_int(statement,0);
            char *rowData = (char *)sqlite3_column_text(statement,1);
            // 利用从数据库中获取的值设置相应的字段
            NSString *fieldValue = [[NSString alloc] initWithUTF8String:rowData];
            UITextField *field = self.lineFields[row];
            field.text = fieldValue;
        }
        // 关闭数据库连接
        sqlite3_finalize(statement);
    }
    sqlite3_close(database);
    
    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
    
    
}

- (void)applicationWillResignActive:(NSNotification *)notification
{
    // 打开数据库
    sqlite3 *database;
    if (sqlite3_open([[self dataFilePath] UTF8String], &database) != SQLITE_OK) {
        sqlite3_close(database);
        NSAssert(0, @"Failed to open database");
    }
    
    // 遍历数据库每一行,更新里面的数据
    for (int i = 0; i < 4; i++) {
        UITextField *field = self.lineFields[i];
        char *update = "INSERT OR REPLACE INTO FIELDS (ROW,FIELD_DATA)"
                        "VALUES(?,?)";
        char *errorMsg = NULL;
        
        // 声明一个指向语句的指针,然后为语句添加绑定变量,并将值绑定到两个绑定变量
        sqlite3_stmt *stmt;
        if (sqlite3_prepare_v2(database, update, -1, &stmt, nil) == SQLITE_OK) {
            sqlite3_bind_int(stmt,1,i);
            sqlite3_bind_text(stmt,2,[field.text UTF8String],-1,NULL);
        }
        // 调用sqlite3_step来执行更新,检查并确定其运行正常,然后完成语句,结束循环
        if (sqlite3_step(stmt) != SQLITE_DONE) {
            NSAssert(0, @"Error updating table:%s",errorMsg);
        }
        sqlite3_finalize(stmt);
    }
    // 关闭数据库
    sqlite3_close(database);
}



@end

ViewController.m

 

四、苹果公司提供的持久化工具Core Data

iOS开发 数据迁移掘金 ios开发数据存储_数据库_14

1、键-值编码(KVC)

2、在上下文中结合

3、创建新的托管对象

4、获取托管对象

iOS开发 数据迁移掘金 ios开发数据存储_iOS开发 数据迁移掘金_02

iOS开发 数据迁移掘金 ios开发数据存储_数据库_03

//
//  AppDelegate.h
//  Core Data Persistance
//
//  Created by  Jierism on 16/7/27.
//  Copyright © 2016年  Jierism. All rights reserved.
//

#import <UIKit/UIKit.h>
#import <CoreData/CoreData.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;


@end

AppDelegate.h

iOS开发 数据迁移掘金 ios开发数据存储_iOS开发 数据迁移掘金_02

iOS开发 数据迁移掘金 ios开发数据存储_数据库_03

#pragma mark - Core Data stack

@synthesize managedObjectContext = _managedObjectContext;
@synthesize managedObjectModel = _managedObjectModel;
@synthesize persistentStoreCoordinator = _persistentStoreCoordinator;

- (NSURL *)applicationDocumentsDirectory {
    // The directory the application uses to store the Core Data store file. This code uses a directory named "jie.Core_Data_Persistance" in the application's documents directory.
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}

- (NSManagedObjectModel *)managedObjectModel {
    // The managed object model for the application. It is a fatal error for the application not to be able to find and load its model.
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"Core_Data_Persistance" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {
    // The persistent store coordinator for the application. This implementation creates and returns a coordinator, having added the store for the application to it.
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    // Create the coordinator and store
    
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Core_Data_Persistance.sqlite"];
    NSError *error = nil;
    NSString *failureReason = @"There was an error creating or loading the application's saved data.";
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        // Report any error we got.
        NSMutableDictionary *dict = [NSMutableDictionary dictionary];
        dict[NSLocalizedDescriptionKey] = @"Failed to initialize the application's saved data";
        dict[NSLocalizedFailureReasonErrorKey] = failureReason;
        dict[NSUnderlyingErrorKey] = error;
        error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
        // Replace this with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return _persistentStoreCoordinator;
}


- (NSManagedObjectContext *)managedObjectContext {
    // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (!coordinator) {
        return nil;
    }
    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    return _managedObjectContext;
}

#pragma mark - Core Data Saving support

- (void)saveContext {
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        NSError *error = nil;
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}

@end

AppDelegate.m

iOS开发 数据迁移掘金 ios开发数据存储_iOS开发 数据迁移掘金_02

iOS开发 数据迁移掘金 ios开发数据存储_数据库_03

//
//  ViewController.m
//  Core Data Persistance
//
//  Created by  Jierism on 16/7/27.
//  Copyright © 2016年  Jierism. All rights reserved.
//

#import "ViewController.h"
#import "AppDelegate.h"

static NSString * const kLineEntityName = @"Line";
static NSString * const kLineNumberKey = @"lineNumber";
static NSString * const kLineTextKey = @"lineText";

@interface ViewController ()

@property (strong,nonatomic) IBOutletCollection(UITextField) NSArray *lineFields;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // 获取对应用委托的引用,使用这个引用获得为我们创建的托管对象上下文
    AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
    NSManagedObjectContext *context = [appDelegate managedObjectContext];
    
    // 创建一个获取请求并将实体描述传递给它,以便请求指导要检索的对象类型
    NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:kLineEntityName];
    
    // 检索存储中所有Line对象,上下文返回库中每一个Line对象。确保返回的是有效数组,否则记录相应日志
    NSError *error;
    NSArray *objects = [context executeFetchRequest:request error:&error];
    if (objects == nil) {
        NSLog(@"There was an error!"); // 进行适当错误处理
    }
    
    // 使用快熟枚举遍历已获取托管对象的数组,从中提取每个托管对象的lineNum和lineText的值,并用该信息更新用户界面上的文本框
    for (NSManagedObject *oneObject in objects) {
        int lineNum = [[oneObject valueForKey:kLineNumberKey] intValue];
        NSString *lineText = [oneObject valueForKey:kLineTextKey];
        
        UITextField *theField = self.lineFields[lineNum];
        theField.text = lineText;
    }
    
    // 在应用终止时获取通知,保存更改
    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:app];
}


- (void)applicationWillResignActive:(NSNotification *)notification
{
    
    // 与上面一样,获取对应用委托的引用,使用引用获取指向应用的默认托管对象上下文的指针
    AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
    NSManagedObjectContext *context = [appDelegate managedObjectContext];
    
    
    NSError *error;
    
    // 获得每一个字段对应的索引
    for (int i = 0; i < 4; i++) {
        UITextField *theField = self.lineFields[i];
        
        
        // 为Line实体创建获取请求,创建一个谓词确认存储中是否已经有一个与这个字段对应的托管对象
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:kLineEntityName];
        NSPredicate *pred = [NSPredicate predicateWithFormat:@"(%K = %d)",kLineNumberKey,i]; // 谓词
        [request setPredicate:pred];
        
        // 在上下文中执行后去请求,并检查objects是否为nil
        
        NSArray *objects = [context executeFetchRequest:request error:&error];
        if (objects == nil) {
            NSLog(@"There was an error!");
        }
        
        // 声明一个指向NSManagedObject的指针并设置为nil。因为我们不知道要从持久存储中加载托管对象,还是创建新的托管对象。
        // 因此,可以检查与条件匹配的返回对象。如果返回有效的对象就进行加载,否则就创建一个新的托管对象来保存这个字段的文本
        NSManagedObject *theLine = nil;
        if ([objects count] > 0) {
            theLine = [objects objectAtIndex:0];
        }else{
            theLine = [NSEntityDescription insertNewObjectForEntityForName:kLineEntityName inManagedObjectContext:context];
        }
        
        // 使用键-值编码(KVC)来设置行号以及此托管对象的文本
        [theLine setValue:[NSNumber numberWithInt:i] forKey:kLineNumberKey];
        [theLine setValue:theField.text forKey:kLineTextKey];
    }
    // 完成循环,保存更改
    [appDelegate saveContext];
}

@end

ViewController.m