BNCSQLite
围绕 SQLite 的Objective-C 包装器。受 CTPersistance 启发。
安装
BNCSQLite 支持多种方法在项目中安装库
使用 CocoaPods 安装
CocoaPods 是 Objective-C 的依赖管理器,它自动简化了在项目中使用第三方库(如 BNCSQLite)的过程。您可以使用以下命令安装它
$ gem install cocoapods
Podfile
要使用 CocoaPods 将 BNCSQLite 集成到您的 Xcode 项目中,请在 Podfile 中指定它
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.0'
target 'TargetName' do
pod 'BNCSQLite', '~> 1.3.7'
end
然后,运行以下命令
$ pod install
使用 Carthage 安装
Carthage 是一个去中心化的依赖管理器,它会编译你的依赖并提供二进制框架。
您可以使用以下命令使用 Homebrew 安装 Carthage:
$ brew update
$ brew install carthage
要使用 Carthage 在 Xcode 项目中集成 BNCSQLite,请在 Cartfile 中指定它。
github "BigNerdCoding/BNCSQLite" ~> 1.1.0
运行 carthage 以构建框架,并将构建的 BNCSQLite.framework 拖动到您的 Xcode 项目中。
使用说明
持久化文件声明
首先,在正式创建具体的数据表之前,我们需要新建持久化文件。
// BNCSQLiteTestDatabase.h
#import <Foundation/Foundation.h>
#import <BNCSQLite/BNCSQLiteDatabaseInfoProtocol.h>
@interface BNCSQLiteTestDatabase : NSObject <BNCSQLiteDatabaseInfoProtocol>
@end
// BNCSQLiteTestDatabase.m
#import "BNCSQLiteTestDatabase.h"
@implementation BNCSQLiteTestDatabase
#pragma mark - BNCSQLiteDatabaseInfoProtocol
- (NSString *)databaseFilePath {
return [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"BNCSQLiteORMTest.sqlite"];
}
@end
在此处,我们新建了一个遵循 BNCSQLiteDatabaseInfoProtocol 协议的 BNCSQLiteTestDatabase 类,该类表示一个持久化文件。它的 databaseFilePath 方法指明了持久化文件的路径。如果需要创建内存数据库,则在该 databaseFilePath 中返回 kBNCSQLiteMemoryModePath。
结构化数据声明和定义
在此处,我们根据业务需求实现相关的结构化数据。需要注意的是,为了后续通过 BNCSQLiteTable 中的 API 进行数据库操作,避免手动编写 SQL 语句的繁琐工作。这里所有的结构化数据都必须有一个 NSNumber 类型的属性,该属性代表数据库对应表中自增的主键字段。
#import <Foundation/Foundation.h>
#import <BNCSQLite/BNCSQLiteRecord.h>
@interface BNCSQLiteTestRecord : BNCSQLiteRecord
@property(nonatomic, strong) NSNumber *rowID;
@property(nonatomic, strong) NSString *name;
@property(nonatomic, assign) NSInteger age;
@property(nonatomic, assign) BOOL isCelebrity;
@property(nonatomic, strong) NSData *bolbData;
@property(nonatomic, assign) double progress;
@property(nonatomic, strong) NSString *nilText;
@property(nonatomic, strong) NSString *defaultText;
@property(nonatomic, assign) NSInteger defaultInt;
@property(nonatomic, assign) double defaultReal;
@property(nonatomic, assign) long long timeStamp;
@end
数据表声明
当定义好业务需求的结构化数据模型之后,我们需要定义一个与其相对应的数据库表结构。后续的数据库操作均基于该类实现。
// BNCSQLiteTestTable.h
#import <Foundation/Foundation.h>
#import <BNCSQLite/BNCSQLiteTable.h>
@interface BNCSQLiteTestTable : BNCSQLiteTable
@end
// BNCSQLiteTestTable.m
#import "BNCSQLiteTestTable.h"
#import "BNCSQLiteTestRecord.h"
#import "BNCSQLiteTestDatabase.h"
#import <BNCSQLite/BNCSQLiteTableColumn.h>
#import <BNCSQLite/BNCSQLiteTableColumnIndex.h>
@implementation BNCSQLiteTestTable
#pragma mark - BNCSQLiteTableProtocol
- (id<BNCSQLiteDatabaseInfoProtocol>)databaseInfo {
return [[BNCSQLiteTestDatabase alloc] init];
}
- (NSString *)tableName {
return @"test_table";
}
- (NSArray< id<BNCSQLiteTableColumnProtocol> > *)columnInfo {
BNCSQLiteTableColumn *column = [BNCSQLiteTableColumn primaryRowIDColWithName:@"rowID"];
BNCSQLiteTableColumn *column2 = [BNCSQLiteTableColumn notNullColWithName:@"name" type:BNCSQLiteTableColumnTypeText];
BNCSQLiteTableColumn *column3 = [BNCSQLiteTableColumn notNullColWithName:@"age" type:BNCSQLiteTableColumnTypeInt];
BNCSQLiteTableColumn *column4 = [BNCSQLiteTableColumn intColWithName:@"isCelebrity"];
BNCSQLiteTableColumn *column5 = [BNCSQLiteTableColumn binaryColWithName:@"bolbData"];
BNCSQLiteTableColumn *column6 = [BNCSQLiteTableColumn realColName:@"progress"];
BNCSQLiteTableColumn *column7 = [BNCSQLiteTableColumn textColName:@"nilText"];
BNCSQLiteTableColumn *column8 = [BNCSQLiteTableColumn textColName:@"defaultText" constraint:^(BNCSQLiteTableColumn *column) {
[column settingDefaultValueConstraint:@" '' "];
}];
BNCSQLiteTableColumn *column9 = [BNCSQLiteTableColumn intColWithName:@"defaultInt" constraint:^(BNCSQLiteTableColumn *column) {
[column settingDefaultValueConstraint:@" 0 "];
}];
BNCSQLiteTableColumn *column10 = [BNCSQLiteTableColumn realColName:@"defaultReal" constraint:^(BNCSQLiteTableColumn *column) {
[column settingDefaultValueConstraint:@" 0.0 "];
}];
BNCSQLiteTableColumn *column11 = [BNCSQLiteTableColumn initUniqueColWithName:@"timeStamp" type:BNCSQLiteTableColumnTypeInt];
return @[column, column2, column3, column4, column5, column6, column7, column8, column9, column10, column11];
}
- (Class)recordClass {
return [BNCSQLiteTestRecord class];
}
- (NSString *)primaryKeyName {
return @"rowID";
}
- (NSArray< id<BNCSQLiteTableColumnIndexProtocol> > *)indexList {
BNCSQLiteTableColumnIndex *index = [[BNCSQLiteTableColumnIndex alloc] initWithIndexName:@"index_test" fields:@[@"age",@"timeStamp"]];
return @[index];
}
@end
在 BNCSQLiteTestTable.m 文件中,我们实现了 BNCSQLiteTableProtocol 协议中的相关方法。其中,databaseInfo 函数指明了该数据表属于哪一个数据库,tableName 函数返回值是该数据表的表名,columnInfo 返回值是该表所对应的列及其数据类型和相关约束(与 BNCSQLiteTestRecord 的属性相对应),recordClass 函数指明了该数据表对应的结构化数据类型,primaryKeyName 返回值则表示该数据表对应的自增主键名,indexList 则是该数据表对应的索引信息。
CRUD 操作
相关的数据表和结构化数据模型创建好之后,下面就是我们常用的 CRUD 操作了。
新增数据库记录:
BNCSQLiteTestTable *table = [[BNCSQLiteTestTable alloc] init];
// 新增一条记录
NSError *error = nil;
BNCSQLiteTestRecord *record = [[BNCSQLiteTestRecord alloc] init];
record.name = @"";
record.age = 1;
record.timeStamp = 1000;
BOOL isSuccess = [table insertRecord:record error:&error];
// 新增一组记录
NSMutableArray *arr = [NSMutableArray array];
for (NSInteger index = 0; index < 10; index++) {
BNCSQLiteTestRecord *record = [[BNCSQLiteTestRecord alloc] init];
record.name = [NSString stringWithFormat:@"testName_%ld",(long)index];
record.age = index;
record.timeStamp = 1000;
[arr addObject:record];
}
isSuccess = [table insertRecordList:arr error:&error];
查询数据库记录
无条件查询:
BNCSQLiteTestTable *table = [[BNCSQLiteTestTable alloc] init];
// 查询全部记录
NSError *error = nil;
NSArray *results = [table findAllWithError:&error];
// 查询全部记录但是按照某列排序
results = [table findAllWithOrder:@"timeStamp desc" error:&error];
// 查询最近一条记录
BNCSQLiteTestRecord *record = [table findLatestRecordWithError:&error];
// 查询最近几条记录
results = [table findRecordWithLimit:11 error:&error];
单个条件查询:
// 主键查询
BNCSQLiteTestTable *table = [[BNCSQLiteTestTable alloc] init];
NSError *error = nil;
BNCSQLiteTestRecord *record = [table findWithPrimaryKey:@(100) error:&error];
// 单条件等值查询
NSArray *result = [table findAllWithColumn:@"age" value:@(20) error:&error];
// 单条件等值查询 & 排序
result = [table findAllWithColumn:@"progress" value:@(0.5) orderBy:@"timeStamp desc" error:&error];
// 单条件 IN 查询
NSArray *result = [table findAllWithColumn:@"age" inValueList:@[@(1),@(3),@(5),@(7),@(9),@(11)] error:&error];
多个条件查询:
BNCSQLiteTestTable *table = [[BNCSQLiteTestTable alloc] init];
NSError *error = nil;
// 多条件查询
NSArray *results = [_table findAllWithCondition:@"progress = :progress AND timeStamp < :timeStamp" params:@{@"timeStamp":@(1005),@"progress":@"0.5"} error:&error];
// 多条件查询 & 排序
results = [_table findAllWithCondition:@"progress = :progress AND timeStamp < :endTimeStamp" params:@{@"endTimeStamp":@(1005),@"progress":@"0.5"} orderBy:@"age desc" error:&error];
无需 bind 的条件查询:
在上面的但条件和多条件查询操作中,为了得到最终的 where 子句,我们都需要执行形如 sqlite3_bind_int64 这类操作将对应的 value 值绑定到特定位置上。如果我们已经明确知道 where 子句条件的话,则可以调用下列方法避免不必要的 bind 过程:
BNCSQLiteTestTable *table = [[BNCSQLiteTestTable alloc] init];
NSError *error = nil;
// 无排序
NSArray *result = [table findAllWithWhere:@"age = 20" error:&error];
// 排序
result = [table findAllWithWhere:@"age > 4" orderBy:@"age DESC" error:&error];
// limit
result = [table findRecordWithWhere:@"age > 4" limit:1 error:&error];
// orderBy & limit
result = [table findRecordWithWhere:@"age > 4" orderBy:@"age DESC" limit:1 error:&error];
数据库升级
在日常使用中我们总会遇到数据库升级操作的需求,而 BNCSQLite 中数据库升级操作如下:
例如:BNCSQLiteMigrationTestRecord 及其对应的 BNCSQLiteMigrationTestTable 已经升级过两次,从默认的版本 1 升级到了版本 2 然后再升级到版本 3。
// 第一版
@interface BNCSQLiteMigrationTestRecord : BNCSQLiteRecord
@property(nonatomic, strong) NSNumber *rowID;
@property(nonatomic, strong) NSString *version1;
@end
// 第二版,相比第一版增加了 version2 字段
@interface BNCSQLiteMigrationTestRecord : BNCSQLiteRecord
@property(nonatomic, strong) NSNumber *rowID;
@property(nonatomic, strong) NSString *version1;
@property(nonatomic, strong) NSString *version2;
@end
// 第三版,相比第二版增加了 version3 字段
@interface BNCSQLiteMigrationTestRecord : BNCSQLiteRecord
@property(nonatomic, strong) NSNumber *rowID;
@property(nonatomic, strong) NSString *version1;
@property(nonatomic, strong) NSString *version2;
@property(nonatomic, strong) NSString *version3;
@end
那么我们需要定义这二次升级对应的步骤:
// 第二版升级操作
#import <Foundation/Foundation.h>
#import <BNCSQLite/BNCSQLiteMigrationStepProtocol.h>
@interface BNCSQLiteMigrationTestStep2 : NSObject <BNCSQLiteMigrationStepProtocol>
@end
#import "BNCSQLiteMigrationTestStep2.h"
#import <BNCSQLite/NSString+BNCSQLiteSchema.h>
#import <BNCSQLite/BNCSQLiteTableColumn.h>
@implementation BNCSQLiteMigrationTestStep2
#pragma mark - BNCSQLiteMigrationStepProtocol
- (BOOL)goUpWithAction:(BNCSQLiteDatabase *)dbConnect {
NSError *error = nil;
BNCSQLiteTableColumn *column2 = [[BNCSQLiteTableColumn alloc] initWithColName:@"version2" type:BNCSQLiteTableColumnTypeText constraint:nil];
NSString *sql = [NSString addColumn:column2 tableName:@"migration_test_table"];
return [dbConnect executeSQL:sql bind:nil rowHandle:nil error:&error];
}
@end
// 第三次升级操作
#import <Foundation/Foundation.h>
#import <BNCSQLite/BNCSQLiteMigrationStepProtocol.h>
@interface BNCSQLiteMigrationTestStep3 : NSObject <BNCSQLiteMigrationStepProtocol>
@end
#import "BNCSQLiteMigrationTestStep2.h"
#import <BNCSQLite/NSString+BNCSQLiteSchema.h>
#import <BNCSQLite/BNCSQLiteTableColumn.h>
@implementation BNCSQLiteMigrationTestStep3
#pragma mark - BNCSQLiteMigrationStepProtocol
- (BOOL)goUpWithAction:(BNCSQLiteDatabase *)dbConnect {
NSError *error = nil;
BNCSQLiteTableColumn *column3 = [[BNCSQLiteTableColumn alloc] initWithColName:@"version3" type:BNCSQLiteTableColumnTypeText constraint:nil];
NSString *sql = [NSString addColumn:column3 tableName:@"migration_test_table"];
return [dbConnect executeSQL:sql bind:nil rowHandle:nil error:&error];
}
@end
定义好每次升级对应的操作后,接下来我们需要在对应的持久化类中配置这些升级步骤:
// BNCSQLiteMigratorTest.h
#import <Foundation/Foundation.h>
#import <BNCSQLite/BNCSQLiteMigratorProtocol.h>
@interface BNCSQLiteMigratorTest : NSObject <BNCSQLiteMigratorProtocol>
@end
// BNCSQLiteMigratorTest.m
#import "BNCSQLiteMigratorTest.h"
#import "BNCSQLiteMigrationTestStep2.h"
#import "BNCSQLiteMigrationTestStep3.h"
@implementation BNCSQLiteMigratorTest
#pragma mark - BNCSQLiteMigratorProtocol
- (NSArray<NSNumber *> *)migrationVersionList {
return @[@(kBNCSQLiteInitVersion),@(2),@(3)];
}
- (NSDictionary<NSNumber *,id<BNCSQLiteMigrationStepProtocol> > *)migrationStepDictionary {
return @{@(2):[[BNCSQLiteMigrationTestStep2 alloc] init],
@(3):[[BNCSQLiteMigrationTestStep3 alloc] init]};
}
@end
//BNCSQLiteTestDatabase.m
#import "BNCSQLiteTestDatabase.h"
#import "BNCSQLiteMigratorTest.h"
@implementation BNCSQLiteTestDatabase
#pragma mark - BNCSQLiteDatabaseInfoProtocol
- (NSString *)databaseFilePath {
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject] stringByAppendingPathComponent:@"BNCSQLiteMigrationTest.sqlite"];
return filePath;
}
- (id<BNCSQLiteMigratorProtocol>)databaseMigrator {
return [[BNCSQLiteMigratorTest alloc] init];
}
@end
上面代码中,我们新建了一个升级执行管理类 BNCSQLiteMigratorTest 在里面我们配置了数据库的所有版本历史 migrationVersionList 以及每个版本对应的升级操作步骤 migrationStepDictionary 。最后我们在数据库配置类中的 databaseMigrator 协议方法里返回前面管理类的实例。
注意:数据库默认版本为 kBNCSQLiteInitVersion 即为 1 ,如果是新建数据库则会使用版本列表中最新的版本号且不会执行数据库升级操作。只有在本地已存在旧版本数据库文件时才会执行数据库升级操作。
更多操作
CRUD 操作涉及的接口非常多,详情可以去查看代码中的 API 注释。另外该部分代码配备了单元测试,使用者也可以参考单元测试中的代码了解具体的 API 使用。
授权协议
BNCSQLite 采用 MIT 授权协议发布。具体信息请参阅 授权协议。