Log Entry

WCDB-入门教程

WCDB的使用操作

WCDB 入门教程

目录

  1. WCDB 简介
  2. 环境搭建
  3. 项目结构
  4. 模型类定义
  5. 数据库操作
  6. 遇到的问题及解决方案

WCDB 简介

WCDB (WeChat DataBase) 是微信团队开源的一个高效、完整、易用的移动数据库框架,基于 SQLite 开发,支持 iOS、macOS、Android 等平台。

主要特性

  • 易用性:支持 ORM (对象关系映射),通过宏定义即可完成模型类和数据库表的绑定
  • 高效性:基于 SQLite 优化,支持多线程并发读写
  • 完整性:提供数据库加密、损坏修复、数据迁移等完整解决方案
  • 跨平台:iOS/macOS 使用 Objective-C/Swift,Android 使用 Java/Kotlin

环境搭建

1. 使用 CocoaPods 集成

在项目的 Podfile 中添加依赖:

platform :ios, '12.0'
 use_frameworks!
 
 target 'WCDB-Test' do
   pod 'WCDB.objc', '~> 2.1.0'
 end

然后执行安装:

pod install

注意:安装完成后,后续打开项目需要使用 .xcworkspace 文件而不是 .xcodeproj


项目结构

WCDB-Test/
 ├── WCDB-Test/
 │   ├── AppDelegate.h/m          # 应用代理
 │   ├── SceneDelegate.h/m        # 场景代理 (iOS 13+)
 │   ├── ViewController.h/mm      # 主控制器 (数据库操作演示)
 │   ├── Sample.h/m               # 数据模型类
 │   ├── Assets.xcassets/         # 资源文件
 │   └── Base.lproj/              # 本地化资源
 ├── Podfile                      # CocoaPods 依赖配置
 ├── Podfile.lock                 # 锁定依赖版本
 ├── WCDB-Test.xcodeproj/         # Xcode 项目
 └── WCDB-Test.xcworkspace/       # Xcode 工作空间

模型类定义

Sample.h

WCDB 通过宏定义实现 ORM 映射,需要在头文件中声明属性和数据库字段:

#import <Foundation/Foundation.h>
 #import <WCDBObjc/WCDBObjc.h>
 
 @interface Sample : NSObject <WCTTableCoding>
 
 @property (nonatomic, assign) int identifier;
 @property (nonatomic, strong) NSString *content;
 
 // 声明 WCDB 属性,生成对应的类方法
 WCDB_PROPERTY(identifier)
 WCDB_PROPERTY(content)
 
 @end

Sample.m

在实现文件中绑定模型类与数据库表:

#import "Sample.h"
 
 @implementation Sample
 
 // 声明实现
 WCDB_IMPLEMENTATION(Sample)
 
 // 合成属性,建立属性与数据库字段的映射
 WCDB_SYNTHESIZE(identifier)
 WCDB_SYNTHESIZE(content)
 
 @end

关键宏说明

宏 作用
WCDB_PROPERTY(property) 在头文件中声明,生成获取属性的类方法
WCDB_IMPLEMENTATION(class) 在实现文件中声明,标识该类参与 WCDB 绑定
WCDB_SYNTHESIZE(property) 合成属性,建立属性与数据库字段的映射

数据库操作

1. 初始化数据库

#import "Sample.h"
 #import <WCDBObjc/WCDBObjc.h>
 
 @interface ViewController ()
 @property (nonatomic, strong) WCTDatabase *database;
 @end
 
 @implementation ViewController
 
 - (void)setupDatabase {
     // 获取数据库路径 (Document 目录)
     NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
                                                            NSUserDomainMask, YES) firstObject];
     NSString *dbPath = [path stringByAppendingPathComponent:@"sample.db"];

     // 创建数据库对象
     self.database = [[WCTDatabase alloc] initWithPath:dbPath];

     // 创建表
     BOOL result = [self.database createTable:@"sampleTable" withClass:Sample.class];
     NSLog(@"创建表: %@", result ? @"成功" : @"失败");
 }
 
 @end

2. 插入数据 (Create)

- (void)insertData {
     // 插入 3 条测试数据
     for (int i = 1; i <= 3; i++) {
         Sample *obj = [[Sample alloc] init];
         obj.identifier = (int)[[NSDate date] timeIntervalSince1970] + i;
         obj.content = [NSString stringWithFormat:@"test_data_%d", i];

         BOOL result = [self.database insertObject:obj intoTable:@"sampleTable"];
         NSLog(@"插入第 %d 条: %@", i, result ? @"成功" : @"失败");
     }
 }

3. 查询数据 (Read)

- (void)queryData {
     // 查询所有数据
     NSArray<Sample *> *objects = [self.database getObjectsOfClass:Sample.class
                                                        fromTable:@"sampleTable"];

     NSLog(@"查询到 %lu 条数据:", (unsigned long)objects.count);
     for (Sample *obj in objects) {
         NSLog(@"  ID: %d, Content: %@", obj.identifier, obj.content);
     }
 }

4. 更新数据 (Update)

- (void)updateData:(int)targetId {
     // 准备更新数据
     Sample *updateObject = [[Sample alloc] init];
     updateObject.content = @"updated_content";

     // 更新指定 ID 的数据
     BOOL result = [self.database updateTable:@"sampleTable"
                                  setProperties:[Sample content]  // 要更新的字段
                                     toObject:updateObject
                                        where:[Sample identifier] == targetId];

     NSLog(@"更新结果: %@", result ? @"成功" : @"失败");
 }

重要说明:在 Objective-C++ 文件(.mm)中,访问 WCDB 属性时必须使用消息发送语法 [Sample content],不能使用点语法 Sample.content。

5. 删除数据 (Delete)

- (void)deleteData {
     // 删除所有数据
     BOOL result = [self.database deleteFromTable:@"sampleTable"];
     NSLog(@"删除所有数据: %@", result ? @"成功" : @"失败");
 }
 
 // 删除指定条件的数据
 - (void)deleteDataWithId:(int)targetId {
     BOOL result = [self.database deleteFromTable:@"sampleTable"
                                            where:[Sample identifier] == targetId];
     NSLog(@"删除 ID=%d 的数据: %@", targetId, result ? @"成功" : @"失败");
 }

遇到的问题及解决方案

问题 1:Property 'content' not found on object of type 'Sample'

错误信息:

ViewController.m:154:59 Property 'content' not found on object of type 'Sample'

问题原因:
WCDB 的 WCDB_PROPERTY 宏生成的是类方法(如 + (WCTProperty *)content),而不是类属性。在 Objective-C++ 文件(.mm)中,使用点语法 Sample.content 访问类方法会导致编译错误。

解决方案:
将点语法改为消息发送语法:

// ❌ 错误写法(在 .mm 文件中)
 setProperties:Sample.content
 where:Sample.identifier == targetId
 
 // ✅ 正确写法
 setProperties:[Sample content]
 where:[Sample identifier] == targetId

问题 2:Sandbox: rsync deny file-write-create

错误信息:

Sandbox: rsync(32540) deny(1) file-write-create /Users/.../WCDBObjc.framework/_CodeSignature
 rsync: mkpathat: Operation not permitted

问题原因:
Xcode 15 引入的 ENABLE_USER_SCRIPT_SANDBOXING 功能会限制构建脚本的文件系统访问权限,导致 CocoaPods 的 [CP] Embed Pods Frameworks 脚本无法正常复制和签名 Framework。

解决方案:
在项目的 Build Settings 中,将 ENABLE_USER_SCRIPT_SANDBOXING 设置为 NO:

  1. 打开项目 → 选择 Target → Build Settings
  2. 搜索 ENABLE_USER_SCRIPT_SANDBOXING
  3. 将值改为 NO

或者修改 project.pbxproj 文件:

- ENABLE_USER_SCRIPT_SANDBOXING = YES;
 + ENABLE_USER_SCRIPT_SANDBOXING = NO;

建议操作:
修改后需要清理构建缓存:

  • Xcode: Product → Clean Build Folder (Cmd+Shift+K)
  • 或命令行: rm -rf ~/Library/Developer/Xcode/DerivedData/项目名-*

问题 3:插入 3 条数据但查询出 4 条

问题描述:
代码意图插入 3 条数据,但查询结果显示有 4 条。

问题代码:

- (void)insertData {
     // 第 1 条:单独插入
     Sample *object = [[Sample alloc] init];
     ...
     [self.database insertObject:object intoTable:@"sampleTable"];

     // 第 2-4 条:循环插入 3 条
     for (int i = 1; i <= 3; i++) {
         Sample *obj = [[Sample alloc] init];
         ...
         [self.database insertObject:obj intoTable:@"sampleTable"];
     }
     [self log:@"额外插入 3 条测试数据"];  // 日志误导
 }

问题原因:
代码实际插入了 1 + 3 = 4 条数据,但日志提示"额外插入 3 条",容易让人误解为总共只插入了 3 条。

解决方案:
统一使用循环插入,并修正日志:

- (void)insertData {
     [self log:@"\n--- 插入操作 ---"];

     // 插入 3 条测试数据
     for (int i = 1; i <= 3; i++) {
         Sample *obj = [[Sample alloc] init];
         obj.identifier = (int)[[NSDate date] timeIntervalSince1970] + i;
         obj.content = [NSString stringWithFormat:@"test_data_%d", i];

         BOOL result = [self.database insertObject:obj intoTable:@"sampleTable"];
         [self log:[NSString stringWithFormat:@"插入第 %d 条: %@", i, result ? @"成功" : @"失败"]];
     }
     [self log:@"共插入 3 条测试数据"];
 }

最佳实践

1. 数据库路径选择

  • 数据库文件应放在应用的沙盒目录内(如 Documents 或 Library)
  • 不要放在 tmp 目录,因为系统可能会清理该目录
  • 考虑将数据库放在 Library/Application Support 下,该目录会被 iTunes 备份

2. 线程安全

  • WCDB 是线程安全的,可以在多线程环境中使用
  • 数据库操作会在内部队列中异步执行

3. 主键设置

  • 建议为模型类设置主键,避免插入重复数据
  • 可以使用 WCDB_PRIMARY 宏声明主键:
// Sample.h
 @interface Sample : NSObject <WCTTableCoding>
 @property (nonatomic, assign) int identifier;
 @property (nonatomic, strong) NSString *content;
 WCDB_PROPERTY(identifier)
 WCDB_PROPERTY(content)
 WCDB_PRIMARY(identifier)  // 声明主键
 @end
 
 // Sample.m
 WCDB_IMPLEMENTATION(Sample)
 WCDB_SYNTHESIZE(identifier)
 WCDB_SYNTHESIZE(content)
 WCDB_PRIMARY_AUTO_INCREMENT(identifier)  // 主键自增

4. 错误处理

  • WCDB 操作返回 BOOL 值表示成功/失败
  • 可以通过 WCTError 获取详细的错误信息
WCTError *error = nil;
 BOOL result = [self.database insertObject:obj
                                 intoTable:@"sampleTable"
                                     error:&error];
 if (!result && error) {
     NSLog(@"插入失败: %@", error.message);
 }

参考资料


本文档基于 WCDB 2.1.0 版本编写