##Realm
#### Creating a database
Use RLMRealm *realm = [RLMRealm defaultRealm]; Default database configuration, Or use + (nullable instancetype)realmWithConfiguration:(RLMRealmConfiguration *)configuration error:(NSError **)error; A full range of configurations.
The database is created after the method is called.
# # # # to create tables
The table creation is also done in the RLMRealm build method, which uses the objc_copyClassList to get all the registered classes, then extracts the classes inherited from RLMObject, and generates the corresponding tables from those classes.
So for the consumer, only the data model needs to be defined, and these model classes inherit from RLMObject.
- Attributes are defined just like normal classes, except that the attribute’s description keyword is removed. Probably because the getters/setters for the properties have all been overwritten, these properties are useless.
Realm ignores Objective‑C property attributes like nonatomic, atomic, strong, copy, weak, etc. These aren’t meaningful for Realm storage; it has its own optimized storage semantics.
- One to many property
RLM_ARRAY_TYPE(Book); @property (nonatomic) RLMArray<Book*><Book> *books;Copy the code
- Inverse relationship
There are books in the Library. Books can belong to the Library. If a Book is added to a new Library, the Library books name is changed, but the owner of the Book does not change. That is, two properties have an effect on each other, so that one depends on the other, so that the modification of one property can be maintained.
Let owner follow books:
// class book@property (readonly) RLMLinkingObjects *owners;
+(NSDictionary<NSString *,RLMPropertyDescriptor *> *)linkingObjectsProperties{
return @{
@"owners" : [RLMPropertyDescriptor descriptorWithClass:Library.class propertyName:@"books"]}; }Copy the code
- You can also set RLMObject to primaryKey, default defaultPropertyValues, ignored property ignoredProperties, required property requiredProperties, and index indexedProperties. More useful are the primary keys and indexes.
#### Data operation
# # # # #
Library *library = [[Library alloc] init];
[realm transactionWithBlock:^{
[realm addObject:library];
}];
Copy the code
Build and copy use transactions when stored to the database, just like normal objects. Once added, the object is managed by realm, and changes to its properties must be made within a write transaction or crash.
Terminating app due to uncaught exception ‘RLMException’, reason: ‘Attempting to modify object outside of a write transaction – call beginWriteTransaction on an RLMRealm instance first.’
# # # # # authorization
[realm transactionWithBlock:^{
bk.name = @"Harmonious World 2";
[realm deleteObject:bk];
}];
Copy the code
# # # # # query
RLMResults<Book *> *results = [Book objectsWhere:@"age == 101"];
Copy the code
The string after where is used to build the NSPredicate, so follow its syntax.
##### Automatic update
Two objects correspond to the same data in the database. If one object is modified and submitted to the database, the other object will automatically follow suit.
RLMResults *results = [Book objectsWhere:@"age == 119"];
Book *bk = results.firstObject;
NSLog(@"1: % @",bk.name);
RLMResults *results2 = [Book objectsWhere:@"age == 119"];
Book *bk2 = results2.firstObject;
NSLog(@"2: % @",bk2.name);
[realm transactionWithBlock:^{
bk.name = [NSString stringWithFormat:@"% @ _ modified + 1",bk.name];
}];
NSLog(@3: "% @",bk2.name);
Copy the code
The name of the third output is the modified name.
Query results can also be automatically updated
However, these updates are limited to the current thread, and the Realm does not support cross-thread data sharing. New threads require new RLMRealm objects and new data objects.
#### Data Migration
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration]; config.schemaVersion = 2; Config.migrationblock = ^(RLMMigration * _Nonnull Migration, Uint64_t oldSchemaVersion) {// Data migration codeif(oldSchemaVersion < 1) { [migration enumerateObjects:Book.className block:^(RLMObject * _Nullable oldObject, RLMObject * _Nullable newObject) { }]; }}; [RLMRealmConfigurationsetDefaultConfiguration:config];
Copy the code
Realm’s data migration logic is:
- judge
config.schemaVersion
Is the database version the same, different, and greater than 0? Perform data migration - Construct a new Realm based on the current class (the table of the realm is automatically generated by the class, which is an advantage), and execute
config.migrationBlock
Populate the new Realm with data - if
migrationBlock
Nothing. In fact, the new table will be created, but the old data will be lost. So there are two steps: 1. The new table is automatically built by realm 2. We are inmigrationBlock
To complete the population of the new table data - in
migrationBlock
It has the old version number so you can upgrade step by step. If you have a new version, add the migration code this time and do not delete the previous code next time, so that you can gradually update, such as:
Config.migrationblock = ^(RLMMigration * _Nonnull Migration, Uint64_t oldSchemaVersion) {// Data migration codeif(oldSchemSchemAversion < 1) {// Update operations from 0 to 1}if(oldSchemSchemAversion < 2) {// Update operations from 1 to 2}if(oldSchemSchemAversion < 3) {// Operation updates from 2 to 3}};Copy the code
If you update directly from version 0, 1, and 2 to version 3, you can also put step 3 in front of you.
The final Realm is not based on SQLite, but is a separate database.
##FMDB
FMDB is just a lightweight encapsulation of SQLite. There is no mapping of models and tables, no monitoring of data, no data migration assistance and other ORM features. It is just a functional encapsulation of the operations that would otherwise need to execute SQL.
# # # # to build library
NSString *dbPath = [NSHomeDirectory() stringByAppendingString:@"/Documents/book.db"];
FMDatabase *database = [FMDatabase databaseWithPath:dbPath];
Copy the code
Build an FMDatabase object. [database open] [database open]
# # # # to build table
Very common to execute SQL statement:
BOOL state = [database executeStatements:
@"create table if not exists Book (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)"];
if(! state) { NSLog(@"create table error!");
return;
}
Copy the code
#### Insert and update data
Book *bk = [[Book alloc] init];
bk.name = @"Big World";
bk.id = 123;
bk.age = 10000;
NSDictionary *bkKeyValues = @{
@"id":@(bk.id),
@"name":bk.name,
@"age":@(bk.age)
};
[database executeUpdate:
@"insert into Book values(:id, :name, :age)" withParameterDictionary:bkKeyValues];
Copy the code
The core method is executeUpdate:, which is built on sqlite3_prepare_v2.
Insert table Book (name) values(‘ SQLite Authority Guide ‘) when adding or updating data. However, if the data is long, a placeholder character is inserted in the content part to indicate the actual value and then bound:
- Insert into Book values(:name, :age)
- use
sqlite3_prepare_v2
Execute the statement - Then use
sqlite3_bind_xxx
A series of functions bind actual data. For example, if the name field is a string, useSqlite3_bind_text (STMT,1," Authoritative guide to SQlite ")
The index starts at 1.
There are several formats for placeholder characters:? ,? Parameter_index ($string, $string, $string, $string, $string, $string, $string, $string, $string, $string) The content passed in is the contents of the characters following the string.
In FMDB, if you update the dictionary to pass in data, you use the :string placeholder, with the string content: 1. Get the index from sqLite 2. Get the corresponding field data from the dictionary, associate the contents of 1 and 2, and pass in sqlite3_bind_xxx.
If data is passed as an array or argument, the index corresponds to the meaning of the data in the array:
NSArray *infos = @[bk.name, @(bk.age)];
[database executeUpdate:@"insert into Book (name, age) values(? ,?) " withArgumentsInArray:infos];
Copy the code
This is where the simplest question mark can be used, right? , the first placeholder uses the first data in the array, the second placeholder uses the second data, and so on. The variable method is passed in to correspond to the first parameter and the second parameter……
In other words, the core of this function is how to handle the corresponding relationship between the binding parameter index and the actual value. Once you understand this problem, this function is understood.
Updating data is the same logic as inserting data, and this method is also used.
#### Query data
The input logic for the query is the same as above, using SQLITe3_prepare_v2 to process SQL statements, using placeholder characters and SQLITe3_bind_XXX to pass in data. The values of where, limit, offset, and order by can be handled this way.
When inserting or updating data, you simply execute the operation, whereas when executing an executeQuery query, you only get the FMResultSet object and extract the data:
NSDictionary *queryInfos = @{@"name": @"insert array"The @"limitx": @ @ (2)"order": @"id desc"The @"ment": @"desc"};
FMResultSet *result = [database executeQuery:@"select name, id from Book where name = :name order by :order limit :limitx " withParameterDictionary:queryInfos];
Copy the code
Extraction data:
NSMutableArray *models = [[NSMutableArray alloc] init];
while ([result next]) {
Book *book = [[Book alloc] init];
book.id = [result longLongIntForColumnIndex:1];
book.name = [result stringForColumnIndex:0];
// book.age = [result longLongIntForColumnIndex:0];
[models addObject:book];
}
Copy the code
Keep using the next function to move to the next piece of data, and the inner core is SQlite3_step. Then use longLongIntForColumnIndex a series of methods, such as the attribute values are extracted from the one by one, assign values to the object.
This is the most painful part of native SQLite processing, where the “one data -> one object” transition is handled field by field. Like this:
- Fields are painful to write and are boring code
- It takes a lot of work to write one code for each table/model, and this code needs to be changed whenever the model changes.
At this point, ORM is needed to save the world. Unfortunately, CoreData has many functions in addition to this work, and even some details are closed, resulting in a rather troublesome use.
ORM, short for Object Relational Mapping, can be well understood from the work here. Object relational mapping, mapping one kind of object to another kind of object, the basic work here is to automatically convert a piece of data in the database (database object) to the model object we define.
#### Multi-threaded environment
Use FMDatabaseQueue to call:
FMDatabaseQueue *dbQueue = [[FMDatabaseQueue alloc] initWithPath:dbPath];
[dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
[db executeUpdate:@"insert into Book values(200, 'dbQueueInsert', 2013)"];
}];
Copy the code
This is a conservative solution:
dispatch_sync(_queue, ^() { FMDatabase *db = [self database]; block(db); . }... _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
Copy the code
This _queue is a serial queue, so using FMDatabaseQueue to access the database, no matter which thread you call it from, it will end up in this _queue, which is serial, so there will be no concurrency. Without concurrency, there would be no multithreading problems.
If you do this, then you have a global FMDatabaseQueue, use it everywhere, whether different queues are restricted to each other, whether they are concurrent, or whether they cause problems.
Also, because _queue is serial and the synchronous method dispatch_sync is used, nested calls cause deadlocks. FMDB does queue detection, calling inDatabase in the same queue will crash.
// Use dispatch_get_specific to get the queue id FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); assert(currentSyncQueue ! = self &&"inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
Copy the code
This is conservative because it completely isolates the operation of multithreaded access, where each operation takes place sequentially, without concurrency. But at least reading and reading can coexist.
# # other libraries
Key-value Database YTKKeyValueStore
Wechat open source database WCDB
# # #
Sqlite has a number of features that can support ORM implementation, so it can implement a simple ORM based on FMDB, such as runtime load create table, without SQL add, delete, change query. Considering that wechat open source database WCDB and Realm are already very mature solutions, I didn’t make much use of them, so it was just an attempt to familiarize myself with the idea of ORM and SQLite. For those interested, take a look at TFDatabaseMapper.