iOS 本地数据库持久化存储 (包含版本升级和切换账号的问题)

概述

移动app端数据库的创建,在思想上有两种方式:
a.创建通用表(id, time, data), time是操作时间,data是存放json数据的字符串(把数据转json字符串存放)。这样的好处是不需要想着数据库升级的问题,但若是有查询类似点赞数量就感觉不太适合了。
b.创建正常表,有什么字段写什么字段。但这样会涉及到表字段的增加,也就是数据库的升级。

参考文档:
a:这是唐巧大神的文章 通用表
b:这是一位高手的文章 正常表

正文

我今天说的是b种数据库。解决了app切换账号数据库的更换和数据库版本升级问题。

总体思想:

1.切换账号:例如手机号登录,以手机号来命名数据库,这样解决不同手机登录,来访问不同的数据库。还有种思想是一个数据库,表不同,某个功能表前加前缀来区分,但是我想象感觉有点麻烦,去升级和添加字段的时候。

2.版本升级:它也有两种思路,一种是直接sql命令添加需要的字段名和类型。另一种是创建新表,把老表数据拷贝到新表里去,然后删去老表,更改新表的名字。

代码展示

这是我写的 demo
欢迎点赞收藏和批评。每个语言都博大精深我们应该谦虚向学。

IGFMDB.h 文件

#import 
#import "IGParameters.h"

NS_ASSUME_NONNULL_BEGIN



// 数据库支持的类型(如果不满足条件以下条件,那么在后续会增加)
typedef NS_ENUM(NSUInteger, IGFMDBValueType) {
    IGFMDBValueTypeString,     // 字符串
    IGFMDBValueTypeInteger,    // 整型,长整型,bool值 都是integer
    IGFMDBValueTypeFloat,      // 浮点型,double
    IGFMDBValueTypeData,       // 二进制数据
};

// 数学运算的类型
typedef NS_ENUM(NSUInteger, IGFMDBMathType) {
    IGFMDBMathTypeSum,         // 总和
    IGFMDBMathTypeAvg,         // 平均值
    IGFMDBMathTypeMax,         // 最大值
    IGFMDBMathTypeMin,         // 最小值
};







@interface IGFMDB : NSObject

#pragma mark - 初始化

+ (instancetype) shareDatabase; //会默认创建一个db

- (void) createDBWithName:(NSString *)dbName;

- (void) createDBWithName:(NSString *)dbName path:(NSString *)dbPath;


- (void)upgradeDatabase:(NSString *)dbName;






#pragma mark - 创建表

/**
 *  根据传入的Model去创建表(推荐使用此方法)
 *  @param modelClass
    model的属性名称作为表的key值, 属性的value的类型也就是表里面的value的类型,如value可以是NSString,integer,float,bool等
 *  @param excludedProperties  被排除掉属性,这些属性被排除掉之后则不会存在数据库当中
 *  @param tableName           表名,不可以为nil
 *  @return 是否创建成功
 */
- (BOOL)createTableWithModelClass:(Class _Nonnull)modelClass
               excludedProperties:(NSArray * _Nullable)excludedProperties
                        tableName:(NSString * _Nonnull)tableName;







#pragma mark - 插入数据

/**
 *  插入一条数据(推荐使用)
 *  @param model        需要插入Model
 *  @param tableName    表名,不可以为nil
 *  @return             是否插入成功
 */
- (BOOL)insertWithModel:(id _Nonnull)model tableName:(NSString * _Nonnull)tableName;


/**
 *  插入多条数据
 *  @param models       需要插入的存放Model的数组。其中必须要保证数组内的Model都是同一类型的Model
 *  @param tableName    表名,不可以为nil
 *  在连续插入多条数据的时候,很有可能会出现插入不成功的情况,如果想要联调,请将shouldOpenDebugLog设为YES
 */
- (void)insertWithModels:(NSArray *)models tableName:(NSString * _Nonnull)tableName;






#pragma mark - 删除数据

/**
 *  根据参数删除表中的数据
 *  @param tableName     表的名字
 *  @param parameters    参数,IGParameters决定了sql语句"where"后面的参数。
 *  @return 是否删除成功
 */
- (BOOL)deleteFromTable:(NSString * _Nonnull)tableName whereParameters:(IGParameters *)parameters;

/**
 *  删除所有数据
 *  @param tableName    同上
 *  @return             同上
 */
- (BOOL)deleteAllDataFromTable:(NSString * _Nonnull)tableName;





#pragma mark - 更改数据

/**
 *  根据参数删除表中的数据
 *  @param tableName    表的名字,不可以为nil
 *  @param dictionary   要更新的key-value.在我经验来看,更改典里部分数据
 *  @param parameters   参数,IGParameters决定了sql语句"where"后面的参数
 */
- (BOOL)updateTable:(NSString * _Nonnull)tableName dictionary:(NSDictionary * _Nonnull)dictionary whereParameters:(IGParameters *)parameters;





#pragma mark - 查询数据

/**
 *  根据参数删除表中的数据
 *  @param tableName    表的名字,不可以为nil
 *  @param modelClass   modelClass里属性的都当key拿值
 *  @param parameters   参数,IGParameters决定了sql语句"where"后面的参数
 *  @return             返回所有符合条件的数据
 */
- (NSArray *)queryFromTable:(NSString * _Nonnull)tableName model:(Class _Nonnull)modelClass whereParameters:(IGParameters *)parameters;




#pragma mark - 除去增删改查之外常用的功能


/**
 *  表是否存在
 *  @param tableName    表的名字
 *  @return             表是否存在
 */
- (BOOL)existTable:(NSString * _Nonnull)tableName;

/**
 *  为一个表增加字段
 *  @param tableName    表的名字
 *  @param column       要增加的字段
 *  @param type         增加的字段类型
 *  @return             是否添加成功
 */
- (BOOL)alterTable:(NSString * _Nonnull)tableName column:(NSString * _Nonnull)column type:(IGFMDBValueType)type;

/**
 *  删除一张表
 *  @param tableName    表的名字
 *  @return             是否删除成功
 */
- (BOOL)dropTable:(NSString * _Nonnull)tableName;

/**
 *  获取某一个表中所有的字段名
 *  @param tableName    表的名字
 *  @return             所有字段名
 */
- (NSArray *)getAllColumnsFromTable:(NSString * _Nonnull)tableName;

/**
 *  获取表中有多少条数据
 *  @param tableName    表的名字
 *  @param parameters   参数,IGParameters决定了sql语句"where"后面的参数
 *  @return             数据的个数
 */
- (long long int)numberOfItemsFromTable:(NSString * _Nonnull)tableName whereParameters:(IGParameters * _Nullable)parameters;

/**
 *  数学相关操作
 *  @param type         数学运算的type
 *  @param tableName    表的名字
 *  @param parameters   参数,IGParameters决定了sql语句"where"后面的参数
 *  @return             计算的值
 */
- (double)numberWithMathType:(IGFMDBMathType)type table:(NSString * _Nonnull)tableName column:(NSString * _Nonnull)column whereParameters:(IGParameters * _Nullable)parameters;





@end

NS_ASSUME_NONNULL_END

IGFMDB.m 文件

#import "IGFMDB.h"
#import 
#import 

#ifdef DEBUG
#define debugLog(...)    NSLog(__VA_ARGS__)
#define debugMethod()    NSLog(@"%s", __func__)
#define debugError()     NSLog(@"Error at %s Line:%d", __func__, __LINE__)
#else
#define debugLog(...)
#define debugMethod()
#define debugError()
#endif

#define PATH_OF_DOCUMENT    [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]

#define kDatabaseVersionKey     @"YH_DBVersion" //数据库版本


static NSString * const DEFAULT_DB_NAME = @"database.sqlite";
static NSString * const ig_primary_key  = @"primaryId";     // 主键
static NSString * const ig_sql_text     = @"text";          // 字符串
static NSString * const ig_sql_real     = @"real";          // 浮点型
static NSString * const ig_sql_blob     = @"blob";          // 二进制
static NSString * const ig_sql_integer  = @"integer";       // 整型



@interface IGFMDB ()

@property (strong, nonatomic) FMDatabaseQueue * dbQueue;

@property (nonatomic, assign) int currentDBVersion;  //当要升级就给这个值赋值

@end

@implementation IGFMDB
{
    // 保证创建sql语句时的线程安全
    dispatch_semaphore_t _sqlLock;
}


- (void)close {
    [_dbQueue close];
    _dbQueue = nil;
}

// 校验表名
- (BOOL)checkTableName:(NSString *)tableName {
    if (tableName == nil || tableName.length == 0 || [tableName rangeOfString:@" "].location != NSNotFound) {
        debugLog(@"ERROR, table name: %@ format error.", tableName);
        return NO;
    }
    return YES;
}

- (BOOL)isStringVaild:(id)object  {
     return [object isKindOfClass:[NSString class]] && ((NSString*)object).length > 0;
}




#pragma mark -单利,创建默认DB

+ (instancetype) shareDatabase {
    static IGFMDB *_instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (_instance == nil) {
            _instance = [[IGFMDB alloc] init];
        }
    });
    return _instance;
}

- (instancetype) init {
    if (self = [super init]) {
        _currentDBVersion = 0;
        _sqlLock = dispatch_semaphore_create(1);
        [self createDBWithName:DEFAULT_DB_NAME];
    }
    return self;
}




#pragma mark -创建DB方法

- (void) createDBWithName:(NSString *)dbName {
    
    [self createDBWithName:dbName path:PATH_OF_DOCUMENT];
}

- (void) createDBWithName:(NSString *)dbName path:(NSString *)dbPath {
    
    NSString *path = [dbPath stringByAppendingPathComponent:dbName];
    
    debugLog(@"-----path: %@ \n-----",path);
    
    if (_dbQueue) {
        [self close];
    }
    _dbQueue = [FMDatabaseQueue databaseQueueWithPath:path];
    
    

    
}

#pragma mark -数据库升级模块
//这样写还是很好的, 不可以参数暴露在外面
- (void)upgradeDatabase:(NSString *)dbName
{
    int dbVersion = [self getDBVersion:dbName];
    
    if (_currentDBVersion > dbVersion) {
        //debugLog(@"-----升级操作: 为student表 增加个sound------");
        if ([self existTable:@"student"]) {
            [self alterTable:@"student" column:@"sound" type:IGFMDBValueTypeData];
            [self setDBVersion:_currentDBVersion dbName:dbName];
            //debugLog(@"-----升级操作成功-----");
        } 
    }
    
    
}




- (int)getDBVersion:(NSString *)dbName {
    return (int)[[NSUserDefaults standardUserDefaults] integerForKey:kDatabaseVersionKey];
}

- (void)setDBVersion:(int)version dbName:(NSString *)dbName {
    [[NSUserDefaults standardUserDefaults] setInteger:version forKey:kDatabaseVersionKey];
    [[NSUserDefaults standardUserDefaults] synchronize];
}






#pragma mark -创建表

- (BOOL)createTableWithModelClass:(Class _Nonnull)modelClass excludedProperties:(NSArray * _Nullable)excludedProperties tableName:(NSString * _Nonnull)tableName {
    
    if (![self checkTableName:tableName]) {
        return NO;
    }

    IGLock(_sqlLock);
    NSString *pkID = ig_primary_key;
    NSMutableString *sqliteString = [NSMutableString  stringWithFormat:@"create table if not exists %@ (%@ integer primary key",tableName, pkID];
    IGUnLock(_sqlLock);

    NSDictionary *properties = [self getPropertiesWithModel:modelClass]; //获取model的所有属性以及类型
    for (NSString *key in properties) {
        if ([excludedProperties containsObject:key]) {
            continue;
        }
        [sqliteString appendFormat:@", %@ %@", key, properties[key]];
    }
    [sqliteString appendString:@")"];

    __block BOOL res ;
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        res = [db executeUpdate:sqliteString];
    }];

    return res;
}

#pragma mark - 插入数据

- (BOOL)insertWithModel:(id _Nonnull)model tableName:(NSString * _Nonnull)tableName {
    
    if (![self checkTableName:tableName]) {
        return NO;
    }
    
    if (model)
    {
        IGLock(_sqlLock);
        NSMutableString *sqliteString = [NSMutableString stringWithFormat:@"insert into %@ (", tableName];
        NSArray *columns =  [self getAllColumnsFromTable:tableName dbQueue:self.dbQueue isIncludingPrimaryKey:NO];
        NSMutableArray *values = [NSMutableArray array];
        for (int index = 0; index < columns.count; index++) {
            [values addObject:@"?"];
        }
        [sqliteString appendFormat:@"%@) values (%@)", [columns componentsJoinedByString:@","], [values componentsJoinedByString:@","]];
        IGUnLock(_sqlLock);
        
        
        __block BOOL isSuccess ;
        NSArray *arguments = [self getValuesFromModel:model columns:columns];
        [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
            isSuccess = [db executeUpdate:sqliteString withArgumentsInArray:arguments];
        }];
        if (!isSuccess) {
            debugLog(@"----插入失败----");
        }
        else {
            debugLog(@"----插入成功----");
        }
        return  isSuccess;
        
    }
    else {
        
        return NO;
    }
    
    
}


- (void)insertWithModels:(NSArray *)models tableName:(NSString * _Nonnull)tableName {
    
    if (![self checkTableName:tableName]) return;
    
    
    if (models && [models isKindOfClass:[NSArray class]] && models.count > 0)
    {
        
        IGLock(_sqlLock);
        NSMutableString *sqliteString = [NSMutableString stringWithFormat:@"insert into %@ (", tableName];
        NSArray *columns = [self getAllColumnsFromTable:tableName dbQueue:self.dbQueue isIncludingPrimaryKey:NO];
        NSMutableArray *values = [NSMutableArray array];
        for (int index = 0; index < columns.count; index++) {
            [values addObject:@"?"];
        }
        [sqliteString appendFormat:@"%@) values (%@)", [columns componentsJoinedByString:@","], [values componentsJoinedByString:@","]];
        IGUnLock(_sqlLock);
        
        
        for (id model in models) {
            
            __block BOOL isSuccess;
            NSArray *arguments = [self getValuesFromModel:model columns:columns];
            [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
                isSuccess = [db executeUpdate:sqliteString withArgumentsInArray:arguments];
            }];
            if (!isSuccess) {
                debugLog(@"----插入失败----");
            }
        }
    }
    else {
        
        debugLog(@"----插入数据的数据源有误----");
    }
    
}






#pragma mark - 删除数据

- (BOOL)deleteFromTable:(NSString * _Nonnull)tableName whereParameters:(IGParameters *)parameters {

    if (![self checkTableName:tableName]) return NO;
    
    
    if (![self isStringVaild:parameters.whereParameters]) {
        debugLog(@"-----单条删除没有条件------");
        return NO;
    }
    
    
    IGLock(_sqlLock);
    NSMutableString *sqliteString = [NSMutableString stringWithFormat:@"delete from %@", tableName];
    if (parameters) {
        [sqliteString appendFormat:@" where %@", parameters.whereParameters];
    }
    IGUnLock(_sqlLock);
    
    
    __block BOOL isSuccess ;
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        isSuccess = [db executeUpdate:sqliteString];
    }];
    if (!isSuccess) {
        debugLog(@"----删除失败----");
    }
    return isSuccess;
}


- (BOOL)deleteAllDataFromTable:(NSString * _Nonnull)tableName {

    if (![self checkTableName:tableName]) return NO;
    

    IGLock(_sqlLock);
    NSMutableString *sqliteString = [NSMutableString stringWithFormat:@"delete from %@", tableName];
    IGUnLock(_sqlLock);
    
    
    __block BOOL isSuccess ;
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        isSuccess = [db executeUpdate:sqliteString];
    }];
    if (!isSuccess) {
        debugLog(@"----删除失败----");
    }
    return isSuccess;
    
}








#pragma mark - 更改数据

- (BOOL)updateTable:(NSString * _Nonnull)tableName dictionary:(NSDictionary * _Nonnull)dictionary whereParameters:(IGParameters *)parameters {

    if (![self checkTableName:tableName]) return NO;
    
    if (dictionary.allKeys.count <= 0) {
        debugLog(@"----要更新的数据不能为nil----");
        return NO;
    }
    

    IGLock(_sqlLock);
    NSMutableString *sqliteString = [NSMutableString stringWithFormat:@"update %@ set ", tableName];
    NSMutableArray *values = [NSMutableArray array];
    for (NSString *key in dictionary) {
        if ([key isEqualToString:ig_primary_key]) {
            continue;
        }
        [sqliteString appendFormat:@"%@ = ? ", key];
        [values addObject:dictionary[key]];
    }
    IGUnLock(_sqlLock);

    
    if (values.count > 0) {
        
        if ([self isStringVaild:parameters.whereParameters]) {
            [sqliteString appendFormat:@"where %@", parameters.whereParameters];
        } else {
            debugLog(@"sql语句当中,where后面的参数为nil");
            [sqliteString deleteCharactersInRange:NSMakeRange(sqliteString.length-1, 1)];
        }

        __block BOOL isSuccess;
        [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
            isSuccess = [db executeUpdate:sqliteString withArgumentsInArray:values];
        }];
        
        if (!isSuccess) {
            debugLog(@"-----更改数据------");
        }
        return isSuccess;
        
    } else {

        debugLog(@"要更新的数据不能仅仅含有主键");
        return NO;
    }
}





#pragma mark - 查询数据

- (NSArray *)queryFromTable:(NSString * _Nonnull)tableName model:(Class _Nonnull)modelClass whereParameters:(IGParameters *)parameters {

    
    if (![self checkTableName:tableName]) return nil;
    

    IGLock(_sqlLock);
    NSMutableString *sqliteString = [NSMutableString stringWithFormat:@"select * from %@", tableName];
    if (parameters && [self isStringVaild:parameters.whereParameters]) {
        [sqliteString appendFormat:@" where %@", parameters.whereParameters];
    }
    IGUnLock(_sqlLock);
    
    
    __block NSMutableArray *array = [NSMutableArray array];
    NSDictionary *properties = [self getPropertiesWithModel:modelClass];
    
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        
        FMResultSet *res = [db executeQuery:sqliteString];
        while ([res next]) {
            NSMutableDictionary *dict = [NSMutableDictionary dictionary];
            for (NSString *key in properties) {
                NSString *type = properties[key];
                // 根据数据类型从数据库当中获取数据
                if ([type isEqualToString:ig_sql_text]) {
                    // 字符串
                    dict[key] = [res stringForColumn:key] ? : @"";
                } else if ([type isEqualToString:ig_sql_integer]) {
                    // 整型
                    dict[key] = @([res longLongIntForColumn:key]);
                } else if ([type isEqualToString:ig_sql_real]) {
                    // 浮点型
                    dict[key] = @([res doubleForColumn:key]);
                } else if ([type isEqualToString:ig_sql_blob]) {
                    // 二进制
                    id value = [res dataForColumn:key];
                    if (value) {
                        dict[key] = value;
                    }
                }
            }
            [array addObject:dict];
        }
        
    }];
    
    return (array.count > 0 ? array : nil);
    
}




#pragma mark - 除去增删改查之外常用的功能



/**
 *  表是否存在
 *  @param tableName    表的名字
 *  @return             表是否存在
 */
- (BOOL)existTable:(NSString * _Nonnull)tableName {

    if (![self checkTableName:tableName]) return NO;
    
    __block BOOL isExist;
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
         FMResultSet *res = [db executeQuery:@"select count(*) as 'count' from sqlite_master where type ='table' and name = ?", tableName];
        while ([res next]) {
            NSInteger count = [res intForColumn:@"count"];
            isExist = ((count == 0) ? NO : YES);
        }
    }];
    
    return isExist;

}


/**
 *  为一个表增加字段
 *  @param tableName    表的名字
 *  @param column       要增加的字段
 *  @param type         增加的字段类型
 *  @return             是否添加成功
 */
- (BOOL)alterTable:(NSString * _Nonnull)tableName column:(NSString * _Nonnull)column type:(IGFMDBValueType)type {

    if (![self checkTableName:tableName]) return NO;
    
    if (![self isStringVaild:column]) {
        debugLog(@"---要新增的column必须是字符串,且不能为nil-----");
        return NO;
    }
    
    
    IGLock(_sqlLock);
    NSString *typeString = nil;
    switch (type) {
        case IGFMDBValueTypeString:
            typeString = ig_sql_text;
            break;
        case IGFMDBValueTypeInteger:
            typeString = ig_sql_integer;
            break;
        case IGFMDBValueTypeFloat:
            typeString = ig_sql_real;
            break;
        case IGFMDBValueTypeData:
            typeString = ig_sql_blob;
            break;
        default:
            typeString = @"";
            break;
    }
    NSString *sqliteString = [NSString stringWithFormat:@"alter table %@ add column %@ %@", tableName, column, typeString];
    IGUnLock(_sqlLock);
    
    __block BOOL isSuccess;
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        isSuccess = [db executeUpdate:sqliteString];
    }];
    
    return isSuccess;
    
    
}


/**
 *  删除一张表
 *  @param tableName    表的名字
 *  @return             是否删除成功
 */
- (BOOL)dropTable:(NSString * _Nonnull)tableName {

    if (![self checkTableName:tableName]) return NO;
    
    IGLock(_sqlLock);
    NSString *sqliteString = [NSString stringWithFormat:@"drop table %@", tableName];
    IGUnLock(_sqlLock);
    
    __block BOOL isSuccess ;
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        isSuccess = [db executeUpdate:sqliteString];
    }];
    
    return isSuccess;
    
}

/**
 *  获取某一个表中所有的字段名
 *  @param tableName    表的名字
 *  @return             所有字段名
 */
- (NSArray *)getAllColumnsFromTable:(NSString * _Nonnull)tableName {
    
    if (![self checkTableName:tableName]) return nil;
    
    return [self getAllColumnsFromTable:tableName dbQueue:self.dbQueue isIncludingPrimaryKey:YES];
}


/**
 *  获取表中有多少条数据
 *  @param tableName    表的名字
 *  @param parameters   参数,IGParameters决定了sql语句"where"后面的参数
 *  @return             数据的个数
 */
- (long long int)numberOfItemsFromTable:(NSString * _Nonnull)tableName whereParameters:(IGParameters * _Nullable)parameters {

    if (![self checkTableName:tableName]) return 0;
    
    IGLock(_sqlLock);
    NSMutableString *sqliteString = [NSMutableString stringWithFormat:@"select count(*) as 'count' from %@", tableName];
    if (parameters && [self isStringVaild:parameters.whereParameters]) {
        [sqliteString appendFormat:@" where %@", parameters.whereParameters];
    }
    IGUnLock(_sqlLock);
    
    __block long long count ;
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        FMResultSet *res = [db executeQuery:sqliteString];
        while ([res next]) {
            count = [res longLongIntForColumn:@"count"];
        }
    }];
    return count;

}



/**
 *  数学相关操作
 *  @param type         数学运算的type
 *  @param tableName    表的名字
 *  @param parameters   参数,IGParameters决定了sql语句"where"后面的参数
 *  @return             计算的值
 */
- (double)numberWithMathType:(IGFMDBMathType)type table:(NSString * _Nonnull)tableName column:(NSString * _Nonnull)column whereParameters:(IGParameters * _Nullable)parameters {


    if (![self checkTableName:tableName]) return 0;
    
    
    if (![self isStringVaild:parameters.whereParameters]) {
        debugLog(@"---要新增的column必须是字符串,且不能为nil----");
        return 0.0;
    }
    
    IGLock(_sqlLock);
    NSMutableString *sqliteString = nil;
    NSString *operation = nil;
    switch (type) {
        case IGFMDBMathTypeSum:
            operation = @"sum";
            break;
        case IGFMDBMathTypeAvg:
            operation = @"avg";
            break;
        case IGFMDBMathTypeMax:
            operation = @"max";
            break;
        case IGFMDBMathTypeMin:
            operation = @"min";
            break;
        default:
            break;
    }
    if ([self isStringVaild:operation]) {
        sqliteString = [NSMutableString stringWithFormat:@"select %@(%@) %@Count from %@", operation, column, operation, tableName];
    } else {
        debugLog(@"----不支持当前运算----");
    }
    if (parameters) {
        [sqliteString appendFormat:@" where %@", parameters.whereParameters];
    }
    IGUnLock(_sqlLock);
    
    __block double value = 0.0;
    [_dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        FMResultSet *res = [db executeQuery:sqliteString];
        while ([res next]) {
            value = [res doubleForColumn:[NSString stringWithFormat:@"%@Count", operation]];
        }
    }];
    
    return value;
    

}




#pragma mark - 数据库相关操作

// 获取数据库里的所有元素
- (NSArray *)getAllColumnsFromTable:(NSString *)tableName dbQueue:(FMDatabaseQueue *)dbQueue isIncludingPrimaryKey:(BOOL)isIncluding {

    __block  NSMutableArray *columns = [NSMutableArray array];
    
    [dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
        FMResultSet *res = [db getTableSchema:tableName];
        while ([res next]) {
            NSString *columnName = [res stringForColumn:@"name"];
            if ([columnName isEqualToString:ig_primary_key] && !isIncluding) {
                 continue;
            }
            [columns addObject:columnName];
        }
    }];
    return columns;
}






#pragma mark - Private Method

/**
 *  基于runtime获取model的所有属性以及类型
 *  根据传入的ModelClass去获取所有的属性的key以及类型type,返回值的字典的key就是modelClass的属性,value就是modelClass的属性对应的type
 */
- (NSDictionary *)getPropertiesWithModel:(Class)modelClass {
    NSMutableDictionary *dict = [NSMutableDictionary dictionary];

    unsigned int count;
    objc_property_t *propertyList = class_copyPropertyList(modelClass, &count);
    for (int index = 0; index < count; index++) {
        objc_property_t property = propertyList[index];
        NSString *key = [NSString stringWithFormat:@"%s", property_getName(property)];
        NSString *type = nil;
        NSString *attributes = [NSString stringWithFormat:@"%s", property_getAttributes(property)];

        if ([attributes hasPrefix:@"T@\"NSString\""]) {
            type = ig_sql_text;
        } else if ([attributes hasPrefix:@"Tf"] || [attributes hasPrefix:@"Td"]) {
            type = ig_sql_real;
        } else if ([attributes hasPrefix:@"T@\"NSData\""]) {
            type = ig_sql_blob;
        } else if ([attributes hasPrefix:@"Ti"] || [attributes hasPrefix:@"TI"] || [attributes hasPrefix:@"Tl"] || [attributes hasPrefix:@"TL"] || [attributes hasPrefix:@"Tq"] || [attributes hasPrefix:@"TQ"] || [attributes hasPrefix:@"Ts"] || [attributes hasPrefix:@"TS"] || [attributes hasPrefix:@"TB"] || [attributes hasPrefix:@"T@\"NSNumber\""]) {
            type = ig_sql_integer;
        }

        if (type) {
            [dict setObject:type forKey:key];
        } else {
            debugLog(@"---%@----",[NSString stringWithFormat:@"不支持的属性:key = %@, attributes = %@", key, attributes]);
        }
    }

    free(propertyList);

    return dict;
}

// 根据keys获取到model里面的所有values
- (NSArray *)getValuesFromModel:(id _Nonnull)model columns:(NSArray *)columns {
    NSMutableArray *array = [NSMutableArray array];
    for (NSString *column in columns) {
        id value = [model valueForKey:column];
        [array addObject:value ? : @""];
    }
    return array;
}


// 加锁
void IGLock(dispatch_semaphore_t semaphore) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

// 解锁
void IGUnLock(dispatch_semaphore_t semaphore) {
    dispatch_semaphore_signal(semaphore);
}


@end

IGParameters.h 文件

#import 

NS_ASSUME_NONNULL_BEGIN

// 参数相关的关系
typedef NS_ENUM(NSUInteger, IGParametersRelationType) {
    IGParametersRelationTypeEqualTo,               // 数学运算@"=",等于
    IGParametersRelationTypeUnequalTo,             // 数学运算@"!=",不等于
    IGParametersRelationTypeGreaterThan,           // 数学运算@">",大于
    IGParametersRelationTypeGreaterThanOrEqualTo,  // 数学运算@">=",大于等于
    IGParametersRelationTypeLessThan,              // 数学运算@"<",小于
    IGParametersRelationTypeLessThanOrEqualTo,     // 数学运算@"<=",小于等于
    IGParametersRelationTypeLike,                  // 字符串运算@"like",模糊查询   这个又有左右模糊之分
};

// 排序顺序
typedef NS_ENUM(NSUInteger, IGParametersOrderType) {
    IGParametersOrderTypeAsc,                      // 升序
    IGParametersOrderTypeDesc,                     // 降序
};



@interface IGParameters : NSObject

#pragma mark - sql语句当中为where之后的条件增加参数

/**
 *  筛选条件的数量限制
 */
@property (nonatomic, assign) NSInteger limitCount;

/**
 *  and(&&,与)操作
 *  @param column           数据库中表的key值
 *  @param value            column值对应的value值
 *  @param relationType     column与value之间的关系
 *  比如只执行[andWhere:@"age" value:18 relationType:IGParametersRelationTypeGreaterThan],那么where后面的参数会变成"age > 18"
 */
- (void)andWhere:(NSString * _Nonnull)column value:(id _Nonnull)value relationType:(IGParametersRelationType)relationType;


/**
 *  or(||,或)操作
 *  @param column           数据库中表的key值
 *  @param value            column值对应的value值
 *  @param relationType     column与value之间的关系
 */
- (void)orWhere:(NSString * _Nonnull)column value:(id _Nonnull)value relationType:(IGParametersRelationType)relationType;


/**
 *  设置排序结果
 *  @param column           排序的字段
 *  @param orderType        排序选择,有升序和降序
 *  比如执行[ orderByColumn:@"Id" orderType:IGParametersOrderTypeAsc],那么对应的sql语句就是@"order by Id asc",意思就是根据"Id"来进行升序排列
 */
- (void)orderByColumn:(NSString * _Nonnull)column orderType:(IGParametersOrderType)orderType;


/**
 *  sql语句的参数,也就是sql语句当中,where之后的参数.
 *  值得一提的是,如果设置了这个参数,那么在属性whereParameters上面的方法都无效
 *  如果不设置这个参数,那么调用此属性的get方法则会获取到以上的方法所形成的sql语句
 */
@property (nonatomic, copy)   NSString *whereParameters;










@end

NS_ASSUME_NONNULL_END

IGParameters.m 文件

#import "IGParameters.h"

@interface IGParameters ()

@property (nonatomic, strong) NSMutableArray *andParameters;    // and参数
@property (nonatomic, strong) NSMutableArray *orParameters;     // or参数
@property (nonatomic, copy)   NSString *orderString;                        // 排序语句


@end

@implementation IGParameters

- (NSMutableArray *)andParameters {
    if (!_andParameters) {
        _andParameters = [NSMutableArray array];
    }
    
    return _andParameters;
}

- (NSMutableArray *)orParameters {
    if (!_orParameters) {
        _orParameters = [NSMutableArray array];
    }
    
    return _orParameters;
}

- (NSString *)whereParameters {
    if (_whereParameters) {
        return _whereParameters;
    } else {
        NSMutableString *string = [NSMutableString string];
        NSString *andString = [self.andParameters componentsJoinedByString:@" and "];
        NSString *orString  = [self.orParameters componentsJoinedByString:@" or "];
        if (andString && andString.length > 0) {
            [string appendFormat:@"%@", andString];
        }
        
        if (orString && orString.length > 0) {
            [string appendFormat:@"%@%@", (string.length > 0 ? @" or " : @""), orString];
        }
        
        if (self.orderString) {
            [string appendFormat:@" %@", self.orderString];
        }
        
        if (self.limitCount > 0) {
            [string appendFormat:@" limit %ld", (long)self.limitCount];
        }
        
        return (NSString *)(string.length > 0 ? string : nil);
    }
}






/**
 *  and(&&,与)操作
 */
- (void)andWhere:(NSString * _Nonnull)column value:(id _Nonnull)value relationType:(IGParametersRelationType)relationType {
    
    if ([value isKindOfClass:[NSString class]] && relationType != IGParametersRelationTypeLike) {
        
        value = [NSString stringWithFormat:@"'%@'",value]; //如果是字符串, 外面会加 单引号
    }
    
    
    NSString *string = nil;
    switch (relationType) {
        case IGParametersRelationTypeEqualTo:
            string = [NSString stringWithFormat:@"%@ = %@", column, value];
            break;
        case IGParametersRelationTypeUnequalTo:
            string = [NSString stringWithFormat:@"%@ != %@", column, value];
            break;
        case IGParametersRelationTypeGreaterThan:
            string = [NSString stringWithFormat:@"%@ > %@", column, value];
            break;
        case IGParametersRelationTypeGreaterThanOrEqualTo:
            string = [NSString stringWithFormat:@"%@ >= %@", column, value];
            break;
        case IGParametersRelationTypeLessThan:
            string = [NSString stringWithFormat:@"%@ < %@", column, value];
            break;
        case IGParametersRelationTypeLessThanOrEqualTo:
            string = [NSString stringWithFormat:@"%@ <= %@", column, value];
            break;
        case IGParametersRelationTypeLike:
        {
            //左右都要模糊 %%%@%%
            //左模糊 %%%@
            //右模糊 %@%%
            string = [NSString stringWithFormat:@"%@ like '%@%%' ", column, value];
        }
            break;
        default:
            break;
    }
    if (string) {
        [self.andParameters addObject:string];
    }
}




/**
 *  or(||,或)操作
 */
- (void)orWhere:(NSString * _Nonnull)column value:(id _Nonnull)value relationType:(IGParametersRelationType)relationType {
    
    NSString *string = nil;
    switch (relationType) {
        case IGParametersRelationTypeEqualTo:
            string = [NSString stringWithFormat:@"%@ = %@", column, value];
            break;
        case IGParametersRelationTypeUnequalTo:
            string = [NSString stringWithFormat:@"%@ != %@", column, value];
            break;
        case IGParametersRelationTypeGreaterThan:
            string = [NSString stringWithFormat:@"%@ > %@", column, value];
            break;
        case IGParametersRelationTypeGreaterThanOrEqualTo:
            string = [NSString stringWithFormat:@"%@ >= %@", column, value];
            break;
        case IGParametersRelationTypeLessThan:
            string = [NSString stringWithFormat:@"%@ < %@", column, value];
            break;
        case IGParametersRelationTypeLessThanOrEqualTo:
            string = [NSString stringWithFormat:@"%@ <= %@", column, value];
            break;
        default:
            break;
    }
    if (string) {
        [self.orParameters addObject:string];
    }
}




/**
 *  设置排序结果
 */
- (void)orderByColumn:(NSString * _Nonnull)column orderType:(IGParametersOrderType)orderType {
    
    if (orderType == IGParametersOrderTypeAsc) {
        self.orderString = [NSString stringWithFormat:@"order by %@ asc", column];
    } else if (orderType == IGParametersOrderTypeDesc) {
        self.orderString = [NSString stringWithFormat:@"order by %@ desc", column];
    }
}

@end

你可能感兴趣的:(iOS 本地数据库持久化存储 (包含版本升级和切换账号的问题))