IOS数据存储 之WCDB (一)

  • 1. WCDB 简介
    • 1.1 使用WCDB框架3大优势
    • 1.2 WCDB 的一些基础概念
      • 1.2.1 类字段绑定(ORM)
      • 1.2.2 WINQ(WCDB语言集成查询)
        • 1.2.2.1 字段映射与运算符
        • 1.2.2.2 字段组合
        • 1.2.2.3 AllProperties
        • 1.2.2.4 AnyProperty
      • 1.2.3 加密
      • 1.2.4 全局监控
        • 1.2.4.1 WCTError
        • 1.2.4.2 获取错误
      • 1.2.5 损坏修复
      • 1.2.6 性能监控
        • 1.2.6.1 操作耗时
        • 1.2.6.2 监控耗时
        • 1.2.6.3 SQL执行监控
  • 2. WCDB OC版本
    • 2.1 WCDB OC版本 增删改查(CRUD)
      • 2.1.1 增
      • 2.1.2 删
      • 2.1.3 改
      • 2.1.4 查
    • 2.2 事务 (Transaction)
  • 3. WCDB Swift版本
    • 3.1 WCDB.swift安装
    • 3.2 WCDB.swift使用
  • 4. 数据库从FMDB迁移到WCDB
    • 4.1 为什么要迁移到WCDB?
      • 4.1.1 WCDB 对比FMDB的优势一:高效
      • 4.1.2 WCDB 对比FMDB的优势一:易用
      • 4.1.3 WCDB 对比FMDB的优势一:完整
    • 4.2 FMDB迁移
      • 4.2.1 安装
      • 4.2.2 创建数据库
      • 4.2.3 打开数据库
      • 4.2.4 建表与ORM
      • 4.2.5 数据库升级
      • 4.2.6 访问数据库
        • 4.2.6.1 查询
        • 4.2.6.2 插入
        • 4.2.6.3 修改
        • 4.2.6.4 删除
      • 4.2.7 条件语句
        • 4.2.7.1 改写条件语句
          • 4.2.7.1.1 部分查询
          • 4.2.7.1.2 自增插入
          • 4.2.7.1.3 数值更新
          • 4.2.7.1.4 部分删除
      • 4.2.8 特殊语句和核心层接口
        • 4.2.8.1 执行WINQ
        • 4.2.8.2 获取WINQ运行结果
      • 4.2.9 事务
        • 4.2.9.1 便捷事务接口
      • 4.2.10 多重语句和批处理
      • 4.2.11 线程安全与并发
      • 4.2.12 配置
      • 4.2.13 关闭数据库
        • 4.2.13.1 回收对象
        • 4.2.13.2 手动关闭数据库
      • 4.2.14 隔离Objective-C++代码
        • 4.2.14.1 WCTTableCoding文件模版

1. WCDB 简介

  • WCDB是腾讯开发的,微信中使用的DB开源框架:

引用官方说法:“WCDB是一个易用、高效、完整的移动数据库框架,它基于 SQLite 和 SQLCipher 开发。”

1.1 使用WCDB框架3大优势

  • 易用性
  1. one line of code 是它坚持的原则,大多数操作只需要一行代码即可完成.
  2. 使用WINQ 语句查询,不用为拼接SQL语句而烦恼了,模型绑定映射也是按照规定模板去实现方便快捷。
  • 高效性
  1. 和fmdb做对比

  • 完整性
  1. 支持基于SQLCipher 加密
  2. 持全文搜索
  3. 支持反注入,可以避免第三方从输入框注入 SQL,进行预期之外的恶意操作。
  4. 用户不用手动管理数据库字段版本,升级方便自动.
  5. 提供数据库修复工具。

1.2 WCDB 的一些基础概念

1.2.1 类字段绑定(ORM)

  • ORM定义:

在WCDB内,ORM(Object Relational Mapping)是指

  1. 将一个ObjC的类,映射到数据库的表和索引;
  2. 将类的property,映射到数据库表的字段;
  3. 这一过程。通过ORM,可以达到直接通过Object进行数据库操作,省去拼装过程的目的。
  • WCDB通过内建的宏实现ORM的功能。如下:
//Message.h
@interface Message : NSObject@property int localID;
@property(retain) NSString *content;
@property(retain) NSDate *createTime;
@property(retain) NSDate *modifiedTime;
@property(assign) int unused; //You can only define the properties you need@end
//Message.mm
#import "Message.h"
@implementation MessageWCDB_IMPLEMENTATION(Message)
WCDB_SYNTHESIZE(Message, localID)
WCDB_SYNTHESIZE(Message, content)
WCDB_SYNTHESIZE(Message, createTime)
WCDB_SYNTHESIZE(Message, modifiedTime)WCDB_PRIMARY(Message, localID)WCDB_INDEX(Message, "_index", createTime)@end
//Message+WCTTableCoding.h
#import "Message.h"
#import <WCDB/WCDB.h>@interface Message (WCTTableCoding) <WCTTableCoding>WCDB_PROPERTY(localID)
WCDB_PROPERTY(content)
WCDB_PROPERTY(createTime)
WCDB_PROPERTY(modifiedTime)@end
  • 将一个已有的ObjC类进行ORM绑定的过程如下:
  1. 定义该类遵循WCTTableCoding协议。可以在类声明上定义,也可以通过文件模版在category内定义。
  2. 使用WCDB_PROPERTY宏在头文件声明需要绑定到数据库表的字段。
  3. 使用WCDB_IMPLEMENTATIO宏在类文件定义绑定到数据库表的类。
  4. 使用WCDB_SYNTHESIZE宏在类文件定义需要绑定到数据库表的字段。
  • 简单几行代码,就完成了将类和需要的字段绑定到数据库表的过程。这三个宏在名称和使用习惯上,也都和定义一个ObjC类相似,以此便于记忆。
  • 除此之外,WCDB还提供了许多可选的宏,用于定义数据库索引、约束等,如:

WCDB_PRIMARY用于定义主键
WCDB_INDEX用于定义索引
WCDB_UNIQUE用于定义唯一约束
WCDB_NOT_NULL用于定义非空约束

  • 定义完成后,只需要调用createTableAndIndexesOfName:withClass:接口,即可创建表和索引。
WCTDatabase *database = [[WCTDatabase alloc] initWithPath:path];
/*CREATE TABLE messsage (localID INTEGER PRIMARY KEY,content TEXT,createTime BLOB,modifiedTime BLOB)*/
BOOL result = [database createTableAndIndexesOfName:@"message"withClass:Message.class];
  • 接口会根据ORM的定义,创建对应表和索引。

1.2.2 WINQ(WCDB语言集成查询)

  • WINQ简介
  1. WINQ(WCDB Integrated Query,音’wink’),是将自然查询的SQL集成到WCDB框架中的技术,基于C++实现。
  2. 传统的SQL语句,通常是开发者拼接字符串完成。这种方式不仅繁琐、易错,而且出错后很难定位到问题所在。同时也容易给SQL注入留下可乘之机。
  3. 而WINQ将查询语言集成到了C++中,可以通过类似函数调用的方式来写SQL查询。借用IDE的代码提示和编译器的语法检查,达到易用、纠错的效果。
  4. WINQ的使用上接近于C函数调用。对于熟悉SQL的开发者,无须特别学习即可立刻上手使用.
  • WINQ原理:请参考官方文档:WINQ原理

1.2.2.1 字段映射与运算符

  • 对于一个已绑定ORM的类,可以通过className.propertyName的方式,获得数据库内字段的映射,以此书写SQL的条件、排序、过滤等等所有语句。如下是几个例子:
/*SELECT MAX(createTime), MIN(createTime)FROM messageWHERE localID>0 AND content IS NOT NULL*/
[database getObjectsOnResults:{Message.createTime.max(), Message.createTime.min()}fromTable:@"message"where:Message.localID > 0 && Message.content.isNotNull()];
/*SELECT DISTINCT localIDFROM messageORDER BY modifiedTime ASCLIMIT 10*/
[database getObjectsOnResults:Message.localID.distinct()fromTable:@"message"orderBy:Message.modifiedTime.order(WCTOrderedAscending)limit:10];
/*DELETE FROM messageWHERE localID BETWEEN 10 AND 20 OR content LIKE 'Hello%'*/
[database deleteObjectsFromtable:@"message"where:Message.local.between(10, 20) || Message.content.like("Hello%")];
  • 由于WINQ通过接口调用实现SQL查询,因此在书写过程中会有IDE的代码提示和编译器的语法检查,从而提升开发效率,避免写错。
  • WINQ的接口包括但不限于:

一元操作符:+、-、!等
二元操作符:||、&&、+、-、*、/、|、&、<<、>>、<、<=、==、!=、>、>=等
范围比较:IN、BETWEEN等
字符串匹配:LIKE、GLOB、MATCH、REGEXP等
聚合函数:AVG、COUNT、MAX、MIN、SUM等

  • 凡是SQLite支持的语法规则,WINQ基本都有其对应的接口。且接口名称与SQLite的语法规则基本保持一致。

1.2.2.2 字段组合

  • 多个字段映射可通过大括号{}进行组合,如:
/*SELECT localID, contentFROM message*/
[database getAllObjectsOnResults:{Message.localID, Message.content}fromTable:@"message"];
/*SELECT *FROM messageORDER BY createTime ASC, localID DESC*/
[database getObjectsOfClass:Message.class fromTable:@"message" orderBy:{Message.createTime.order(WCTOrderedAscending),  Message.localID.order(WCTOrderedDescending)}];

1.2.2.3 AllProperties

  • 在上述的组合中,大括号{}的语法实质是C++中列表std::list的隐式初始化。而className.AllProperties则用于获取类定义的所有字段映射的列表,如:
/*SELECT localID, content, createTime, modifiedTimeFROM message
*/
[database getAllObjectsOnResults:Message.AllPropertiesfromTable:@"message"];

1.2.2.4 AnyProperty

  • className.AnyProperty用于指代SQL中的*,如:
/*SELECT count(*)FROM message*/[database getOneValueOnResult:Message.AnyProperty.count()fromTable:@"message"];

1.2.3 加密

  • WCDB提供基于sqlcipher的数据库加密功能,如下:
WCTDatabase *database = [[WCTDatabase alloc] initWithPath:path];
NSData *password = [@"MyPassword" dataUsingEncoding:NSASCIIStringEncoding];
[database setCipherKey:password];

1.2.4 全局监控

  • WCDB提供了对错误和性能的全局监控,可用于调试错误和性能。

  • WCDB可以对所有错误进行统一的监控,也可以获取某个特定操作的错误信息。所有错误都以WCTError的形式出现。

1.2.4.1 WCTError

  • WCTError继承自NSError,包含了WCDB错误的所有信息,以供调试或发现问题。
    type表示错误的类型,不同类型的错误其错误码和拥有的信息不同。其对应关系如下
type 描述 相关参考代码
SQLite 表示该错误来自SQLite接口 请参考rescode
SystemCall 表示该错误来自系统调用 请参考errno
Core 表示该错误来自WCDB Core层 请参考源码的error.hpp
Interface 表示该错误来自WCDB Interface层 请参考源码的error.hpp
Abort 表示中断,该错误一般是开发错误,应该在发布前修复
Warning 表示警告,建议修复
SQLiteGlobal 表示该信息来自SQLite的log接口,一般只作为debug log 请参考rescode
  • 其他错误信息通过infoForKey接口获得,包括:
type 描述 相关参考代码
Tag 正在操作的数据库的tag
Operation 正在进行的操作 请参考源码的error.hpp
Extended Code SQLite的扩展码 请参考rescode
Message 错误信息
SQL 发生错误时正在执行的SQL
Path 发生错误时正在操作的文件的路径

1.2.4.2 获取错误

  • 由于便捷接口的设计原则是易用,因此不提供获取错误的方式。错误处理需使用链式接口
WCTSelect *select = [database prepareSelectObjectsOfClass:Message.classfromTable:@"message"];
NSArray<Message *> *objects = [[[select where:Message.localID > 0] orderBy:Message.createTime.order()] limit:10].allObjects;
WCTError *error = select.error;
  • 开发者也可以注册全局的错误接口,以调试、上报、打log, 代码如下:
//Error Monitor
[WCTStatistics SetGlobalErrorReport:^(WCTError *error) {NSLog(@"[WCDB]%@", error);
}];
//Performance Monitor
[WCTStatistics SetGlobalPerformanceTrace:^(WCTTag tag, NSDictionary<NSString *, NSNumber *> *sqls, NSInteger cost) {NSLog(@"Database with tag:%d", tag);NSLog(@"Run :");[sqls enumerateKeysAndObjectsUsingBlock:^(NSString *sqls, NSNumber *count, BOOL *) {NSLog(@"SQL %@ %@ times", sqls, count);}];NSLog(@"Total cost %lld nanoseconds", cost);
}];
//SQL Execution Monitor
[WCTStatistics SetGlobalSQLTrace:^(NSString *sql) {NSLog(@"SQL: %@", sql);
}];

1.2.5 损坏修复

  • WCDB内建了修复工具,以应对数据库损坏,无法使用的情况。
  • 开发者需要在数据库未损坏时,对数据库元信息定时进行备份,如下:
NSData *backupPassword = [@"MyBackupPassword" dataUsingEncoding:NSASCIIStringEncoding];
[database backupWithCipher:backupPassword];
  • 当检测到数据库损坏,即WCTErrortypeWCTErrorTypeSQLitecode为11或26(SQLITE_CORRUPTSQLITE_NOTADB)时,可以进行修复。
//Since recovering is a long time operation, you'd better call it in sub-thread.
[view startLoading];
dispatch_async(DISPATCH_QUEUE_PRIORITY_BACKGROUND, ^{WCTDatabase *recover = [[WCTDatabase alloc] initWithPath:recoverPath];NSData *password = [@"MyPassword" dataUsingEncoding:NSASCIIStringEncoding];NSData *backupPassword = [@"MyBackupPassword" dataUsingEncoding:NSASCIIStringEncoding];int pageSize = 4096;//Default to 4096 on iOS and 1024 on macOS.[database close:^{[recover recoverFromPath:path withPageSize:pageSize backupCipher:cipher databaseCipher:password];}];[view stopLoading];
});

1.2.6 性能监控

  • WCDB支持获取单次操作的耗时,也支持对单个DB或全局注册统一接口监控性能。
  • 所有性能监控都会有少量的性能损坏,请根据需求开启.

1.2.6.1 操作耗时

  • 由于便捷接口的设计原则是易用,因此不提供获取错误的方式。操作耗时需使用链式接口。

  • 首先安通过setStatisticsEnabled:打开耗时监控

WCTSelect *select = [database prepareSelectObjectsOfClass:Message.classfromTable:@"message"];
[select setStatisticsEnabled:YES];//You should call this before all other operations
  • 在操作执行完成后,通过cost接口获取耗时
NSArray<Message *> *objects = [[[select where:Message.localID > 0] orderBy:Message.createTime.order()] limit:10].allObjects;
NSLog(@"%f", select.cost);//You should call this after all other operations

1.2.6.2 监控耗时

  • WCDB支持对所有SQL操作进行全局监控,也支持监控单个特定的数据库.

所有监控的返回数据都相同,包括三个数据:

  1. Tag,执行操作的数据库的tag
  2. sqls,执行的SQL和对应的次数。
    对于非事务操作,则为单条SQL
    对于事务操作,则为该次事务所执行的所有SQL和每个sql执行的次数
  3. cost,耗时
  • 全局监控:

    监控所有db的数据库操作耗时,该接口需要在所有db打开、操作之前调用。

[WCTStatistics SetGlobalTrace:^(WCTTag tag, NSDictionary<NSString *, NSNumber *> *sqls, NSInteger cost) {NSLog(@"Tag: %d", tag);[sqls enumerateKeysAndObjectsUsingBlock:^(NSString *sql, NSNumber *count, BOOL *) {NSLog(@"SQL: %@ Count: %d", sql, count.intValue);}];NSLog(@"Total cost %ld nanoseconds", (long) cost);
}];
  • 特定数据库监控

对于特定的数据库,该接口会覆盖全局监控的注册。

[db setTrace:^(WCTTag tag, NSDictionary<NSString *, NSNumber *> *sqls, NSInteger cost) {NSLog(@"Tag: %d", tag);[sqls enumerateKeysAndObjectsUsingBlock:^(NSString *sql, NSNumber *count, BOOL *) {NSLog(@"SQL: %@ Count: %d", sql, count.intValue);}];NSLog(@"Total cost %ld nanoseconds", (long) cost);
}];
  • 操作耗时监控耗时的区别
  1. 操作耗时cost返回的耗时为浮点数的秒,监控耗时的cost返回的耗时为整型的纳秒。
  2. 监控耗时仅包括SQL在SQLite层面的耗时,包括SQL的编译、I/O等。而操作耗时除以上之外,还包括了WCDB层面对类封装等产生的耗时

1.2.6.3 SQL执行监控

  • WCDB可以监控所有SQL的执行,以确定代码符合预期
//SQL Execution Monitor
[WCTStatistics SetGlobalSQLTrace:^(NSString *sql) {NSLog(@"SQL: %@", sql);
}];

2. WCDB OC版本

  • 得益于ORM的定义,WCDB可以直接进行通过object进行增删改查(CRUD)操作。开发者可以通过WCTDatabaseWCTTable两个类进行一般的增删改查操作。

2.1 WCDB OC版本 增删改查(CRUD)

2.1.1 增

//插入
Message *message = [[Message alloc] init];
message.localID = 1;
message.content = @"Hello, WCDB!";
message.createTime = [NSDate date];
message.modifiedTime = [NSDate date];
/*INSERT INTO message(localID, content, createTime, modifiedTime) VALUES(1, "Hello, WCDB!", 1496396165, 1496396165);*/
BOOL result = [database insertObject:messageinto:@"message"];

2.1.2 删

//删除
//DELETE FROM message WHERE localID>0;
BOOL result = [database deleteObjectsFromTable:@"message"where:Message.localID > 0];

2.1.3 改

//修改
//UPDATE message SET content="Hello, Wechat!";
Message *message = [[Message alloc] init];
message.content = @"Hello, Wechat!";
BOOL result = [database updateRowsInTable:@"message"onProperties:Message.contentwithObject:message];

2.1.4 查

//查询
//SELECT * FROM message ORDER BY localID
NSArray<Message *> *message = [database getObjectsOfClass:Message.classfromTable:@"message"                                                         orderBy:Message.localID.order()];
  • WCTTable相当于预设了表名和类名的WCTDatabase对象,接口和WCTDatabase基本一致。
WCTTable *table = [database getTableOfName:@"message"withClass:Message.class];
//查询
//SELECT * FROM message ORDER BY localID
NSArray<Message *> *message = [table getObjectsOrderBy:Message.localID.order()];

2.2 事务 (Transaction)

  • WCDB内可通过两种方式执行事务,一是runTransaction:接口,如下:
BOOL commited = [database runTransaction:^BOOL {[database insertObject:message into:@"message"];return YES; //return YES to commit transaction and return NO to rollback transaction.
}];
  • 这种方式要求数据库操作在一个BLOCK内完成,简单易用。
  • 另一种方式则是获取WCTTransaction对象,如下:
WCTTransaction *transaction = [database getTransaction];
BOOL result = [transaction begin];
[transaction insertObject:message into:@"message"];
result = [transaction commit];
if (!result) {[transaction rollback];NSLog(@"%@", [transaction error]);
}
  • WCTTransaction对象可以在类或函数间传递,因此这种方式也更具灵活性。

3. WCDB Swift版本

3.1 WCDB.swift安装

  • 安装要求:

Swift 4.0 及以上
Xcode 9.0 及以上

  • 腾讯官方文档:WCDB.swift安装与兼容性
  • pod 安装:
 pod 'WCDB.swift'

3.2 WCDB.swift使用

  • 模型绑定,直接用wcdb提供的模板
class Sample: TableCodable {var identifier: Int? = nilvar description: String? = nilenum CodingKeys: String, CodingTableKey {typealias Root = Samplestatic let objectRelationalMapping = TableBinding(CodingKeys.self)case identifiercase description}
}
  • 数据库创建以及操作单独写了个单例类 HMDataBaseManager.swift
import Foundation
import WCDBSwiftstruct HMDataBasePath {let dbPath = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask,true).last! + "/HMDB/HMDB.db"
}class HMDataBaseManager: NSObject {static let share = HMDataBaseManager()let dataBasePath = URL(fileURLWithPath: HMDataBasePath().dbPath)var dataBase: Database?private override init() {super.init()dataBase = createDb()}///创建dbprivate func createDb() -> Database {debugPrint("数据库路径==\(dataBasePath.absoluteString)")return Database(withFileURL: dataBasePath)}///创建表func createTable<T: TableDecodable>(table: String, of ttype:T.Type) -> Void {do {try dataBase?.create(table: table, of:ttype)} catch let error {debugPrint("create table error \(error.localizedDescription)")}}///插入func insertToDb<T: TableEncodable>(objects: [T] ,intoTable table: String) -> Void {do {try dataBase?.insert(objects: objects, intoTable: table)} catch let error {debugPrint(" insert obj error \(error.localizedDescription)")}}///修改func updateToDb<T: TableEncodable>(table: String, on propertys:[PropertyConvertible],with object:T,where condition: Condition? = nil) -> Void{do {try dataBase?.update(table: table, on: propertys, with: object,where: condition)} catch let error {debugPrint(" update obj error \(error.localizedDescription)")}}///删除func deleteFromDb(fromTable: String, where condition: Condition? = nil) -> Void {do {try dataBase?.delete(fromTable: fromTable, where:condition)} catch let error {debugPrint("delete error \(error.localizedDescription)")}}///查询func qureyFromDb<T: TableDecodable>(fromTable: String, cls cName: T.Type, where condition: Condition? = nil, orderBy orderList:[OrderBy]? = nil) -> [T]? {do {let allObjects: [T] = try (dataBase?.getObjects(fromTable: fromTable, where:condition, orderBy:orderList))!debugPrint("\(allObjects)");return allObjects} catch let error {debugPrint("no data find \(error.localizedDescription)")}return nil}///删除数据表func dropTable(table: String) -> Void {do {try dataBase?.drop(table: table)} catch let error {debugPrint("drop table error \(error)")}}/// 删除所有与该数据库相关的文件func removeDbFile() -> Void {do {try dataBase?.close(onClosed: {try dataBase?.removeFiles()})} catch let error {debugPrint("not close db \(error)")}}
}
  • 比较复杂的查询可以使用 prepareSelect 查询接口
//查询所有站点并按字母排序去重func qureyAllStations(cityId: Int) -> [StationModel]{var stationArray = [StationModel]()do {let selectPrep = try HMDataBaseManager.share.dataBase?.prepareSelect(on: StationModel.Properties.all, fromTable: String(describing: StationModel.self)).where(StationModel.Properties.cityid == cityId).group(by: StationModel.Properties.statid).order(by: StationModel.Properties.statpname.asOrder(by: .ascending))stationArray = try selectPrep?.allObjects() ?? []} catch let error {debugPrint("\(error)")}return stationArray}

4. 数据库从FMDB迁移到WCDB

4.1 为什么要迁移到WCDB?

  1. WCDB依托于微信上亿用户的实际场景,解决了许多在开发和线上遇到的共性问题,在性能、易用性、功能完整性以及兼容性上都有较好的表现。并且,开发者可以平滑地从FMDB升级到WCDB。
  2. WCDB有三大优势:更高效,更易用,功能更完整,具体优势比较上面已经对比过了。

4.1.1 WCDB 对比FMDB的优势一:高效

  • WCDB在并发、ORM以及SQLite源码都做了许多针对性的优化,使得在写入、多线程并发、初始化等方面比FMDB有30%-280%的性能提升。

4.1.2 WCDB 对比FMDB的优势一:易用

  • WCDB通过WINQ和ORM,使得从拼接SQL、获取数据、拼装Object的整个过程,只需要一行代码即可完成。

  • FMDB代码,实现一个查询需要一堆胶水代码:

/*FMDB Code*/
FMResultSet *resultSet = [fmdb executeQuery:@"SELECT * FROM message"];
NSMutableArray<Message *> *messages = [[NSMutableArray alloc] init];
while ([resultSet next]) {Message *message = [[Message alloc] init];message.localID = [resultSet intForColumnIndex:0];message.content = [resultSet stringForColumnIndex:1];message.createTime = [NSDate dateWithTimeIntervalSince1970:[resultSet doubleForColumnIndex:2]];message.modifiedTime = [NSDate dateWithTimeIntervalSince1970:[resultSet doubleForColumnIndex:3]];[messages addObject:message];
}
  • WCDB只需要一行代码:
/*WCDB Code*/
NSArray<Message *> *messages = [wcdb getAllObjectsOfClass:Message.class fromTable:@"message"];

4.1.3 WCDB 对比FMDB的优势一:完整

  • FMDB只是简单对Sqlite3的封装,没有提供错误统计,性能统计,损坏修复,反注入,加密等功能。
  • 相反WCDB提供了一套完整的功能。

错误统计
性能统计
损坏修复
反注入
加密

4.2 FMDB迁移

4.2.1 安装

  • 首先在工程的配置Build Phases->Link Binary With Libraries中,将FMDB以及SQLite的库移出工程。
  • 然后参考安装教程选择适合方式链入WCDB的库。

4.2.2 创建数据库

  • WCTDatabase通过指定路径进行创建。同时,该接口会自动创建路径中未创建的目录。
NSString* path = @"intermediate/directory/will/be/created/automatically/wcdb";
WCTDatabase* wcdb = [[WCTDatabase alloc] initWithPath:path];
  • 临时数据库可以创建在iOS/macOS的临时目录上。
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:@"tmp.db"];
WCTDatabase* wcdb = [[WCTDatabase alloc] initWithPath:path];
  • WCDB暂不支持创建内存数据库。由于移动平台的磁盘介质大多为SSD,其性能与纯内存操作差别不大。同时内存数据库会占用大量内存,从而导致FOOM。

4.2.3 打开数据库

  • WCDB会在第一次访问数据库时,自动打开数据库,不需要开发者主动操作。
  • canOpen接口可用于测试数据库能否正常打开,isOpened接口可用于测试数据库是否已打开。
if (![wcdb canOpen]) {NSLog(@"open failed");
}if ([wcdb isOpened]) {NSLog(@"database is already opened");
}

4.2.4 建表与ORM

  • FMDB不支持ORM,而WCDB可以通过绑定类与表绑定起来,从而大幅度减少代码量。
  • 对于在FMDB已经定义的类:
//Message.h
@interface Message : NSObject@property int localID;
@property(retain) NSString *content;
@property(retain) NSDate *createTime;
@property(retain) NSDate *modifiedTime;@end
  • 对于在FMDB已经定义的表:
FMDatabase* fmdb = [[FMDatabase alloc] initWithPath:path];
[fmdb executeUpdate:@"CREATE TABLE message(localID INTEGER PRIMARY KEY AUTOINCREMENT, content TEXT, createTime INTEGER, db_modifiedTime INTEGER)"];
[fmdb executeUpdate:@"CREATE INDEX message_index ON message(createTime)"];
  • 可以将其建模为:
//Message.h
@interface Message : NSObject <WCTTableCoding>@property int localID;
@property(retain) NSString *content;
@property(retain) NSDate *createTime;
@property(retain) NSDate *modifiedTime;WCDB_PROPERTY(localID)
WCDB_PROPERTY(content)
WCDB_PROPERTY(createTime)
WCDB_PROPERTY(modifiedTime)@end//Message.mm
@implementation MessageWCDB_IMPLEMENTATION(Message)
WCDB_SYNTHESIZE(Message, localID)
WCDB_SYNTHESIZE(Message, content)
WCDB_SYNTHESIZE(Message, createTime)
WCDB_SYNTHESIZE_COLUMN(Message, modifiedTime, "db_modifiedTime")WCDB_PRIMARY_AUTO_INCREMENT(Message, localID)
WCDB_INDEX(Message, "_index", createTime)@end

其中:

  1. WCDB_IMPLEMENTATION(className)用于定义进行绑定的类
  2. WCDB_PROPERTY(propertyName)WCDB_SYNTHESIZE(className, propertyName)用于声明和定义字段。
  3. WCDB_SYNTHESIZE(className, propertyName)默认使用属性名作为数据库表的字段名。对于属性名与字段名不同的情况,可以使用WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName)进行映射。
    对于在FMDB已经创建的表,若属性名与字段名不同,则可以用WCDB_SYNTHESIZE_COLUMN宏进行映射,如例子中的db_modifiedTime字段
  4. WCDB_PRIMARY_AUTO_INCREMENT(className, propertyName)用于定义主键且自增。
  5. WCDB_INDEX(className, indexNameSubfix, propertyName)用于定义索引。
  • 定义完成后,调用createTableAndIndexesOfName:withClass:即可完成创建。
WCTDatabase* wcdb = [[WCTDatabase alloc] initWithPath:path];
[wcdb createTableAndIndexesOfName:@"message" withClass:Message.class]

注:该接口使用的是IF NOT EXISTS的SQL,因此可以用重复调用。不需要在每次调用前判断表或索引是否已经存在。

4.2.5 数据库升级

createTableAndIndexesOfName:withClass:会根据ORM的定义,创建表或索引。
当定义发生变化时,该接口也会对应的增加字段或索引。
因此,该接口可用于数据库表的升级。

  • 定义模型
//Message.h
@interface Message : NSObject <WCTTableCoding>@property int localID;
@property(assign) const char *newContent;
//@property(retain) NSDate *createTime;
@property(retain) NSDate *modifiedTime;
@property(retain) NSDate *newProperty;WCDB_PROPERTY(localID)
WCDB_PROPERTY(newContent)
//WCDB_PROPERTY(createTime)
WCDB_PROPERTY(modifiedTime)
WCDB_PROPERTY(newProperty)@end//Message.mm
@implementation MessageWCDB_IMPLEMENTATION(Message)
WCDB_SYNTHESIZE(Message, localID)
WCDB_SYNTHESIZE_COLUMN(Message, newContent, "content")
//WCDB_SYNTHESIZE(Message, createTime)
WCDB_SYNTHESIZE_COLUMN(Message, modifiedTime, "db_modifiedTime")
WCDB_SYNTHESIZE(Message, newProperty)WCDB_PRIMARY_AUTO_INCREMENT(Message, localID)
WCDB_INDEX(Message, "_index", createTime)
WCDB_UNIQUE(Message, modifiedTime)
WCDB_INDEX(Message, "_newIndex", newProperty)@end
  • 新建表
WCTDatabase* db = [[WCTDatabase alloc] initWithPath:path];
[db createTableAndIndexesOfName:@"message" withClass:Message.class]
  • 删除字段

如例子中的createTime字段,删除字段只需直接将ORM中的定义删除即可。

注:由于SQLite不支持删除字段,因此该操作只是将对应字段忽略。

  • 增加字段

如例子中的newProperty字段,增加字段只需直接在ORM定义出添加,并再次调用createTableAndIndexesOfName:withClass:

  • 修改字段

如例子中的newContent字段,字段类型可以直接修改,但需要确保新类型与旧类型兼容;字段名称则需要通过WCDB_SYNTHESIZE_COLUMN(className, proeprtyName, columnName)重新映射到旧字段。

注:由于SQLite不支持修改字段名,因此该操作只是将新的属性映射到原来的字段名。

  • 增加约束

如例子中的WCDB_UNIQUE(Message, modifiedTime),新的约束只需直接在ORM中添加,并再次调用createTableAndIndexesOfName:withClass:

  • 增加索引

如例子中的WCDB_INDEX(Message, "_newIndex", newProperty),新的索引只需直接在ORM添加,并再次调用createTableAndIndexesOfName:withClass:

4.2.6 访问数据库

  • 得益于ORM的定义,开发者无需使用类似intForColumnIndex:的接口手动组装Object。以下是增删查改的代码示例。

4.2.6.1 查询

  • FMDB代码
/*FMDB Code*/
FMResultSet *resultSet = [fmdb executeQuery:@"SELECT * FROM message"];
NSMutableArray<Message *> *messages = [[NSMutableArray alloc] init];
while ([resultSet next]) {Message *message = [[Message alloc] init];message.localID = [resultSet intForColumnIndex:0];message.content = [resultSet stringForColumnIndex:1];message.createTime = [NSDate dateWithTimeIntervalSince1970:[resultSet doubleForColumnIndex:2]];message.modifiedTime = [NSDate dateWithTimeIntervalSince1970:[resultSet doubleForColumnIndex:3]];[messages addObject:message];
}
  • WCDB代码
NSArray<Message *> *messages = [wcdb getAllObjectsOfClass:Message.class fromTable:@"message"];

4.2.6.2 插入

  • FMDB代码
/*FMDB Code*/
[fmdb executeUpdate:@"INSERT INTO message VALUES(?, ?, ?, ?)", @(message.localID), message.content, @(message.createTime.timeIntervalSince1970), @(message.modifiedTime.timeIntervalSince1970)];
  • WCDB代码
[wcdb insertObject:message into:@"message"];

4.2.6.3 修改

  • FMDB代码
/*FMDB Code*/
[fmdb executeUpdate:@"UPDATE message SET modifiedTime=?", @(message.modifiedTime.timeIntervalSince1970)];
  • WCDB代码
[wcdb updateAllRowsInTable:@"message" onProperties:Message.modifiedTime withObject:message];

4.2.6.4 删除

  • FMDB代码
/*FMDB Code*/
[fmdb executeUpdate:@"DELETE FROM message"];
  • WCDB代码
[wcdb deleteAllObjects];

4.2.7 条件语句

  • WCDB通过WINQ完成条件语句,以减轻了拼装SQL的繁琐,并提供一系列优化和反注入等特性。
  • 以下是SQLWINQ之间转换的一些例子。
类型 SQL示例 WINQ示例
排序 ORDER BY localID ASC Message.localID.order(WCTOrderedAscending)
多字段排序 ORDER BY localID ASC, content DESC {Message.localID.order(WCTOrderedAscending), Message.content.order(WCTOrderedDescending)}
聚合函数 MAX(localID) Message.localID.max()
条件语句 localID==2 AND content IS NOT NULL Message.localID==2&&Message.content.isNotNull()
多个字段组合 localID, content {Message.localID, Message.content}
* COUNT(*) Message.AnyProperty.count()
所有ORM定义的字段 (localID, content, createTime, modifiedTime) Message.AllProperties
指定table myTable.localID Message.localID.inTable(“myTable”)

4.2.7.1 改写条件语句

  • 了解了WINQ,就可以完成更复杂的增删查改操作了。
4.2.7.1.1 部分查询
  • FMDB代码
/*FMDB Code*/
NSMutableArray<Message *> *messages = [[NSMutableArray alloc] init];
FMResultSet* resultSet = [fmdb executeQuery:@"SELECT localID, createTime FROM message WHERE localID>=1 OR modified!=createTime"];
while (resultSet && [resultSet next]) {Message *message = [[Message alloc] init];message.localID = [resultSet intForColumnIndex:0];message.createTime = [NSDate dateWithTimeIntervalSince1970:[resultSet doubleForColumnIndex:2]];[messages addObject:message];
}
  • WCDB代码
NSArray *messages = [wcdb getObjectsOnResults:{Message.localID, Message.createTime} fromTable:@"message"where:Message.localID>1||Message.modifiedTime!=Message.createTime];
4.2.7.1.2 自增插入
  • FMDB代码
/*FMDB Code*/
[fmdb executeUpdate:@"INSERT INTO message(localID, content) VALUES(?, ?)", nil, message.content];
  • WCDB代码
message.isAutoIncrement = YES;
[wcdb insertObject:message onProperties:{Message.localID, Message.content} into:@"message"];
4.2.7.1.3 数值更新
  • FMDB代码
/*FMDB Code*/
[fmdb executeUpdate:@"UPDATE message SET modifiedTime=? WHERE localID==?", @([NSDate date].timeIntervalSince1970), @(1)];
  • WCDB代码
[wcdb updateRowsInTable:@"message" onProperty:Message.modifiedTime withValue:[NSDate date]where:Message.localID==1];
4.2.7.1.4 部分删除
  • FMDB代码
/*FMDB Code*/
[fmdb executeUpdate:@"DELETE FROM message WHERE localID>0 AND content IS NULL LIMIT ?", @(1)];
  • WCDB代码
[wcdb deleteObjectsFromTable:@"messsage" where:Message.localID>0&&Message.content!=nillimit:1];

4.2.8 特殊语句和核心层接口

  • WCDBObjC层接口封装了绝大部分场景下适用的增删查改语句。但SQL千变万化,接口层不可能覆盖全部场景。对于这种情况,可以通过WINQ的核心层接口进行调用。
  • 对于SQL:EXPLAIN QUERY PLAN CREATE TABLE message(localID INTEGER)
  1. 找到其对应的sql-stmt,然后通过以WCDB::Statement开头的类进行调用。如例子中,其对应的sql-stmt为WCDB::StatementExplain和WCDB::StatementCreateTable。
  2. 获取字段映射。对于已经定义ORM的字段,可以通过className.propertyName获取,如:Message.localID。对于未定义ORM的字段,可以通过WCDB::Column columnName(“columnName”)创建,如 WCDB::Column localID(“localID”).
  3. 根据Statement内的定义,按照与SQL同名的函数调用获得完整的WINQ语句。如例子中,其对应的WINQ语句为:
WCDB::ColumnDefList columnDefList = {WCTSampleORM.identifier.def(WCTColumnTypeInteger32, true)};
WCDB::StatementExplain statementExplain = WCDB::StatementExplain().explainQueryPlan(WCDB::StatementCreateTable().create("message", columnDefList));

4.2.8.1 执行WINQ

通过exec:执行WINQ statement

[wcdb exec:statement];

4.2.8.2 获取WINQ运行结果

通过prepare:运行WINQ statement,获得WCTStatement,并以此获取返回值。

WCTStatement *statement = [wcdb prepare:statementExplain];
if (statement && [statement step]) {for (int i = 0; i < [statement getCount]; ++i) {NSString *columnName = [statement getNameAtIndex:i];WCTValue *value = [statement getValueAtIndex:i];NSLog(@"%@:%@", columnName, value);}
}
  1. 通过getDescription()打印log,调试确保SQL正确
NSLog(@"SQL: %s", statementExplain.getDescription().c_str());

4.2.9 事务

  • WCDB的基础事务接口与FMDB的接口类似。

  • FMDB代码

/*FMDB Code*/
BOOL result = [fmdb beginTransaction];
if (!result) {//failed
}
//do sth...
if (![fmdb commit]) {//failed[fmdb rollback];
}
  • WCDB代码
/*WCDB Code*/
BOOL result = [wcdb beginTransaction];
if (!result) {//failed
}
//do sth...
if (![wcdb commitTransaction]) {[wcdb rollbackTransaction];
}

4.2.9.1 便捷事务接口

  • runTransaction:接口会在commit失败时自动rollback事务。开发者也可以在BLOCK结束时返回YESNO来决定commitrollback事务,以此减少代码量。
[wcdb runTransaction:^BOOL{//do sth...return result;//YES to commit transaction and NO to rollback transaction
}];

4.2.10 多重语句和批处理

  • WCDB不支持多重语句。多个语句需拆分单独写。

  • WCDB对于涉及批量操作的接口,都有内置的事务。如createTableAndIndexesOfName:withClass:insertObjects:into:等,这类接口通常不止执行一条SQL,因此WCDB会自动嵌入事务,以提高性能。

4.2.11 线程安全与并发

  • FMDB通过FMDatabasePool完成多线程任务。

  • 而对于WCDBWCTDatabaseWCTTableWCTTransaction的所有SQL操作接口都是线程安全,并且自动管理并发的。

  • WCDB的连接池会根据数据库访问所在的线程、是否处于事务、并发状态等,自动分发合适的SQLite连接进行操作,并在完成后回收以供下一次再利用。

  • 因此,开发者既不需要使用一个新的类来完成多线程任务,也不需要过多关注线程安全的问题。同时,还能获得更高的性能表现。

  • FMDB代码

/*FMDB Code*/
//thread-1 read
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{[fmdbPool inDatabase:^(FMDatabase *_Nonnull db) {NSMutableArray *messages = [[NSMutableArray alloc] init];FMResultSet *resultSet = [db executeQuery:@"SELECT * FROM message"];while ([resultSet next]) {Message *message = [[Message alloc] init];message.localID = [resultSet intForColumnIndex:0];message.content = [resultSet stringForColumnIndex:1];message.createTime = [NSDate dateWithTimeIntervalSince1970:[resultSet doubleForColumnIndex:2]];message.modifiedTime = [NSDate dateWithTimeIntervalSince1970:[resultSet doubleForColumnIndex:3]];[messages addObject:message];}//...}];
});
//thread-2 write
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{[fmdbPool inDatabase:^(FMDatabase *_Nonnull db) {[db beginTransaction]for (Message *message in messages) {[db executeUpdate:@"INSERT INTO message VALUES(?, ?, ?, ?)", @(message.localID), message.content, @(message.createTime.timeIntervalSince1970), @(message.modifiedTime.timeIntervalSince1970)];}if (![db commit]) {[db rollback];}}];
});
  • WCDB代码
/*WCDB Code*/
//thread-1 read
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{NSArray *messages = [wcdb getAllObjectsOfClass:Message.class fromTable:@"message"];//...
});
//thread-2 write
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{[wcdb insertObjects:messages into:@"message"];
});

4.2.12 配置

  • 在使用数据库时,通常会对其设置一些默认的配置,如cache_sizejournal_mode等。

  • FMDB通过FMDatabasePoolDelegate进行配置,但其只能在SQLite Handle创建时进行配置。对于已经产生的SQLite handle,很难再次更改配置。

  • WCDB可以随时灵活地对其设置或变更。

  • FMDB代码

/*FMDB Code*/
- (BOOL)databasePool:(FMDatabasePool *)pool shouldAddDatabaseToPool:(FMDatabase *)database
{FMResultSet* resultSet = [database executeQuery:@"PRAGMA cache_size=-2000"];[result next];
}
  • WCDB代码
/*WCDB Code*/
[wcdb setConfig:^BOOL(std::shared_ptr<WCDB::Handle> handle, WCDB::Error &error) {return handle->exec(WCDB::StatementPragma().pragma(WCDB::Pragma::CacheSize, -2000));
} forName:@"CacheSizeConfig"]'

4.2.13 关闭数据库

  • 关闭数据库通常有两种场景:
  1. 数据库使用结束,回收对象。
  2. 数据库进行某些操作,需要临时关闭数据库。如移动、复制数据库文件。

4.2.13.1 回收对象

  • 对于这种情况,开发者无需手动操作。WCDB会自动管理这个过程。对于某一路径的数据库,WCDB会在所有对其的引用释放时,自动关闭数据库,并回收资源。
  • 对于iOS平台,当内存不足时,WCDB会自动关闭空闲的SQLite连接,以节省内存。开发者也可以手动调用[db purgeFreeHandles]对清理单个数据库的空闲SQLite连接。或调用[WCTDatabase PurgeFreeHandlesInAllDatabases]清理所有数据库的空闲SQLite连接。

4.2.13.2 手动关闭数据库

  • 无论是WCDB的多线程管理,还是FMDBFMDatabasePool,都存在多线程关闭数据库的问题。即,当一个线程希望关闭数据库时,另一个线程还在继续执行操作。

  • 而某些特殊的操作需要确保数据库完全关闭,例如移动、重命名、删除数据库等文件层面的操作。

  • 例如,若在A线程进行插入操作的执行过程中,B线程尝试复制数据库,则复制后的新数据库很可能是一个损坏的数据库。

  • 因此,WCDB提供了close:接口确保完全关闭数据库,并阻塞其他线程的访问。

[wcdb close:^(){//do something on this closed database}];

4.2.14 隔离Objective-C++代码

  • WCDB基于WINQ,引入了Objective-C++代码,因此对于所有引入WCDB的源文件,都需要将其后缀.m改为.mm。为减少影响范围,可以通过Objective-Ccategory特性将其隔离,达到只在model层使用Objective-C++编译,而不影响controllerview
  • 对于已有类WCTSampleAdvance
//WCTSampleAdvance.h
#import <Foundation/Foundation.h>
#import "WCTSampleColumnCoding.h"@interface WCTSampleAdvance : NSObject@property(nonatomic, assign) int intValue;
@property(nonatomic, retain) WCTSampleColumnCoding *columnCoding;@end//WCTSampleAdvance.mm
@implementation WCTSampleAdvance@end
  • 可以创建WCTSampleAdvance (WCTTableCoding)专门用于定义ORM
  • 为简化定义代码,WCDB同样提供了文件模版.

4.2.14.1 WCTTableCoding文件模版

  • 为了简化定义,WCDB同样提供了Xcode文件模版来创建WCTTableCodingcategory
  1. 首先需要安装文件模版。

安装脚本集成在WCDB的编译脚本中,只需编译一次WCDB,就会自动安装文件模版。
也可以手动运行cd path-to-your-wcdb-dir/objc/templates; sh install.sh;手动安装 文件模版。

  1. 安装完成后重启Xcode,选择新建文件,滚到窗口底部,即可看到对应的文件模版.

  2. 选择WCTTableCoding ,输入需要实现WCTTableCoding的类

  3. 这里以WCTSampleAdvance为例,Xcode会自动创建WCTSampleAdvance+WCTTableCoding.h文件模版:

#import "WCTSampleAdvance.h"
#import <WCDB/WCDB.h>@interface WCTSampleAdvance (WCTTableCoding) <WCTTableCoding>WCDB_PROPERTY(<#property1 #>)
WCDB_PROPERTY(<#property2 #>)
WCDB_PROPERTY(<#property3 #>)
WCDB_PROPERTY(<#property4 #>)
WCDB_PROPERTY(<#... #>)@end
  1. 加上类的ORM实现即可。
//WCTSampleAdvance.h
#import <Foundation/Foundation.h>
#import "WCTSampleColumnCoding.h"@interface WCTSampleAdvance : NSObject@property(nonatomic, assign) int intValue;
@property(nonatomic, retain) WCTSampleColumnCoding *columnCoding;@end//WCTSampleAdvance.mm
@implementation WCTSampleAdvanceWCDB_IMPLEMENTATION(WCTSampleAdvance)
WCDB_SYNTHESIZE(WCTSampleAdvance, intValue)
WCDB_SYNTHESIZE(WCTSampleAdvance, columnCoding)WCDB_PRIMARY_ASC_AUTO_INCREMENT(WCTSampleAdvance, intValue)@end//WCTSampleAdvance+WCTTableCoding.h
#import "WCTSampleAdvance.h"
#import <WCDB/WCDB.h>@interface WCTSampleAdvance (WCTTableCoding) <WCTTableCoding>WCDB_PROPERTY(intValue)
WCDB_PROPERTY(columnCoding)@end
  1. 此时,原来的WCTSampleAdvance.h中不包含任何C++的代码。因此,其他文件对其引用时,不需要修改文件名后缀。只有Model层需要使用WCDB接口的类,才需要包含WCTSampleAdvance+WCTTableCoding.h,并修改文件名后缀为.mm。

IOS数据存储 之WCDB (一)相关推荐

  1. IOS数据存储 之WCDB (二)WCDB.swift使用篇

    IOS数据存储 之WCDB (二)WCDB.swift使用篇 1.WCDB.Swfit基础使用 1.1 WCDB.Swfit 简介 1.1.1 模型绑定 1.1.2 创建数据库与表 1.1.3 操作数 ...

  2. iOS开发 数据存储之WCDB的介绍

    一.介绍 WCDB是一个高效.完整.易用的移动数据库框架,基于SQLCipher,支持iOS,macOS和Android 二.基本特性 易用,WCDB支持一句代码即可将数据取出并组合为object W ...

  3. IOS数据存储之文件沙盒存储

    前言: 之前学习了数据存储的NSUserDefaults,归档和解档,对于项目开发中如果要存储一些文件,比如图片,音频,视频等文件的时候就需要用到文件存储了.文件沙盒存储主要存储非机密数据,大的数据. ...

  4. iOS数据存储简要笔记

    1.  数据存储常用的方式(1)XML 属性列表(plist)归档 (2)preference(偏好设置) (3)NSKeyedArchiver归档(NSCoding) (4)  SQLite3   ...

  5. iOS数据存储——沙盒。

    iOS数据的持久化,可以通过以下途径解决 1.通过文件管理\归档来解决. 2.如果只是少量数据,可通过NSUserDefaults解决,相当于android中的sharedPreferences. 3 ...

  6. iOS数据存储-钥匙串存储

    2017.11.20 14:41* 字数 227 阅读 678评论 0喜欢 0 钥匙串介绍   1. 表示设备唯一号的标识,在IOS7中要么被禁止使用,要么重新安装程序后两次获取的标识符不一样. 2. ...

  7. IOS数据存储5种方式

    iOS中的数据存储(上) iOS应用数据存储的常用方式: 应用沙盒: 应用沙盒结构分析 Documents: temp: Library/Caches: Library/Preference: 沙盒根 ...

  8. iOS开发 数据存储之WCDB的使用

    一.类字段绑定 在WCDB内,ORM(Object Relational Mapping)是指 将一个ObjC的类,映射到数据库的表和索引: 将类的property,映射到数据库表的字段: 这一过程. ...

  9. IOS数据存储 —— 1 沙盒(数据存储目录)

    沙盒结构 沙盒的文件系统⽬录,如下图所示(假设应用的名称叫Layer) 1.Documents 目录 您应该将所有的应用程序数据文件写入到这个目录下. 这个目录用于存储用户数据或其它应该定期备份的信息 ...

最新文章

  1. linux c获得时间和设置时间
  2. Myeclipse 2015 stable 2.0 完美破解方法
  3. Linux如何查看进程、杀死进程、查看端口等常用命令
  4. c++判断二个数是否为相反的符号算法实现(附完整源码)
  5. IOT物联网观察之物联网是器,大数据是魂,人工智能是手段!
  6. JAVA高级工程师课程笔记整理——(八)tomcat与九大内置对象
  7. oracle中存储过程 =,oracle中的存储过程使用
  8. 计算机专业简历推荐信范文,个人简历自我推荐信范文【三篇】
  9. deldir | 生成泰森多边形的一段R语言代码
  10. 新型智慧城市 相关网址
  11. linux 多个文件中查找字符串
  12. TOGAF9.2企业架构师考试小记
  13. DirectX Repair
  14. Hashtable、HashMap 与 HashTable区别、HashMap、Hashtable和TreeMap、 LinkedHashMap
  15. 一、计算机网络概述之网络核心
  16. bbox regresion
  17. tewa-500e 虚拟服务器,关于成都电信送的光纤猫设置 TEWA-500E
  18. 输入一个N整数,结果为1 23 456 78910
  19. java listbox_MFC中Listbox控件的简单使用
  20. 解决Win10桌面Explorer.exe占用CPU过高的问题。

热门文章

  1. sql 增删改 合表操作
  2. 循序渐进学习 Java 锁机制
  3. pytorch-unsqueeze用法
  4. PHP二维数组按照中文姓名首字母排序
  5. C# VS2012下的3D显示(三)
  6. 计算机优化英语课堂教学,巧用多媒体课件优化初中英语课堂教学.doc
  7. 通达OA二次开发 开发基于AJAX技术数据列表控件的智能表单(图文)
  8. MyBatis循环Map(高级用法)
  9. android基础复习笔记——1.http的原理和工作机制
  10. Java基础-基本类型