返回
通讯产品
分类

之前很长一段时间我都是使用的FMDB,微信移动端数据库组件WCDB即将开源

日期: 2020-03-25 12:49 浏览次数 : 69

摘要微信开发团队宣布将微信自用的移动端数据库组件 WCDB(WeChat Database)正式对外开源。前言微信团队两周前预告即将开源WCDB后(详见当时资讯《[资讯] 微信移动端数据库组件WCDB即将开源!》),于2017年06月09日正式开源了微信自用的移动端数据库组件 WCDB(WeChat Database)。WCDB简介WCDB是一个高效、完整、易用的移动数据库框架,基于 SQLCipher,支持 iOS、macOS 和 Android。微信高级工程师何俊伟表示:“开源只是故事的开始,我们仍会持续对 WCDB 做改进,包括更易用的接口、更好的性能、更高的可靠性。这些改进最终也会原封不动地在微信使用。”WCDB(WeChatDataBase)是微信官方的移动端数据库组件,致力于提供一个高效、易用、完整的移动端存储方案。它包含三个模块:WCDB-iOS/Mac;WCDB-Android;数据库损坏修复工具WCDBRepair。WCDB的开发背景对于iOS开发者来说,数据库的技术选型一直是个令人头痛的问题。由于Apple提供的CoreData框架差强人意,使得开发者们纷纷将目光投向开源社区,寻找更好的存储方案。 对于微信也是如此。数据库是微信内最基础的组件之一,消息收发、联系人、朋友圈等等业务都离不开数据库的支持。为了满足需求,我们也对现有方案做了对比研究。目前移动端数据库方案按其实现可分为两类:关系型数据库,代表有CoreData、FMDB等。CoreData 它是苹果内建框架,和Xcode深度结合,可以很方便进行ORM;但其上手学习成本较高,不容易掌握。稳定性也堪忧,很容易crash;多线程的支持也比较鸡肋。FMDB 它基于SQLite封装,对于有SQLite和ObjC基础的开发者来说,简单易懂,可以直接上手;而缺点也正是在此,FMDB只是将SQLite的C接口封装成了ObjC接口,没有做太多别的优化,即所谓的胶水代码(Glue

小摩丝.jpg

安装

1 安装CocoaPods.
2 Podfile里面写 pod 'WCDB'
3 pod install
4 安装好后编译一下

  • 注意: 由于WCDB是结合c++写的,引用#import <WCDB/WCDB.h>的文件.m里面都要改成.mm后缀的,所以一般上为了隔离model,不让view喝viewController里面也改成.mm后缀的,我们写一个model的分类,遵守WCTTableCoding协议并写WCDB_PROPERTY(),WCDB编译后项目里有快捷创建model类,直接创建出分类.
  • command + n 弹出窗口,我们拉到下面,发现有WCDB一栏,选择TableCodeable
![](https://upload-images.jianshu.io/upload_images/2510972-8db851d3b7837fda.png)

屏幕快照 2018-04-02 下午7.33.08.png
  • 生成的一个model类和一个model类的分类
![](https://upload-images.jianshu.io/upload_images/2510972-1dbe17cbdf824813.png)

屏幕快照 2018-04-02 下午7.34.37.png
- SZYMessage.h文件
#import <Foundation/Foundation.h>

@interface SZYMessage : NSObject

@property(nonatomic, copy) NSString *name;
@property(nonatomic, assign) NSInteger localID;
@property(nonatomic, assign) float totalScore;
@property(nonatomic, strong) NSDate *createDate;

@end

- SZYMessage.m文件
#import "SZYMessage+WCTTableCoding.h"
#import "SZYMessage.h"
#import <WCDB/WCDB.h>

@implementation SZYMessage
//WCDB_IMPLEMENTATION,用于在类文件中定义绑定到数据库表的类
WCDB_IMPLEMENTATION(SZYMessage)
//WCDB_SYNTHESIZE,用于在类文件中定义绑定到数据库表的字段
WCDB_SYNTHESIZE(SZYMessage, name)
WCDB_SYNTHESIZE(SZYMessage, localID)


//默认使用属性名作为数据库表的字段名。对于属性名与字段名不同的情况,可以使用WCDB_SYNTHESIZE_COLUMN(className, propertyName, columnName)进行映射。
WCDB_SYNTHESIZE_COLUMN(SZYMessage, totalScore, "db_totalScore")

WCDB_SYNTHESIZE_DEFAULT(SZYMessage, createDate, WCTDefaultTypeCurrentDate) //设置一个默认值

//主键
WCDB_PRIMARY_ASC_AUTO_INCREMENT(SZYMessage, localID)
//用于定义非空约束
WCDB_NOT_NULL(SZYMessage, name)

@end

- SZYMessage+WCTTableCoding.h 文件

#import "SZYMessage.h"
#import <WCDB/WCDB.h>

@interface SZYMessage (WCTTableCoding) <WCTTableCoding>
//WCDB_PROPERTY用于在头文件中声明绑定到数据库表的字段,写在分类里,不写在.h里面,这样view和controller不会 引入导入<WCDB/WCDB.h>的文件

WCDB_PROPERTY(name)
WCDB_PROPERTY(localID)
WCDB_PROPERTY(totalScore)
WCDB_PROPERTY(createDate)

@end

3)微信WCDB进化之路 - 开源与开始

  • 作者简介:sanhuazhang
  • 内容简介:今天,WCDB(WeChat Database)通过了公司的最终审核,作为腾讯微信的一个开源组件分享给大家。从WCDB初建,到不断摸索、优化,再到整理代码、文档,最终看着她在 GitHub 上静静等待着“Make Public”被按下,心情犹如看着女儿出嫁的父亲。趁此机会,正好回顾一下 WCDB 这个“微信的数据库”的成长,分享我们的心路历程,也希望以此让大家更了解WCDB。

欢迎关注我们的微信公众号:金融壹账通移动研发团队

图片 1

自动建表建库,更新表字段

只要你定义好了你的模型类的属性,当你准备对表数据进行增删改查操作的时候,你不需要手动的去创建表和数据库,你应该调用统一的建表接口。
如果表已经建好了,当你增加表字段(persistent properties),GYDataCenter也会自动给你更新。我画了一个图:

图片 2

注意:⚠️However, GYDataCenter CANNOT delete or rename an existing column. If you plan to do so, you need to create a new table and migrate the data yourself.
GYDataCenter不能删除和重命名已经存在的列,如果你一定要这么做,需要重新建一张表并migrate数据。

GYDataCenter还可以自动的管理索引(indices),缓存(cacheLevel),事务等等。

以查询为例:

NSArray *employees = [Employee objectsWhere:@"WHERE employeeId < 
? ORDER BY employeeId"  arguments:@[ @10 ]];

不需要为每一个表去写查询接口,只要在参数中写好条件即可。开发者无须拼接字符串,即可完成SQL的条件、排序、过滤、更新等等语句。

Code)。使用过程需要用大量的代码拼接SQL、拼装Object,并不方便。key-value数据库,代表有Realm、LevelDB、RocksDB等。Realm因其在各平台封装、优化的优势,比较受移动开发者的欢迎。对于iOS开发者,key-value的实现直接易懂,可以像使用NSDictionary一样使用Realm。并且ORM彻底,省去了拼装Object的过程。但其对代码侵入性很强,Realm要求类继承RLMObject的基类。这对于单继承的ObjC,意味着不能再继承其他自定义的子类。同时,key-value数据库对较为复杂的查询场景也比较无力。可见,各个方案都有其独特的优势及劣势,没有最好的,只有最适合的。而对于微信来说,我们所期望的数据库应满足:高效;增删改查的高效是数据库最基本的要求。除此之外,我们还希望能够支持多个线程高并发地操作数据库,以应对微信频繁收发消息的场景。易用;这是微信开源的原则,也是WCDB的原则。SQLite本不是一个易用的组件:为了完成一个查询,往往我们需要写很多拼接字符串、组装Object的胶水代码。这些代码冗长繁杂,而且容易出错,我们希望组件能统一完成这些任务。完整;数据库操作是一个复杂的场景,我们希望数据库组件能完整覆盖各种场景。包括数据库损坏、监控统计、复杂的查询、反注入等。显然,上述各个方案都不能完全满足微信的需求。于是,我们造了这个“轮子”

WCDB-iOS/Mac。WCDB通过ORM和WINQ,体现了其易用性上的优势,使得数据库操作不再繁杂。同时,通过链式调用,开发者也能够方便地获取数据库操作的耗时等性能信息。而高级用法则扩展了WCDB的功能和用法。详情请见:《微信移动端数据库组件WCDB系列(一)-iOS基础篇》WCDB源码下载WCDB源码托管地址:

1.移动端数据库新王者:realm
2.Realm数据库 从入门到“放弃”
3.从零开始,对iOS FMDB简单易懂的封装
4.数据存储之NSUserDefaults
5.CoreData基本操作封装
6.【进阶篇】如何优雅的设计coredata的离线缓存策略
7.使用FMDB(查询、增加、删除)
8.APP缓存文件清理
9.iOS离线缓存方案
10.最全iOS数据存储方法介绍:FMDB,SQLite3 ,Core Data,Plist,Preference偏好设置,NSKeyedArchiver归档
11.SQLite数据库设计基础点
12.Realm -- oc版实践(下)
13.(译文)CoreData,NSKeyedArchiver,User Defaults三者之间的比较
14.iOS 本地持久化存储
15.iOS快速归档存储自定义对象
16.[iOS]基于ORM思想的数据库处理
17.NSUserDefaults 简介,使用 NSUserDefaults 存储自定义对象
18.学习iOS离线存储的方案
19.ios 获取文件目录路径方法大全
20.NSFileManager终极杀手
21.为什么我决定使用Realm数据库
22.数据库FMDB的封装以及简单运用
23.解析 NSUserDefaults 数据存储
24.iOS~URLCache探索
25.【iOS_Development】文件操作
26.iOS开发之文件相关的操作(沙盒目录,文件创建、移动、复制等)
27.基于iOS 10、realm封装的下载器(支持存储读取、断点续传、后台下载、杀死APP重启后的断点续传等功能)
28.iOS开发之沙盒机制&文件操作(NSFielManager)
29.iOS 持久化之归档 小结
30.FMDB 再封装,多线程安全
31.iOS-沙盒机制存放文件
32.iOS-缓存的清理
33.iOS开发基础 | 被忽视和误解的NSCache
34.Realm入门指北
35.iOS 将对象序列化成json,写入本地文件
36.iOS架构师之路:本地持久化方案
37.iOS开发中本地数据存储的总结
38.ios缓存机制-非结构化存储
39.iOS客户端开发数据存储使用总结
40.iOS资源存放问题
41.iOS数据库技术进阶
42.iOS应用程序用户信息操作之用户头像图片存入沙盒及从沙盒中读取图片并显示在头像上
43.iOS 两行代码解决数据持久化
44.网络图片保存沙盒,以及从沙盒里读取图片
45.[CoreData] SQL写烦了? 试试亲儿子!
46.数据缓存
47.iOS-FMDB数据库之增删改查使用
48.iOS如何读取.db文件
49.认识CoreData-初识CoreData
50.CoreData 从入门到精通 (一) 数据模型 + CoreData 栈的创建
51.iOS CoreData数据库之创建详解
52.我要娶你做我的CoreData!

下面我们开始创建数据库和表,并进行增删改查

##### 创建数据库和表
- (BOOL)creatDatabaseAndTable {
    //数据库路径
    NSString *path = [self.baseDirectory stringByAppendingPathComponent:@"SampleDB"];
    //NSLog(@"path--> %@",path);
    //创建数据库 路径一样,  该接口使用的是IF NOT EXISTS的SQL,因此可以用重复调用
    WCTDatabase *database = [[WCTDatabase alloc] initWithPath:path];
    _database = database;
    if ([database canOpen]) {
        NSLog(@"创建数据库成功");
    }else{
        NSLog(@"创建数据库失败");
        return NO;
    }


    //创建表  注:该接口使用的是IF NOT EXISTS的SQL,因此可以用重复调用。不需要在每次调用前判断表或索引是否已经存在。
    BOOL result = [database createTableAndIndexesOfName:SZY_TABLE_MESSAGE_NAME withClass:SZYMessage.class];

    if (!result) {
        NSLog(@"创建表失败");
        return NO;
    }
    return YES;
}


##### 插入单个数据
- (BOOL)insertData:(SZYMessage *)message {
    BOOL result = [_database insertObject:message into:SZY_TABLE_MESSAGE_NAME];

    //关闭数据库,_database如果能自己释放的话,会自动关闭,就不用手动调用关闭了
    [_database close];

    if (!result) {
        NSLog(@"插入失败");
        return NO;
    }else{
        NSLog(@"插入成功");
        return YES;
    }
}

    //插入多个数据: 
    BOOL result = [_database insertObject:message into:SZY_TABLE_MESSAGE_NAME];

    //增删改查用下面方法,可以链式调用

/*
    WCTInsert
     WCTDelete
     WCTUpdate
     WCTSelect
 */
     WCTInsert *insert = [_database prepareInsertObjectsOfClass:SZYMessage.class
                                                              into:SZY_TABLE_MESSAGE_NAME];
    BOOL result = [insert executeWithObjects:objects];


##### 查询数据  用localID排序
- (void)selectOrder {
    NSArray<SZYMessage *> *objects2 = [_database getObjectsOfClass:SZYMessage.class fromTable:SZY_TABLE_MESSAGE_NAME orderBy:SZYMessage.localID.order()];
    [objects2 enumerateObjectsUsingBlock:^(SZYMessage *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"用localID排序 --> %@ ",obj);
    }];
}

//查询数据  指定范围
- (void)selectCertainRange {
    NSArray<SZYMessage *> *objects3 = [_database getObjectsOfClass:SZYMessage.class fromTable:SZY_TABLE_MESSAGE_NAME where:SZYMessage.localID.between(0,1) || SZYMessage.name.like(@"lil%")];
    [objects3 enumerateObjectsUsingBlock:^(SZYMessage *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"objects3 --> %@ ",obj);
    }];
}

//定向 将查询的totalScore值赋给新创建的对象
- (void)selectAndAssignment {
    SZYMessage *message5 = [_database getOneObjectOnResults:SZYMessage.totalScore.max().as(SZYMessage.totalScore) fromTable:SZY_TABLE_MESSAGE_NAME];
    message5.localID = 5;
    NSLog(@"message5 --> %@ ",message5);
}

//链式调用
- (void)selectChain {
    //所有的对象
    WCTSelect *select = [_database prepareSelectObjectsOfClass:SZYMessage.class fromTable:SZY_TABLE_MESSAGE_NAME ];

    //链式查询
    NSArray<SZYMessage *> *objects6 = [[select where:SZYMessage.totalScore < 90] limit:2].allObjects;
    [objects6 enumerateObjectsUsingBlock:^(SZYMessage *obj, NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"objects6 --> %@ ",obj);
    }];
}


##### 更新
- (void)updateData {
    WCTUpdate *update = [_database prepareUpdateTable:SZY_TABLE_MESSAGE_NAME
                                        onProperties:SZYMessage.name];
    SZYMessage *object = [[SZYMessage alloc] init];
    object.name = @"xiaoming22";
    BOOL result = [update executeWithObject:object];
    if (!result) {
        NSLog(@"Update by object Error %@", update.error);
    }else{
        NSLog(@"更新成功");
    }
}


//删除表
- (void)deleteData {
    WCTDelete *deletion = [_database prepareDeleteFromTable:SZY_TABLE_MESSAGE_NAME];
    BOOL result = [deletion execute];
    if (!result) {
        NSLog(@"Delete Error %@", deletion.error);
    }else{
        NSLog(@"删除成功");
    }

    [_database close];
    //删除name是xiaoming的人
//    BOOL result = [_database deleteObjectsFromTable:SZY_TABLE_MESSAGE_NAME where:SZYMessage.name == @"xiaoming"];
//    [_database deleteObjectsFromTable:SZY_TABLE_MESSAGE_NAME where:SZYMessage.localID.between(0,1) || SZYMessage.name.like(@"lil%")];

}

1)[译] 17 位谷歌 Android 开发专家是如何看待 Kotlin 的?

  • 译者简介:ASCE1885,《Android 高级进阶》作者
  • 内容简介:Google 已经多次表达过他们不反对 Kotlin 的使用,只要 Kotlin 编译器依然生成有效的字节码,那么 Google 就不会阻止任何人使用它。但很多人依然在等待官方的正式支持,一件可能永远不会发生的事情。虽然我们等到了这一刻,但我想如果能够知道 Google 的 Android 开发专家是如何看待 Kotlin 的,那就更好了。

思考

NSArray *array = [DeptUser objectsWhere:
@",userinfo where deptUsers.uid = userinfo.uid" arguments:nil];

如果参试这样写联合查询,会产生一个错误!

图片 3

原因是:查询结果FMResultSet是按index解析通过KVC赋值modelClass的属性。
你可以通过指定arguments或者调用指定的联合查询接口joinObjectsWithLeftProperties来解决这个问题

下面是我在其源码中写了一个按字段解析方式来验证这个问题,有兴趣的可以看一下。

- (void)setProperty:(NSString *)property ofObject:object withResultSet:(FMResultSet *)resultSet{
    Class<GYModelObjectProtocol> modelClass = [object class];
    GYPropertyType propertyType = [[[modelClass propertyTypes] objectForKey:property] unsignedIntegerValue];
    Class propertyClass;
    if (propertyType == GYPropertyTypeRelationship) {
        propertyClass = [[modelClass propertyClasses] objectForKey:property];
        propertyType = [[[propertyClass propertyTypes] objectForKey:[propertyClass primaryKey]] unsignedIntegerValue];
    }

    id value = nil;
    if (![self needSerializationForType:propertyType]) {
        if (propertyType == GYPropertyTypeDate) {
            value = [resultSet dateForColumn:property];
        } else {
            value = [resultSet objectForColumnName:property];
        }
    } else {
        NSData *data = [resultSet dataForColumn:property];
        if (data.length) {
            if (propertyType == GYPropertyTypeTransformable) {
                Class propertyClass = [[modelClass propertyClasses] objectForKey:property];
                value = [propertyClass reverseTransformedValue:data];
            } else {
                value = [self valueAfterDecodingData:data];
            }
            if (!value) {
                NSAssert(NO, @"database=%@, table=%@, property=%@", [modelClass dbName], [modelClass tableName], property);
            }
        }
    }
    if ([value isKindOfClass:[NSNull class]]) {
        value = nil;
    }

    if (propertyClass) {
        id cache = [_cacheDelegate objectOfClass:propertyClass id:value];
        if (!cache) {
            cache = [[(Class)propertyClass alloc] init];
            [cache setValue:value forKey:[propertyClass primaryKey]];
            [cache setValue:@YES forKey:@"fault"];
            [_cacheDelegate cacheObject:cache];
        }
        value = cache;
    }
    if (value) {
        [object setValue:value forKey:property];
    }
}

图片 4

WINQ

上述例子中的一些特殊语法:

where:Message.localID>0
onProperties:Message.content
orderBy:Message.localID.order(WCTOrderedDescending) 这个便是WINQ。

WINQ(WCDB Integrated Query,音'wink'),即WCDB集成查询,是将自然查询的SQL集成到WCDB框架中的技术,基于C++实现。

传统的SQL语句,通常是开发者拼接字符串完成。这种方式不仅繁琐、易错,而且出错后很难定位到问题所在。同时也容易给SQL注入留下可乘之机。
下面是几个官方文档的例子

图片 5

屏幕快照 2018-04-02 下午8.00.09.png

图片 6

屏幕快照 2018-04-02 下午8.00.16.png

WINQ的接口包括但不限于:

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

凡是SQLite支持的语法规则,WINQ基本都有其对应的接口。且接口名称与SQLite的语法规则基本保持一致。对于熟悉SQL的开发者,无须特别学习即可立刻上手使用。

1)MVP模式在携程酒店的应用和扩展

  • 作者简介:赵伟麟,2011年就职于创新工场旗下点心OS,2014年加入携程酒店事业部,从事Android研发工作。擅长基于组件的业务架构,系统架构,建模,性能优化和重构,关注应用系统的扩展性和耦合性,追求简洁的代码。
  • 内容简介:MVP模式是目前客户端比较流行的框架模式,携程在很早之前就开始探索使用该模式进行相关的业务功能开发,以提升代码的规范性和可维护性,积累了一定的经验。本文将探讨一下该模式在实际工程中的优点和缺陷,并介绍携程面对这些问题时的思考,解决方案以及在实践经验基础上对该模式的扩展模式MVCPI。

数据库的技术选型一直是个令人头痛的问题,之前很长一段时间我都是使用的FMDB,做一些简单的封装。有使用过synchronized同步,也有用FMDB的DB Queue总之,差强人意。