demo 地址 https://github.com/PureLovePeter/DataCache 好用的話 star star stargit
數據庫版本遷移顧名思義就是在原有的數據庫中更新數據庫,數據庫中的數據保持不變對錶的增、刪、該、查。github
數據持久化存儲:sql
plist文件(屬性列表)數據庫
preference(偏好設置)數組
NSKeyedArchiver(歸檔)緩存
SQLite 3微信
CoreDataui
這幾種方式,我就不介紹其餘的了,你能夠本身去查詢其功能用處,主要SQLite 3對數據的存儲。spa
1、手動更新迭代線程
因爲開發須要須要對消息進行存儲,第三方的數據庫侷限因此咱們公司要求對聊天記錄、聊天對像、好友進行查詢(說白了就是和微信搜索作的如出一轍就行了)。
首先你要明白一點爲了不在不通的線程中對相同的數據庫進行操做fmdb已經對數據庫加鎖了。我在數據庫中使用了單例,保證工程全局均可以使用。
+ (MyFMDB *)sharedMyFMDB {
static dispatch_once_t once;
static id instance;
dispatch_once(&once, ^{
instance = [self new];
});
return instance;
}
在本篇文章中先說一下大體的數據庫遷移的思路,在數據庫中存儲一個記錄版本號的表
Version表。本地寫一個記錄工程須要不要更新的版本靜態變量
#define kCurrentSqliteVersion 0;發佈新版本的時候就更改本地的數據庫版本號 0 ->1 而後從數據庫中取出數據庫中的版本號0。
if (oldSqliteVer < kCurrentSqliteVersion) {//sqlite版本小於當前要建立的版本,須要更新
}
依次類推
[self upgrade:oldSqliteVer]; //更新數據庫內容
[self insertSqliteVersion:kCurrentSqliteVersion]; //版本號更新須要放在更新數據庫內容以後,在沒有版本號的數據庫版本中,須要在upgrade的地方去建立version表
而後用遞歸的方式更新
- (void)upgrade:(NSInteger)oldVersion {
if (oldVersion >= kCurrentSqliteVersion) {
return;
}
switch (oldVersion) {
case 0:
[self upgradeFrom0To1];
break;
case 1: //從1版本升級到2版本
[self upgradeFrom1To2];
break;
case 2: //版本拓展:之後如有增長則持續增長
[self upgradeFrom2To3];
break;
case 3: //版本拓展:之後如有增長則持續增長
[self upgradeFrom3To4];
break;
default:
break;
}
oldVersion ++;
// 遞歸判斷是否須要升級:保證老版本從最低升級到當前
[self upgrade:oldVersion];
}
舉個例子,在對應的方法裏邊寫本身更新的內容就能夠了。
- (void)upgradeFrom1To2 {
//這裏執行Sql語句 執行版本1到版本2的更新
FMDatabase * db = [FMDatabase databaseWithPath:self.dbPath];
NSNumber *userId = [NSNumber numberWithLongLong:[UserManeger shareInstance].currentUser.uid];
if ([db open]) {
NSString* SysMessageSql = [NSString stringWithFormat:@"alter table SysMessage add userId int default %@",userId];
NSString* importSql = [NSString stringWithFormat:@"alter table User add userId int default %@",userId];
NSString* importChatSql = [NSString stringWithFormat:@"alter table UserChatMessage add userId int default %@",userId];
BOOL resSysMessage = [db executeUpdate:SysMessageSql];
BOOL res = [db executeUpdate:importSql];
BOOL resChat = [db executeUpdate:importChatSql];
if (res&&resSysMessage&&resChat) {
NSLog(@"更新User,UserChatMessage,SysMessage字段成功");
}else{
NSLog(@"更新User,UserChatMessage,SysMessage字段失敗");
}
[db close];
}
}
1、數據庫的自動更新迭代
數據庫自動更新顧名思義就是:
1.數據庫中有的不須要的表刪除、須要的沒有的表的增長
2.對數據庫已有的不須要的字段刪除、須要的沒有的字段增長;
代碼詳解:
說到自動更新迭代不得不說起runtime,掃盲一下--由於runtime能夠得到屬性的類型和屬性。利用這一點特性進行數據庫的自動更新迭代。(因爲公司要作數據緩存,才寫了這個自動更新換代的數據庫)
首先和手動更新迭代同樣建立單例數據庫對象,全局調用
//建立CacheFMDB類的對象
static CacheFMDB* _instance = nil;
+ (instancetype)sharedCacheFMDB
{
static dispatch_once_t onceToken ;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init] ;
}) ;
return _instance ;
}
咱們首先要想怎麼拼接數據庫語句?
CREATE TABLE PositionTable (id integer PRIMARY KEY NOT NULL , isCollection int , isMyPosition int , isPublicPosition int , newAnswer int , statusId int , importantPos int , matchRate float , urgency int , positionType int , grabOrderPositionStatus int , productType int , lastUpdateTime double , uid double , annualSalary double , areaName text , cityId double , companyId double , createTime double , positionLogo text , companyName text , positionId double , maxShowAnnualSalary double , minShowAnnualSalary double , modifyTime double , publishTime double , suitableTalentCount double , positionTitle text , updateTime double , positionAveFeedBackDay text )
像上邊這一個建立數據庫語句的方法,
REATE TABLE %@ (id integer PRIMARY KEY NOT NULL
這些是固定的而後呢後邊是不通的屬性和對應的類型拼接而成。
那麼c語言對應的oc語言的類型
static NSString *intType = @"i"; // int_32t,int
static NSString *longlongType = @"q"; // long,或者longlong
static NSString *floatType = @"f"; // float
static NSString *doubleType = @"d"; // double
static NSString *boolType = @"B"; // bool
static NSString *imageType = @"UIImage"; // UIImage 類型
static NSString *stringType = @"NSString"; // NSString 類型
static NSString *numberType = @"NSNumber"; // NSNumber 類型
用model便利每個屬性的變量名
Ivar *ivars = class_copyIvarList([model class], &count);
咱們發現變量名字都多餘了一個"_"。包括oc對應的儲存的數據庫語句。因此我進行進一步處理
for (int i = 0; i < count; i++) {
Ivar ivar = ivars[i];
//根據ivar得到其成員變量的名稱
const char *name = ivar_getName(ivar);
//C的字符串轉OC的字符串
NSString *key = [NSString stringWithUTF8String:name];
//放入數組
NSString *keyString = [key stringByReplacingOccurrencesOfString:@"_" withString:@""];
[_ivarsArray addObject:keyString];
// 獲取變量類型,c字符串
const char *cType = ivar_getTypeEncoding(ivar);
//C的字符串轉OC的字符串
NSString *Type = [NSString stringWithUTF8String:cType];
//基本類型數組庫類型轉化
NSLog(@"======%@",Type);
NSString *repleaceString = [self repleaceStringWithCSting:Type];
//放入數組
[_typeArray addObject:repleaceString];
}
/*****屬性和數據庫數據的類型相互轉換*****/
- (NSString *)repleaceStringWithCSting:(NSString *)cSting{
if (![cSting isEqualToString:@""]) {
if ([cSting isEqualToString:@"i"]) {
return @"int";
}else if([cSting isEqualToString:@"q"]){
return @"double";
}else if([cSting isEqualToString:@"f"]){
return @"float";
}else if([cSting isEqualToString:@"d"]){
return @"double";
}else if([cSting isEqualToString:@"B"]){
return @"int";
}else if([cSting containsString:@"NSString"]){
return @"text";
}else if([cSting containsString:@"NSNumber"]){
return @"long";
}
NSAssert(1, @"handleSqliteTable類中 model的屬性狀態不對致使數據庫狀態不對,請覈對後再撥");
return @"未知";
}else return nil;
}
準備工做就緒
我在處理數據庫語句的時候寫了個外部調用方法
//
// handleSqliteTable.h
// RuntimeDemo
//
// Created by peter on 16/3/18.
// Copyright © 2016年 hunteron All rights reserved.
//
#import <Foundation/Foundation.h>
@interface handleSqliteTable : NSObject
/**
* 獲取 model 中的全部屬性數組
* model 須要緩存的對象
*/
- (NSArray *)ivarsArrayWithModel:(NSObject *)model;
/**
* 返回 數據庫表的語句
* tableName 表名字
* model 須要緩存的對象
*/
- (NSString *)sqliteStingWithTableName:(NSString *)tableName model:(NSObject *)model;
/*
*獲取屬性的類型,並轉化爲c的類型,並進行拼接
*/
- (NSArray *)attribleArray:(NSArray *)attribleArray model:(NSObject *)model;
@end
-----------------------數據庫語句的拼接.m文件
//數據庫拼接
- (NSString *)sqliteStingWithTableName:(NSString *)tableName model:(NSObject *)model{
[self test1:model];
return [self complatSqiteAttribiteA:_ivarsArray typeA:_typeArray tableName:tableName];
}
- (NSString *)complatSqiteAttribiteA:(NSArray *)attribiteA typeA:(NSArray *)typeA tableName:(NSString *)tableName{
NSString *string = [NSString stringWithFormat:@"CREATE TABLE %@ (id integer PRIMARY KEY NOT NULL",tableName];
NSString *beginString = @"";
for (int i = 0; i < attribiteA.count;i ++) {
NSString *atAndType = [self sqiteStringAttribite:(NSString *)attribiteA[i] type:(NSString *)typeA[i]];
beginString = [beginString stringByAppendingString:atAndType];
}
return [NSString stringWithFormat:@"%@ %@)",string,beginString];
}
/**
* 數據庫語句拼接
*/
- (NSString *)sqiteStringAttribite:(NSString *)attribite type:(NSString *)type{
return [NSString stringWithFormat:@", %@ %@ ",attribite,type];
}
你把要轉化的model經過
/**
* 返回 數據庫表的語句
* tableName 表名字
* model 須要緩存的對象
*/
- (NSString *)sqliteStingWithTableName:(NSString *)tableName model:(NSObject *)model;
傳過來,例如:
NSString * talentSql = [handel sqliteStingWithTableName:NSStringFromClass([talent class]) model:talent];
就會生成對應的數據庫語句。
CREATE TABLE PositionTable (id integer PRIMARY KEY NOT NULL , isCollection int , isMyPosition int , isPublicPosition int , newAnswer int , statusId int , importantPos int , matchRate float , urgency int , positionType int , grabOrderPositionStatus int , productType int , lastUpdateTime double , uid double , annualSalary double , areaName text , cityId double , companyId double , createTime double , positionLogo text , companyName text , positionId double , maxShowAnnualSalary double , minShowAnnualSalary double , modifyTime double , publishTime double , suitableTalentCount double , positionTitle text , updateTime double , positionAveFeedBackDay text )
這纔是第一步的數據庫語句的拼接,是否是到這一步感受已經好麻煩了??????沒事不要着急
數據庫的遷移無非是:
1.新增數據庫(沒有路徑的狀況)
2.新增表
3.增長字段
4.刪除字段(sqlit3不支持字段的刪除)
1、判斷數據庫路徑是否存在
NSFileManager * fileManager = [NSFileManager defaultManager];
if (![fileManager fileExistsAtPath:kCacheDBPath]) {
這裏我就不說了
}else if([self needUpdateTabelArray:_modelArray]) {
NSLog(@"數據庫已經存在路徑,但須要新增數據庫表:%@",kCacheDBPath);
//須要更新的表的名字
NSArray *tabelName = [self needUpdateTable:_modelArray];
//新建數據表
[self createNewTable:tabelName];
}else{
NSLog(@"數據庫已經存在路徑,不須要新增數據庫表:%@",kCacheDBPath);
}
2、新增表
我會建立一個數組去記錄表名字
_modelArray = [NSMutableArray array];
handleSqliteTable *handel = [[handleSqliteTable alloc]init];
//職位列表
PositionTable *position = [[PositionTable alloc]init];
[_modelArray addObject:position];
NSString * positionSql = [handel sqliteStingWithTableName:NSStringFromClass([position class]) model:position];
//人才列表
TalentTable *talent = [[TalentTable alloc]init];
[_modelArray addObject:talent];
NSString * talentSql = [handel sqliteStingWithTableName:NSStringFromClass([talent class]) model:talent];
判斷有沒有這個表數據庫中
//多個表是否是要更新
- (BOOL)needUpdateTabelArray:(NSArray *)array{
for (NSObject *obj in array) {
NSString *string = NSStringFromClass([obj class]);
NSLog(@"=====%d",[self needUpdateTabel:string]);
if(![self needUpdateTabel:string]) {
return YES;
}
}
return NO;
}
//檢查數據庫的表是否存在
- (BOOL)needUpdateTabel:(NSString *)tableName{
FMDatabase * db = [FMDatabase databaseWithPath:kCacheDBPath];
BOOL need = NO;
if ([db open]) {
//獲得全部的表表名
FMResultSet *rs = [db executeQuery:@"select count(*) as 'count' from sqlite_master where type ='table' and name = ?", tableName];
while ([rs next])
{
// just print out what we've got in a number of formats.
NSInteger count = [rs intForColumn:@"count"];
NSLog(@"isTableOK %ld", (long)count);
if (0 == count)
{
need = NO;
}
else
{
need = YES;
}
}
[rs close];
[db close];
}
return need;
}
表不存在就
//新增表
- (void)createNewTable:(NSArray *)array{
for (NSObject *obj in _modelArray) {
NSString *string = NSStringFromClass([obj class]);
if ([array containsObject:string]) {
handleSqliteTable *handel = [[handleSqliteTable alloc]init];
NSString * tableSqilet = [handel sqliteStingWithTableName:NSStringFromClass([obj class]) model:obj];
[self createATable:tableSqilet];
}
}
}
檢查表裏邊的字段需不須要更新,去如今有的model的屬性和數據庫中的比較找出須要更新的字段
//自動更新機制 表的對應的model變化,須要對錶作相應的增長和刪除操作
for (NSObject *obj in _modelArray) {
//runtime 獲取現有model的全部屬性string
NSArray *array = [handel ivarsArrayWithModel:obj];
//對比數據庫和現有的屬性sting
[self checkAndUpdateTable:obj newAttribe:array];
}
//判斷新老表中有沒有新增字段
- (void)checkAndUpdateTable:(NSObject*)objName newAttribe:(NSArray *)newAttribe{
//數據庫中現有的字段
NSMutableArray *sqliteArray = [NSMutableArray array];
NSString *tableName = NSStringFromClass([objName class]);
FMDatabase * db = [FMDatabase databaseWithPath:kCacheDBPath];
if ([db open]) {
NSString * sql = [NSString stringWithFormat:@"select * from %@",tableName] ;
FMResultSet * rs = [db executeQuery:sql];
NSDictionary * dict = [rs columnNameToIndexMap];
[sqliteArray addObjectsFromArray:[dict allKeys]];
[db close];
}
//須要更新的字段
NSMutableArray *needUpdateName =[NSMutableArray array];
for (NSString *string in newAttribe) {
NSString * lowercaseString = [string lowercaseString];
if (![sqliteArray containsObject:lowercaseString]) {
[needUpdateName addObject:string];
}
}
handleSqliteTable *handel = [[handleSqliteTable alloc]init];
if (needUpdateName.count > 0) {
NSArray *array = [handel attribleArray:needUpdateName model:objName];
//更新
[self updateTabelupdateString:array tableName:tableName];
}
}
//增長新的表字段
- (void)updateTabelupdateString:(NSArray *)updateArray tableName:(NSString *)tableName{
FMDatabase * db = [FMDatabase databaseWithPath:kCacheDBPath];
if ([db open]) {
for (NSString *updateString in updateArray) {
NSString* SysMessageSql = [NSString stringWithFormat:@"alter table %@ add %@",tableName,updateString];
BOOL resSysMessage = [db executeUpdate:SysMessageSql];
if (resSysMessage) {
NSLog(@"新增%@表字段%@成功",tableName,updateString);
}else{
NSLog(@"新增%@表字段%@失敗",tableName,updateString);
}
}
[db close];
}
}
這就完成了9成的更新功能。
還有一成更新表的增刪改查語句。一樣的道理更新數據庫語句.......