Realm是开源的,完全免费的,支持多种语言,跨移动端,底层并不是基于SQLite,而是完全重新写的对象数据库(Object Database),比SQLite和CoreData要高效的非常多。
查看如何加密数据库文件
https://academy.realm.io/posts/tim-oliver-realm-cocoa-tutorial-on-encryption-with-realm/
查看官方编写的加密代码
https://github.com/realm/realm-cocoa/blob/master/examples/ios/objc/Encryption/LabelViewController.m
#import <Realm/Realm.h>
// 创建Dog类
@interface Dog : RLMObject
@property NSString *name;
@property NSData *picture;
@property NSInteger age;
@end
@implementation Dog
@end
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 创建或读取Realm数据库
NSError *error = nil;
// 创建基本配置
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 设置加密秘钥
config.encryptionKey = [self getKey];
NSLog(@"%@", config.encryptionKey); // 就是秘钥
// 创建Realm实例
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (error) {
NSLog(@"%@", error);
}
// 增加数据
[realm transactionWithBlock:^{
for (int i = 0; i < 64; i++) {
// 我就直接创建64个Dog实例,然后插入到数据库
// 数据表是根据雷鸣创建的
Dog *dog = [[Dog alloc] init];
dog.name = [NSString stringWithFormat:@"Rex%d", i];
dog.age = i;
uint8_t buffer[i];
int status = SecRandomCopyBytes(kSecRandomDefault, i, buffer);
dog.picture = [[NSData alloc] initWithBytes:buffer length:sizeof(buffer)];
// 调用插入函数
[realm addObject:dog];
}
}];
// 修改
[realm beginWriteTransaction];
Dog *query_dog = [[Dog objectsInRealm:realm where:@"age = 2"] firstObject];
query_dog.name = @"修改后的值";
[realm commitWriteTransaction];
// 查询
RLMResults *puppies = [Dog objectsInRealm:realm where:@"age > 0"];
for (Dog *d in puppies) {
NSLog(@"name=%@,age=%ld,picture=%@", d.name, (long)d.age, d.picture);
}
}
- (NSData *)getKey {
// Identifier for our keychain entry - should be unique for your application
static const uint8_t kKeychainIdentifier[] = "io.Realm.EncryptionExampleKey";
NSData *tag = [[NSData alloc] initWithBytesNoCopy:(void *)kKeychainIdentifier
length:sizeof(kKeychainIdentifier)
freeWhenDone:NO];
// First check in the keychain for an existing key
NSDictionary *query = @{(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrApplicationTag: tag,
(__bridge id)kSecAttrKeySizeInBits: @512,
(__bridge id)kSecReturnData: @YES};
CFTypeRef dataRef = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataRef);
if (status == errSecSuccess) {
return (__bridge NSData *)dataRef;
}
// No pre-existing key from this application, so generate a new one
static const uint8_t kDBPasswordKey[] = "zhangqiang.realm.key";
NSData *keyData = [[NSData alloc] initWithBytesNoCopy:(void *)kDBPasswordKey
length:sizeof(kDBPasswordKey)
freeWhenDone:NO];
// NSData *keyData = [[NSString stringWithUTF8String:kDBPasswordKey] dataUsingEncoding:NSUTF8StringEncoding];
// Store the key in the keychain
query = @{(__bridge id)kSecClass: (__bridge id)kSecClassKey,
(__bridge id)kSecAttrApplicationTag: tag,
(__bridge id)kSecAttrKeySizeInBits: @512,
(__bridge id)kSecValueData: keyData};
status = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
NSAssert(status == errSecSuccess, @"Failed to insert new key in the keychain");
return keyData;
}
WCDB 来自微信的基于SQLCipher的数据库
Object Relational Mapping(ORM) 对象关系映射
完全开源,性能杠杠的!
https://github.com/Tencent/wcdb
- 映射Objc的类名为表明和索引
- 映射该类的属性为数据表的字段名
先构建一个Message类,请注意,凡是导入了WCDB头文件
的类的文件名
必须要改成.mm
,因为WCDB使用C++封装的,也就是说,mm是可以兼容C++和Objective-C的
#import <WCDB/WCDB.h>
@interface Message : NSObject<WCTTableCoding>
@property int localID;
@property (retain) NSString *content;
@property (retain) NSDate *createTime;
@property (retain) NSDate *modifiedTime;
@property (assign) int unused;
WCDB_PROPERTY(localID)
WCDB_PROPERTY(content)
WCDB_PROPERTY(createTime)
WCDB_PROPERTY(modifiedTime)
@end
@implementation Message
WCDB_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
- 宏
WCDB_PROPERTY
表示声明的属性绑定到数据表的列名 - 宏
WCDB_IMPLEMENTATION
表示类名绑定到数据表名 - 宏
WCDB_SYNTHESIZE
跟WCDB_PROPERTY
是一对,一样的使用 - 宏
WCDB_PRIMARY
表示声明的指定属性为主键 - 宏
WCDB_INDEX
表示定义索引
创建数据表
// 指定数据库地址和名称
NSString *path = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"/wcdb.sqlite"];
// 构建数据库实例对象
WCTDatabase *database = [[WCTDatabase alloc] initWithPath:path];
// 设置数据库加密
[database setCipherKey:[@"123456" dataUsingEncoding:NSASCIIStringEncoding]];
//[database setCipherKey:[NSData dataWithBytes:"123456" length:6]];
// 创建数据表格message
BOOL result = [database createTableAndIndexesOfName:@"message" withClass:Message.class];
if (result) {
}
插入数据
// 构建数据模型
Message *message = [[Message alloc] init];
message.localID = 6;
message.content = @"Hello, WCDB 6!";
message.createTime = [NSDate date];
message.modifiedTime = [NSDate date];
// 插入数据
result = [database insertObject:message into:@"message"];
if (result) {
}
更新数据
Message *update_message = [[Message alloc] init];
update_message.content = @"Hello, WCDB 5!";
result = [database updateRowsInTable:@"message" onProperties:Message.content withObject:update_message where:Message.localID == 5];
if (result) {
}
删除数据
result = [database deleteObjectsFromTable:@"message" where:Message.localID==0];
查询数据
NSArray<Message *> *messages = [database getObjectsOfClass:Message.class fromTable:@"message" orderBy:Message.localID.order()];
if (messages && messages.count > 0) {
for (int i = 0; i < messages.count; i++) {
Message *m = messages[i];
NSLog(@"localID=%d, content=%@, createTime=%@, modifiedTime=%@", m.localID, m.content, m.createTime, m.modifiedTime);
}
}
执行事务
WCTTransaction *transaction = [database getTransaction];
BOOL transStatus = [transaction begin];// 开始事务
// 执行全部的sql语句,假设这里执行for循环64条语句
for (int i = 10; i < 74; i++) {
Message *msg = [[Message alloc] init];
msg.localID = i;
msg.content = [NSString stringWithFormat:@"Hello, WCDB %d!", i];
msg.createTime = [NSDate date];
msg.modifiedTime = [NSDate date];
[transaction insertObject:msg into:@"message"];
}
transStatus = [transaction commit]; // 提交事务
if (!transStatus) {
[transaction rollback]; // 有任何语句报错,就回滚
NSLog(@"%@", [transaction error]);
}
使用WINQ查询
NSArray<Message *> *messages = [database getObjectsOnResults:{Message.localID, Message.content, Message.createTime, Message.modifiedTime} fromTable:@"message" where:Message.localID > 0 && Message.content.isNotNull()];
if (messages && messages.count > 0) {
for (int i = 0; i < messages.count; i++) {
Message *m = messages[i];
NSLog(@"localID=%d, content=%@, createTime=%@, modifiedTime=%@", m.localID, m.content, m.createTime, m.modifiedTime);
}
}
这行查询代码就像这句SQL
SELECT localID,content,createTime,modifiedTime
FROM message
WHERE localID>0 AND content IS NOT NULL