[Realm]是由美国YCombinator
孵化的创业团队历时几年打造,第一个专门针对移动平台设计的数据库*
[Realm]是一个跨平台的移动数据库引擎,目前支持iOS
、Android
平台,同时支持Objective-C
、Swift
、Java
、React Native
、Xamarin
等多种编程语言* Realm
并不是对SQLite
或者CoreData
的简单封装, 是由核心数据引擎C++
打造,是拥有独立的数据库存储引擎,可以方便、高效的完成数据库的各种操作
Realm 数据库是 Realm 移动端数据库容器的一个实例。Realm 数据库可以是本地化的,也可以是可同步的。目前我研究的只是本地化的数据库。
看到这个realm的时候,我就想到了go/python语言中使用的第三方框架数据库xorm,它的表的创建方法跟realm简直是一模一样,同样是利用model来生成对应的表结构,唯一不同的是在移动端(iOS)上创建表的时候必须要生成一个主键,而xorm主键不是必须的。
官方的文档地址:https://realm.io/cn/docs/objc/latest/
realm在iOS上基本用法:数据库的创建、增删改查等操作
数据库创建:设置数据库所在的文件目录以及数据库的名称
- (void)setDefaultRealmForUser:(NSString *)userName{
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
//使用默认的目录,使用用户名来替换默认的文件名
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]URLByAppendingPathComponent:userName]URLByAppendingPathExtension:@"realm"];
NSString *version = [[NSUserDefaults standardUserDefaults] objectForKey:[NSString stringWithFormat:@"%@:%@",userName,@"ormVersion"]];
if (version.integerValue) {
config.schemaVersion = version.integerValue;
}
if (self.isSecu) {
config.encryptionKey = [self randomSec:userName];
}
//将这个配置应用到默认的realm数据库中
[RLMRealmConfiguration setDefaultConfiguration:config];
if (realm == nil) {
realm = [RLMRealm realmWithConfiguration:config error:nil];
}
}
数据库的添加或者更新(用事务来处理):
- (void)realmAdd_UpdateObject:(id)object
{
[realm beginWriteTransaction];
[realm addOrUpdateObject:object];
[realm commitWriteTransaction];
}
数据库的删除(单个删除,全部删除):
- (void)realmDeleteObject:(id)object
{
[realm beginWriteTransaction];
Class objectClass = [object class];
object = [objectClass createOrUpdateInRealm:realm withValue:object];
[realm deleteObject:object];
[realm commitWriteTransaction];
}
这里需要注意一点的是删除的对象必须要是realm数据库所管理的,否则会发生错误,导致程序不能继续运行;所以在删除之前我们可以再次创建这个删除更新下这个对象的实例:
object = [objectClass createOrUpdateInRealm:realm withValue:object];
看方法故名思义是不存在就创建,存在就更新,生成一个新的对象;这样这个要删除的对象就是被realm所管理。
数据的查询:分为精确查询和模糊查询
具体的用法demo:https://github.com/mymirror/iOSORM
其实我看重这个框架的除了建表以及查询方便,最主要的是数据库的迁移也很方便,目前数据库的迁移分为以下几种:
1、值的更新
example: [migration enumerateObjects:Person.className
block:^(RLMObject *oldObject, RLMObject *newObject) {
// 将两个 name 合并到 fullName 当中
newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",
oldObject[@"firstName"],
oldObject[@"lastName"]];
}];
2、属性的重命名
example: if (oldSchemaVersion < 1) {
// 重命名操作必须要在 enumerateObjects:
调用之外进行
[migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"];
}
3、线性迁移
example: 假如说,我们的应用有两个用户: JP 和 Tim。JP 经常更新应用,但 Tim 却经常跳过某些版本。所以 JP 可能下载过这个应用的每一个版本,并且一步一步地跟着更新构架:第一次下载更新后,数据库架构从 v0 更新到 v1;第二次架构从 v1 更新到 v2…以此类推,井然有序。相反,Tim 很有可能直接从 v0 版本直接跳到了 v2 版本。 因此,您应该使用非嵌套的 if (oldSchemaVersion < X) 结构来构造您的数据库迁移模块,以确保无论用户在使用哪个版本的架构,都能完成必需的更新。
*/
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
if (self.isSecu) {
config.encryptionKey = [self randomSec:userName];
}
//使用默认的目录,使用用户名来替换默认的文件名
config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]URLByAppendingPathComponent:userName]URLByAppendingPathExtension:@"realm"];
// 1. 设置新的架构版本。这个版本号必须高于之前所用的版本号(如果您之前从未设置过架构版本,那么这个版本号设置为 0)
NSInteger newVersion = version.integerValue;
config.schemaVersion = newVersion;
[[NSUserDefaults standardUserDefaults] setObject:version forKey:[NSString stringWithFormat:@"%@:%@",userName,@"ormVersion"]];
[[NSUserDefaults standardUserDefaults] synchronize];
// 2. 设置闭包,这个闭包将会在打开低于上面所设置版本号的 Realm 数据库的时候被自动调用
[config setMigrationBlock:^(RLMMigration *migration, uint64_t oldSchemaVersion){
if (oldSchemaVersion < newVersion) {
if (block) {
block(migration,oldSchemaVersion);
}
}
}];
[RLMRealmConfiguration setDefaultConfiguration:config];
[RLMRealm defaultRealm];
如果要求数据安全的话,可以对数据库进行加密,Realm 支持在创建 Realm 数据库时采用64位的密钥对数据库文件进行 AES-256+SHA2 加密。这样硬盘上的数据都能都采用AES-256来进行加密和解密,并用 SHA-2 HMAC 来进行验证。 每次您要获取一个 Realm 实例时,您都需要提供一次相同的密钥。下面给出一段demo给出的生成随机密钥的方法:
-
(NSData *)randomSec:(NSString *)indentify
{
NSString *str = [indentify stringByAppendingFormat:@"%@",[[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleIdentifier"]];
const char *kKeychainIdentifier = [str UTF8String];
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;
}uint8_t buffer[64];
status = SecRandomCopyBytes(kSecRandomDefault, 64, buffer);
NSAssert(status == 0, @"Failed to generate random bytes for key");
NSData *keyData = [[NSData alloc] initWithBytes:buffer length:sizeof(buffer)];// 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;
}
后续会对远程数据库连接处理以及model模型之间套用了解。