iOS数据持久化(二)

(写动画库有点烦,等我写好了,会放到git上,到时候放网址。)

SQLite是嵌入式的和轻量级的sql数据库。广泛用于包括浏览器、ios、android以及一些便携需求的小型web应用系统。

但是它也是关系型数据库,所以在功能上是要强于coreData的。

一、离线缓存

在项目开发中,通常都需要对数据进行离线缓存的处理,就像我上一篇说的,新闻类就很需要这样做。

离线缓存一般都是把数据保存到项目的沙盒中。有以下几种方式

(1)归档:NSCodeing、NSKeyedArchiver(我上一篇说过了)

(2)偏好设置:NSUserDefaults

(3)Plist存储:writeToFile

但是这三者中后两者只能存小型数据,而第一种其实也是不适合大量的数据的,主要是它的存取机制,想存先取,打个比方,你归档了200个模型数据,想增加一个,就必须要先全部读取出来,加上去之后在全部存入。这种很费资源。

所以,这种情况下,上面的三种就没法应付了。

好了  想用它 自然先导库


libsqlite3.dylib, 3是第三个版本的意思。推荐一个博文你们阅读下

由于原生是C语言格式,所以我们先讲原生,然后给大家讲个三方库,很强大,开发时经常用到。

先引用库。#import "sqlite3.h"


然后创建数据库,如果没有就创建(sqlite3_open):

为了避免重复创建,所以在创建前先判断,不过说到这里我想起来了,强调一下,之前一直说 SQLite3不是线程安全,所以所以 一般情况都是加上锁。但是最近我看了一篇博文,之后发现其实是是支持的。等文章最后再说。


<span style="font-size:18px;">sqlite3* database_;
-(BOOL) open{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"mydb.sql"];
    NSFileManager *fileManager = [NSFileManager defaultManager];
    BOOL find = [fileManager fileExistsAtPath:path];
    //找到数据库文件mydb.sql
    if (find) {
        NSLog(@"Database file have already existed.");
        if(sqlite3_open([path UTF8String], &database_) != SQLITE_OK) {
            sqlite3_close(database_);
            NSLog(@"Error: open database file.");
            return NO;
        }
        return YES;
    }
    if(sqlite3_open([path UTF8String], &database_) == SQLITE_OK) {
        bFirstCreate_ = YES;
        [self createChannelsTable:database_];//在后面实现函数createChannelsTable
        return YES;
    } else {
        sqlite3_close(database_);
        NSLog(@"Error: open database file.");
        return NO;
    }
    return NO;
}</span>


创建表格:

<span style="font-size:18px;">//创建表格,假设有五个字段,(id,cid,title,imageData ,imageLen )
 //说明一下,id为表格的主键,必须有。
 //cid,和title都是字符串,imageData是二进制数据,imageLen 是该二进制数据的长度。
  
  - (BOOL) createChannelsTable:(sqlite3*)db
  {
      char *sql = "CREATE TABLE channels (id integer primary key, \
                                         cid text, \
                                          title text, \
                                         imageData BLOB, \
                                         imageLen integer)";
     sqlite3_stmt *statement;
     if(sqlite3_prepare_v2(db, sql, -1, &statement, nil) != SQLITE_OK) {
         NSLog(@"Error: failed to prepare statement:create channels table");
        return NO;
     }
     int success = sqlite3_step(statement);
     sqlite3_finalize(statement);
     if ( success != SQLITE_DONE) {
        NSLog(@"Error: failed to dehydrate:CREATE TABLE channels");
        return NO;
     }
    NSLog(@"Create table 'channels' successed.");
     return YES;

 }</span>

插入数据:

<span style="font-size:18px;">- (BOOL) insertOneChannel:(Channel*)channel
{
    NSData* ImageData = UIImagePNGRepresentation( channel.image_);
    NSInteger Imagelen = [ImageData length];
    sqlite3_stmt *statement;
    static char *sql = "INSERT INTO channels (cid,title,imageData,imageLen)\
                        VALUES(?,?,?,?)";
    //问号的个数要和(cid,title,imageData,imageLen)里面字段的个数匹配,代表未知的值,将在下面将值和字段关联。
    int success = sqlite3_prepare_v2(database_, sql, -1, &statement, NULL);
    if (success != SQLITE_OK) 
    {
        NSLog(@"Error: failed to insert:channels");
        return NO;
    }
   
   //这里的数字1,2,3,4代表第几个问号
    sqlite3_bind_text(statement, 1, [channel.id_ UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(statement, 2, [channel.title_ UTF8String], -1, SQLITE_TRANSIENT);
    sqlite3_bind_blob(statement, 3, [ImageData bytes], Imagelen, SQLITE_TRANSIENT);
    sqlite3_bind_int(statement, 4, Imagelen);    

    success = sqlite3_step(statement);
    sqlite3_finalize(statement);
    
    if (success == SQLITE_ERROR) {
        NSLog(@"Error: failed to insert into the database with message.");
        return NO;
    } 
 
    NSLog(@"Insert One Channel#############:id = %@",channel.id_);
    return YES;
}</span>

查询数据:
- (void) getChannels:(NSMutableArray*)fChannels
{
    sqlite3_stmt *statement = nil;
    char *sql = "SELECT * FROM channels";
    if (sqlite3_prepare_v2(database_, sql, -1, &statement, NULL) != SQLITE_OK) 
    {
        NSLog(@"Error: failed to prepare statement with message:get channels.");
    }
    //查询结 果集中一条一条的遍历所有的记录,这里的数字对应的是列值。     while (sqlite3_step(statement) == SQLITE_ROW) 
    {
        char* cid       = (char*)sqlite3_column_text(statement, 1);
        char* title     = (char*)sqlite3_column_text(statement, 2);
        Byte* imageData = (Byte*)sqlite3_column_blob(statement, 3);
        int imageLen    = sqlite3_column_int(statement, 4);        
        Channel* channel = [[Channel alloc] init];
        if(cid)
            channel.id_ = [NSString stringWithUTF8String:cid];
        if(title)
            channel.title_ = [NSString stringWithUTF8String:title];
        if(imageData)
        {
            UIImage* image = [UIImage imageWithData:[NSData dataWithBytes:imageData length:imageLen]];
            channel.image_ = image;
        }
        [fChannels addObject:channel];
        [channel release];
    }
    sqlite3_finalize(statement);
}

上面的方法不用说,就是看着不舒服,所以,我给大家推荐个第三方库,FMDB

毕竟第三方库,做的真的很好。FMDB下载

通过FMDB,你可以定制你自己的DBManager,我做了一个简单的,直接上代码,很容易懂。

这是我的.h,自己定的一些接口,完成增删查改的功能。

<span style="font-size:18px;">//
//  DBManager.h
//  FMDBDemo
//
//  Created by JackYang on 15/9/8.
//  Copyright (c) 2015年 JackYang. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "PersonModel.h"

@interface DBManager : NSObject

+(instancetype)sharedInstance;

- (void)addPerson:(PersonModel*)model;
- (void)deletePerson:(PersonModel*)model;
- (void)updatePerson:(PersonModel*)model;
- (NSArray*)allPerson;
- (BOOL)isPersonExists:(PersonModel*)model;

@end</span>

然后是.m的实现,由于我只是写了一个Demo,所以只是做了一个PersonModel,实现就是增加,删除person,你们可以自己创建自己想要的表,定制自己的sql语句,w3school 有sql教程。不会的可以去看看。

<span style="font-size:18px;">//
//  DBManager.m
//  FMDBDemo
//
//  Created by JackYang on 15/9/8.
//  Copyright (c) 2015年 JackYang. All rights reserved.
//

#import "DBManager.h"
#import "FMDatabase.h"

@interface DBManager ()

//定义数据库,FMDatabase 来描述操作数据库
@property(nonatomic)FMDatabase *db;

@end

@implementation DBManager

//数据库本质也是一个文件,DBMS 来进行管理
+(instancetype)sharedInstance
{
    static DBManager *s_dbManager = nil;
//    //@synchronized(self) 内部实现时加锁,防止多线程不安全
//    @synchronized(self){
//        if (s_dbManager == nil) {
//            s_dbManager = [[DBManager alloc]init];
//        }
//        return s_dbManager;
//    }
    //两种方式可以创建单例。但是这是共享单例,你可以选择吧init隐藏,就是换个方法名,不对外公开。
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        s_dbManager = [[DBManager alloc]init];
    });
    return s_dbManager;
}

- (id)init
{
    if (self = [super init]) {
        //创建数据库,在数据库中创建表
        NSString *dbFilePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/Person.db"];
        NSLog(@"DB Path = %@",dbFilePath);
        //使用数据库文件路径,初始化db实例
        self.db = [[FMDatabase alloc]initWithPath:dbFilePath];
        
        //打开数据库,如果数据库文件不存在,FMDB自动创建一个,并打开数据库
        if(![self.db open]){
            NSLog(@"打开数据库失败");
        }else{
            [self createPersonTable];
        }
        
    }
    return self;
}

- (void)createPersonTable
{
    NSString *sql = @"CREATE TABLE IF NOT EXISTS Persons(PersonId INTEGER PRIMARY KEY AUTOINCREMENT,Age INTEGER,FirstName TEXT,LastName TEXT,Address TEXT,City varchar(255),HeadImage BLOB)";
    
    //使用FMDB时,对sql语句的操作
    //executeQuery 针对select 语句
    //[self.db executeQuery:<#(NSString *), ...#>];
    
    //update ,delete,insert 使用executeUpdate
    if (![self.db executeUpdate:sql]) {
        NSLog(@"创建Person表失败");
        [self.db close];
    };
}

- (void)addPerson:(PersonModel*)model
{
    //?代表是占位符,该位置会填充相应的值,注意:传入的值一定是id类型,如果是整数,浮点数等,需要转成NSNumber
    NSString *sql = @"INSERT INTO Persons(Age,FirstName,LastName,Address,City,HeadImage) VALUES(?,?,?,?,?,?)";
    
    //UIImagePNGRepresentation 把图片转成NSData的数据
    NSData *imageData = UIImagePNGRepresentation(model.headImage);
    BOOL result = [self.db executeUpdate:sql,@(model.age),model.firstName,model.lastName,model.address,model.city,imageData];
    if(result == NO){
        NSLog(@"插入数据失败");
    }
}

- (void)deletePerson:(PersonModel*)model
{
    NSString *sql = @"DELETE FROM Persons WHERE PersonId = ?";
    BOOL result = [self.db executeUpdate:sql,@(model.personId)];
    if(result == NO){
        NSLog(@"插入数据失败");
    }
}

- (void)updatePerson:(PersonModel*)model
{
    NSString *sql = @"UPDATE Persons SET Age = ?,FirstName = ?,LastName = ?,Address = ?,City = ? WHERE PersonId = ?";
    BOOL result = [self.db executeUpdate:sql,@(model.age),model.firstName,model.lastName,model.address,model.city,@(model.personId)];
    if (!result) {
        NSLog(@"更新记录失败");
    }
}

- (NSArray*)allPerson
{
    NSMutableArray *resultArray = [NSMutableArray array];
    NSString *sql = @"SELECT * FROM Persons";
    FMResultSet *resultSet = [self.db executeQuery:sql];
    //resultSet.next 指向的是一条记录
    while (resultSet.next) {
        PersonModel *person = [[PersonModel alloc]init];
        person.personId   = [resultSet intForColumn:@"personId"];
        person.age        = [resultSet intForColumn:@"Age"];
        person.firstName  = [resultSet stringForColumn:@"FirstName"];
        person.lastName   = [resultSet stringForColumn:@"LastName"];
        person.address    = [resultSet stringForColumn:@"Address"];
        person.city       = [resultSet stringForColumn:@"City"];
        NSData *imageData = [resultSet dataForColumn:@"HeadImage"];
        person.headImage  = [UIImage imageWithData:imageData];
        
        [resultArray addObject:person];
    }
    [resultSet close];
    return resultArray;
}


- (BOOL)isPersonExists:(PersonModel*)model
{
    BOOL result = NO;
    NSString *sql = @"SELECT * FROM Persons WHERE personId = ?";
    FMResultSet *resultSet = [self.db executeQuery:sql,@(model.personId)];
    if (resultSet.next) {
        result = YES;
    }
    [resultSet close];
    return result;
}

@end</span>

在FMDB中 增删改 都是使用的 executeUpdate,只有 查使用的时executeQuery,这里要注意点

而且,在查的时候 也是和原生一样 使用结果集,进行遍历。

剩下的是就是使用你的工具类,存储你的数据了


<span style="font-size:18px;">- (IBAction)getAllPerson:(id)sender {
    self.personList = [[DBManager sharedInstance] allPerson];
    [self.tableView reloadData];
}

- (IBAction)addPerson:(id)sender {
    PersonModel *person = [[PersonModel alloc]init];
    person.age = 20;
    person.firstName = @"123";
    person.lastName  = @"456";
    person.address   = @"china";
    person.city      = @"HeFei";
    person.headImage = [UIImage imageNamed:@"[email protected]"];
    [[DBManager sharedInstance] addPerson:person];
}

- (IBAction)deletePerson:(id)sender {
    PersonModel *person = [self.personList objectAtIndex:0];
    [[DBManager sharedInstance] deletePerson:person];
}

- (IBAction)updatePerson:(id)sender {
    PersonModel *person = [self.personList objectAtIndex:0];
    person.firstName = @"aaa";
    person.lastName  = @"bbb";
    
    [[DBManager sharedInstance] updatePerson:person];
}
</span>

好了,还有什么没有说?

没错,就是原生sql线程安全问题。

这里我就不搬别人的博文了,你们自己去看

好吧,就这些了,啥不懂得 可以问我。








你可能感兴趣的:(ios,sqlite,存储,iOS数据持久化,iOS缓存策略)