[Original] FMDB source code reading (3), fmdb source code reading

Source: Internet
Author: User
Tags savepoint sql error

[Original] FMDB source code reading (3), fmdb source code reading
[Original] FMDB source code reading (3)

Please specify the source for this reprinted article --Polobymulberry-blog

1. Preface

The best thing about FMDB is thatMulti-thread processing. Therefore, this article mainly studies the implementation of multi-thread processing in FMDB. In the latest version of FMDB, The FMDatabaseQueue class is used for multithreading.

2. FMDatabaseQueue example
// Create. It is best to put FMDatabaseQueue * queue = [FMDatabaseQueueDatabaseQueueWithPath: APath]; // use [queue
InDatabase
: ^ (FMDatabase * db) {[db executeUpdate: @ "insert into myTable VALUES (?) ", [NSNumber numberWithInt: 1]; [db executeUpdate: @" insert into myTable VALUES (?) ", [NSNumber numberWithInt: 2]; [db executeUpdate: @" insert into myTable VALUES (?) ", [NSNumber numberWithInt: 3]; FMResultSet * rs = [db executeQuery: @" select * from foo "]; while ([rs next]) {//… }]; // If you want to support the transaction [queue
InTransaction
:^(FMDatabase *db, BOOL *rollback) {    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:1]];    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:2]];    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:3]];    if (whoopsSomethingWrongHappened) {        *rollback = YES;        return;    }    // etc…    [db executeUpdate:@"INSERT INTO myTable VALUES (?)", [NSNumber numberWithInt:4]];}];

We can see that the multi-threaded Implementation of FMDB mainly depends on the FMDatabaseQueue class. Next, let's take a look at the internal implementation of FMDatabaseQueue based on the above example.

2.1 + [FMDatabaseQueue databaseQueueWithPath:]
// Call initWithPath: The function constructs an FMDatabaseQueue object + (instancetype) Certificate :( NSString *) aPath {FMDatabaseQueue * q = [[self alloc] initWithPath: aPath]; fmdbautorelissue (q ); return q ;}

View the initWithPath: function, and find that the essence is to call the-(instancetype) initWithPath :( NSString *) aPath flags :( int) openFlags vfs :( NSString *) vfsName function.

// Use aPath as the database name, and input openFlags and vfsName as the parameters of openWithFlags: vfs: Function
// Initialize a database and the corresponding queue-(instancetype) initWithPath :( NSString *) aPath flags :( int) openFlags vfs :( NSString *) vfsName {// except for defining another _ queue, the initialization of other parts and FMDatabase is no different. self = [super init]; if (self! = Nil) {_ db = [[self class] databaseClass] databaseWithPath: aPath]; FMDBRetain (_ db); # if SQLITE_VERSION_NUMBER> = 3005000 BOOL success = [_ db openWithFlags: openFlags vfs: vfsName]; # else BOOL success = [_ db open]; # endif if (! Success) {NSLog (@ "cocould not create database queue for path % @", aPath); FMDBRelease (self); return 0x00;} _ path = FMDBReturnRetained (aPath ); // a serial queue _ queue = dispatch_queue_create ([NSString stringWithFormat: @ "fmdb. % @ ", self] UTF8String], NULL);/** specify a kDispatchQueueSpecificKey string for the _ queue GCD queue and bind it with self (the current FMDatabaseQueue object. You can use this string to obtain the bound object (self) in the future ). Of course, make sure that the GCD queue being executed is the _ queue you specified earlier. Does it feel like the objc_setAssociatedObject function. The role of this step will be explained in detail in the inDatabase function. */Dispatch_queue_set_specific (_ queue, kDispatchQueueSpecificKey, (_ bridge void *) self, NULL); _ openFlags = openFlags;} return self ;}
2.2-[FMDatabaseQueue inDatabase:]

Note that the inDatabase parameter is a block. This block encapsulates database operations. In addition, this block is executed synchronously in inDatabase.

-(Void) inDatabase :( void (^) (FMDatabase * db) block {/* use dispatch_get_specific to check whether the current queue is the previously set _ queue. If yes, if kDispatchQueueSpecificKey is used as the parameter to be passed to dispatch_get_specific, the returned value is not null and the returned value should be the FMDatabaseQueue object bound to the initWithPath function above. Some people say that there may be other queue besides the current queue? This is the purpose of FMDatabaseQueue. You can create multiple FMDatabaseQueue objects to execute different SQL statements concurrently. In addition, why do we need to determine whether this queue is currently executed? To prevent deadlocks! */FMDatabaseQueue * currentSyncQueue = (_ bridge id) dispatch_get_specific (kDispatchQueueSpecificKey); assert (currentSyncQueue! = Self & "inDatabase: was called reentrantly on the same queue, which wowould lead to a deadlock"); FMDBRetain (self ); // synchronously execute block dispatch_sync (_ queue, ^ () {FMDatabase * db = [self database]; block (db) in the current queue ); // as you can see in the following section, the DEBUG macro is defined, which is obviously used for debugging. If ([db hasOpenResultSets]) {NSLog (@ "Warning: there is at least one open result set around after discovery Ming [FMDatabaseQueue inDatabase:]"); # if defined (DEBUG) & DEBUG NSSet * openSetCopy = FMDBReturnAutoreleased ([[db valueForKey: @ "_ openResultSets"] copy]); for (NSValue * release in openSetCopy) {FMResultSet * rs = (FMResultSet *) [rsInWrappedInATastyValueMeal pointerValue]; NSLog (@ "query: '% @'", [rs query]) ;}# endif }}); FMDBRelease (self );}

In fact, we can see from this function how FMDatabaseQueue achieves multithreading:

-(Void) inTransaction :( void (^) (FMDatabase * db, BOOL * rollback) block {[self beginTransaction: NO withBlock: block];}

We can see that the beginTransaction: withBlock: function is directly encapsulated internally. Let's look at beginTransaction: withBlock: function.

-(Void) beginTransaction :( BOOL) useDeferred withBlock :( void (^) (FMDatabase * db, BOOL * rollback) block {FMDBRetain (self); dispatch_sync (_ queue, ^ () {BOOL shouldRollback = NO; if (useDeferred) {// if a delayed transaction is used, call the function. The following describes the function.
// To set useDeferred to YES, call the inDeferredTransaction function [[self database] beginDeferredTransaction];} else {// exclusive transaction is used by default, the following is a detailed explanation of exclusive transactions [[self database] beginTransaction];} // note that in addition to creating the corresponding database transactions, you also need to select whether to roll back as needed // For example, if the above database operation has an error, you can set the need to roll back, that is, the returned shouldRollback is YES block ([self database], & shouldRollback); // if rollback is required, call the rollback function of FMDatabase if (shouldRollback) {[[self database] rollback];} // If rollback is not required, call the FMDatabase commit function to submit the corresponding SQL operation else {[self database] commit] ;}}. FMDBRelease (self);} // roll back by executing the rollback transaction statement-(BOOL) rollback {BOOL B = [self executeUpdate: @ "rollback transaction"]; // Since the rollback has been completed, the _ inTransaction attribute indicating whether the transaction is being performed must also be set to NO if (B) {_ inTransaction = NO;} return B ;} // execute the commit transaction statement to execute the commit transaction operation-(BOOL) commit {BOOL B = [self executeUpdate: @ "commit tran Saction "]; // Since the transaction has been committed, the _ inTransaction attribute indicating whether the transaction is in progress must also be set to NO if (B) {_ inTransaction = NO ;} return B;} // a delayed transaction means that no locks are performed before database operations. By default, // If you only start a transaction with BEGIN, the transaction is DEFERRED, and it does not obtain any lock-(BOOL) beginDeferredTransaction {BOOL B = [self executeUpdate: @ "begin deferred transaction"]; if (B) {_ inTransaction = YES;} return B;} // exclusive is performed by default) the essence of the EXCLUSIVE operation is to obtain the EXCLUSIVE lock before reading and writing the database. The exclusive lock is nothing more than // tell other database connections: You should stop chasing her. She is my wife. -(BOOL) beginTransaction {BOOL B = [self executeUpdate: @ "begin exclusive transaction"]; if (B) {_ inTransaction = YES;} return B ;}
2.4-[FMDatabaseQueue inSavePoint:]

A savepoint is similar to a game archive. Generally, rollback is equivalent to restarting the game. After a savepoint is added, it is equivalent to returning to the archive location and then playing the game. There is an inSavePoint Method Relative to inDatabase and inTransaction (equivalent to the inDatabase function with the save point function added ).

/* The save point function is only used in SQLite3.7 and later versions, so the following code adds # if SQLITE_VERSION_NUMBER> = 3007000 # else # endif */-(NSError *) inSavePoint :( void (^) (FMDatabase * db, BOOL * rollback) block {# if SQLITE_VERSION_NUMBER> = 3007000 static unsigned long savePointIdx = 0; _ block NSError * err = 0x00; FMDBRetain (self); // synchronously execute dispatch_sync (_ queue, ^ () {// set the savepoint name, that is, set a name for the game archive NSString * name = [NSString stringWithFor Mat: @ "savePoint % ld", savePointIdx ++]; // by default, BOOL shouldRollback = NO is not rolled back. // save point is first archived before the block is executed ). If any problem occurs, directly return the archive (save point) if ([[self database] startSavePointWithName: name error: & err]) {block ([self database], & shouldRollback ); // if rollback is required, call rollbackToSavePointWithName: error: rollback to the archive location (savepoint) if (shouldRollback) {[[self database] rollbackToSavePointWithName: name error: & err];} // remember to release the archive [[self database] releaseSavePointWithName: name error: & err];} no matter whether the block is rolled back or not. FMDBRelease (self); return err; # else NSString * errorMessage = NSLocalizedString (@ "Save point functions require SQLite 3.7", nil); if (self. logsErrors) NSLog (@ "% @", errorMessage); return [NSError errorWithDomain: @ "FMDatabase" code: 0 userInfo: @ {NSLocalizedDescriptionKey: errorMessage}]; # endif}
// Call the savepoint $ savepointname SQL statement to archive database operations-(BOOL) startSavePointWithName :( NSString *) name error :( NSError **) outErr {# if SQLITE_VERSION_NUMBER >=3007000 NSParameterAssert (name); NSString * SQL = [NSString stringWithFormat: @ "savepoint '% @';", FMDBEscapeSavePointName (name)]; return [self executeUpdate: SQL error: outErr withArgumentsInArray: nil orDictionary: nil orVAList: nil]; # else NSString * errorMessage = NSLocalizedString (@ "Save point functions require SQLite 3.7 ", nil); if (self. logsErrors) NSLog (@ "% @", errorMessage); return NO; # endif}
// Use the SQL statement of release savepoint $ savepointname to delete the archive, mainly to release the resource-(BOOL) releaseSavePointWithName :( NSString *) name error :( NSError **) outErr {# if SQLITE_VERSION_NUMBER >=3007000 NSParameterAssert (name); NSString * SQL = [NSString stringWithFormat: @ "release savepoint '% @';", FMDBEscapeSavePointName (name)]; return [self executeUpdate: SQL error: outErr withArgumentsInArray: nil orDictionary: nil orVAList: nil]; # else NSString * errorMessage = NSLocalizedString (@ "Save point functions require SQLite 3.7 ", nil); if (self. logsErrors) NSLog (@ "% @", errorMessage); return NO; # endif}
// The SQL statement that calls rollback transaction to savepoint $ savepointname is returned to the archive-(BOOL) rollbackToSavePointWithName :( NSString *) name error :( NSError **) outErr {# if SQLITE_VERSION_NUMBER> = 3007000 NSParameterAssert (name); NSString * SQL = [NSString stringWithFormat: @ "rollback transaction to savepoint '% @';", callback (name)]; return [self executeUpdate: SQL error: outErr withArgumentsInArray: nil orDictionary: nil orVAList: nil]; # else NSString * errorMessage = NSLocalizedString (@ "Save point functions require SQLite 3.7 ", nil); if (self. logsErrors) NSLog (@ "% @", errorMessage); return NO; # endif}
3. FMDatabasePool (FMDatabaseQueue is recommended)

Tip:

Unless you really know under what circumstances (for example, all operations are read Operations) You can use the FMDatabasePool. Otherwise, use the FMDatabaseQueue whenever possible. Otherwise, a deadlock may occur.

4. Summary

The several frequently-used classes of FMDB are basically learned. FMDB code is not difficult. The core is SQLite3 and database knowledge. More importantly, you need to know the best practices in the real environment.

5. References
  • 53sqlite transactions and locks
  • SQLite Transaction (Transaction)

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.