preface
Through step by step development, we have realized the addition, deletion, check and change of database, and support various types of data storage, such as: All the basic data types, NSArray, NSMutableArray, NSDictionary, NSMutableDictionary, UIImage, NSURL, UIColor, NSSet, NSRange, NSAttributedString, NSData, custom models and complex scenarios where arrays, dictionaries, and models are nested within each other.
Then we perfectly open the database, create database tables, parse model objects, insert data, update data, database upgrade, data migration, close the database series of steps encapsulated into a method, one line of code intelligent implementation of complex model data storage. If you want to see how each part is implemented, head over to the previous article, Portal:
- Create a database for OC from 0 (1)— Create, open, close the database, create the corresponding database table according to the model.
- Create a database for OC from 0 (2)— Model field parsing, insert or update model, query database data.
- Create a database for OC from 0 (3)— Delete data, database upgrade (update), data migration, field rename.
- Start from 0 to create a database for OC (4)Collection and custom model storage and query, model, array, dictionary, string conversion between
This article focuses on multi-threaded security, then follows with a look at common multi-threaded security techniques and a necessary scenario for using @AutoRelease under ARC, and finally shares a bug we encountered during unit testing.
Function implementation
Before we implement the functionality, what do we need to do with multi-threaded security? To put it simply, we want to ensure that the operation of the database is safe when multiple threads operate at the same time. We can also say that we want to ensure that all database operations, no matter which thread comes from, wait for the previous operation to complete before executing this operation, to avoid resource competition and conflict.
Then we will learn about the means to ensure the safety of multithreading under OC. For OC, the most common ones are atomic, and then there are NSLock lock, @synchronized, GCD semaphore and serial queue.
When we struggle to choose what kind of solution, we can first look at the predecessors of the open source database is how to do thread-safe, draw lessons from it, finally, we summed up the two better solution: a solution is used by FMDB synchronous serial queue: all operation with a serial queue, queue for operation. The other is to use the GCD semaphore dispatch_semaphore_t. Combined with the code we wrote before and according to our current database scheme, we quickly compared which is more suitable for us. Finally, we chose GCD semaphore. With this scheme, our code basically does not change.
There are three semaphore operations in GCD:
- Dispatch_semaphore_create Creates a Semaphore
- Dispatch_semaphore_wait Wait for signals
- Dispatch_semaphore_signal Sends a signal
A brief introduction to these three functions:
dispatch_samaphore_t dispatch_semaphore_create(long value); This function takes a long integer argument, which we can think of as the total number of signals; Long dispatch_semaphore_wait(dispatch_semaphore_t dSEMa, dispatch_time_t timeout); If the value of the dsema semaphore is greater than 0, the thread continues to execute the statement and decrement the semaphore value by 1. If desema is less than or equal to zero, then this function blocks the current thread waiting for timeout (note that timeout is dispatch_time_t); Long dispatch_semaphore_signal(dispatch_semaphore_tdSEMa) increments dsema (dsema) by 1;Copy the code
At the same time, considering that our current methods are all class methods, we need an instance to remember the value of desema, so we open a singleton object for CWSqliteModelTool to record the value of desema, set the total amount of signals to 1, and then wait for the semaphore before each method that performs database operation. If the current semaphore is greater than 0, we perform an operation on the database and decrement the semaphore by 1. When the operation is complete, we send a signal to increase the semaphore by 1, so that other waiting threads can start executing the operation.
- (instancetype)init
{
self = [super init];
ifSelf. dsema = dispatch_semaphore_create(1); self.dsema = dispatch_semaphore_create(1); }returnself; } static CWSqliteModelTool * instance = nil; + (instancetype)shareInstance { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[CWSqliteModelTool alloc] init]; });returninstance; + (NSArray *)queryAllModels (Class) CLS uid (NSString *)uid targetId (NSString *)targetId { Move down, otherwise wait dispatch_semaphore_wait([[self shareInstance] dsema], DISPATCH_TIME_FOREVER); NSString *tableName = [CWModelTool tableName:cls targetId:targetId]; NSString *sql = [NSString stringWithFormat:@"select * from %@", tableName]; NSArray <NSDictionary *>*results = [CWDatabase querySql:sql uid:uid]; [CWDatabase closeDB]; // Dispatch_semaphore_signal ([[self shareInstance] dsema]);return [self parseResults:results withClass:cls];
}
Copy the code
We write the same code in other ways, then we use the method of insert and delete data unit test, test scheme for open 3 sliver threads, each thread insert 1000 complex models respectively, when the end of the data into, again using 3 sliver thread to delete the article 2900 of the data, the remaining 100 data, Then let’s unit test:
When using the unit test, and share a pit ~ we found we found a data didn’t insert success or occasionally by inserting one or two data, we test our code over and over again, is no problem in theory, in the end we locate the child thread queue never task execution, after thinking in the end we come to the conclusion: Unit tests are run in the main thread, we use the asynchronous thread does not block the main thread of the running, so the test case running smoothly from the first row to the last line, and unit test run after the last line is out of the program, the program is out of the asynchronous thread operation of course we can no longer be carried out
So instead of using unit tests (or opening a runloop in the unit test itself to keep the program from exiting), we test inside the program and post our very long test code (inside the viewController):
The #pragma mark-for loop does not use the multi-threaded operation of Autoreleasepool
- (void)testMultiThreadingSqliteMore {
dispatch_queue_t queue1 = dispatch_queue_create("CWDBTest1", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue2 = dispatch_queue_create("CWDBTest2", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue3 = dispatch_queue_create("CWDBTest3", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t queue4 = dispatch_queue_create("CWDBTest4", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_group_enter(group);
dispatch_group_enter(group);
dispatch_async(queue1, ^{
for (int i = 1; i < 1000; i++) {
Student *stu = [self studentWithId:i];
BOOL result = [CWSqliteModelTool insertOrUpdateModel:stu uid:@"Chavez" targetId:nil];
NSLog(@"result : %d %zd",result,stu.stuId);
}
NSLog(@"End of --------------- Group 1");
dispatch_group_leave(group);
});
dispatch_async(queue2, ^{
for (int i = 1000; i < 2000; i++) {
Student *stu = [self studentWithId:i];
BOOL result = [CWSqliteModelTool insertOrUpdateModel:stu uid:@"Chavez" targetId:nil];
NSLog(@"result : %d %zd",result,stu.stuId);
}
NSLog(@"End of --------------- Group 2");
dispatch_group_leave(group);
});
dispatch_async(queue3, ^{
for (int i = 2000; i < 3000; i++) {
Student *stu = [self studentWithId:i];
BOOL result = [CWSqliteModelTool insertOrUpdateModel:stu uid:@"Chavez" targetId:nil];
NSLog(@"result : %d %zd",result,stu.stuId);
}
NSLog(@"End of --------------- Group 3"); dispatch_group_leave(group); }); // Call dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@)"---------------------- end of insertion");
dispatch_async(queue4, ^{
for(int i = 1; i < 1000; i++) { Student *stu = [self studentWithId:i]; // Delete data BOOL result = [CWSqliteModelTool deleteModel:stu uid:@"Chavez" targetId:nil];
NSLog(@"delete result : %d %zd",result,stu.stuId); }}); dispatch_async(queue1, ^{for(int i = 2000; i < 3000; i++) { Student *stu = [self studentWithId:i]; // Delete data BOOL result = [CWSqliteModelTool deleteModel:stu uid:@"Chavez" targetId:nil];
NSLog(@"delete result : %d %zd",result,stu.stuId); }}); Dispatch_async (queue2, ^{// Delete data BOOL result = [CWSqliteModelTool deleteModel:[Student class] columnNames:@[@"stuId"The @"stuId"] relations:@[@(CWDBRelationTypeMoreEqual),@(CWDBRelationTypeLess)] values:@[@(1000),@(1900)] isAnd:YES uid:@"Chavez" targetId:nil];
NSLog(@"delete result : %d 1000-1900",result);
});
});
}
#pragma Mark - Get a model quickly
- (Student *)studentWithId:(int)stuId {
School *school1 = [[School alloc] init];
school1.name = @"Peking University";
school1.schoolId = 2;
School *school = [[School alloc] init];
school.name = @"Tsinghua University";
school.schoolId = 1;
school.school1 = school1;
Student *stu = [[Student alloc] init];
stu.stuId = stuId;
stu.name = @"Baidu";
stu.age = 100;
stu.height = 190;
stu.weight = 140;
stu.dict = @{@"name" : @"chavez"}; // Dictionary nested model stu. DictM = [@{@"Tsinghua University" : school , @"Peking University" : school1 , @"money": @(100)} mutableCopy]; Stu. ArrayM = [@[@"chavez"The @"cw"The @"ccww", @ {@"Tsinghua University": school}] mutableCopy]; / / an array of nested model stu. Array = @ [@ (1), @ (2), @ (3), school, school1]; NSAttributedString *attributedStr = [[NSAttributedString alloc] initWithString:@"attributedStr,attributedStr"]; stu.attributedString = attributedStr; Stu. school = school; UIImage *image = [UIImage imageNamed:@"001"];
NSData *data = UIImageJPEGRepresentation(image, 1);
stu.image = image;
stu.data = data;
return stu;
}
Copy the code
Then execute to get the test results, but during repeated side tests, we found a problem, as shown below:
For experienced people, they must immediately locate where the problem, even when they are writing code can know it will be a problem, and I was inexperienced, I thought for a moment, because there is a little theory knowledge, in the end may be a for loop to locate created a large number of temporary variables are not timely release, Then, based on what you’ve seen before using @AutoReleasepool torelease temporary variables, the official Apple documentation hasUsing Autorelease Pool BlocksTo avoid spikes in memory usage, use @Autoreleasepool to free up memory when a large number of intermediate temporary variables are generated. Finally, we modified the code and tested it:
After we solved the problem of the multithreading safety we found that the bulk insert data since there may be need, we will add on an interface to handle bulk insert operations, bulk insert is actually for our users to insert for loop, but in the process of insert, we insert a transaction control operation, And the insertion process is less than the user is to open and close the database operation and check whether the database table needs to be updated each time the data is inserted.
#pragma mark - Tests batch insert data
- (void)testGroupInsert {
NSMutableArray *arr = [NSMutableArray array];
for (int i = 1; i < 2000; i++) {
Student *stu = [self studentWithId:i];
[arr addObject:stu];
}
NSLog(@"Start inserting data"); // 2017-12-23 16:25:46.145023+0800 CWDB[14678:1604328] Start Insert data BOOL result = [CWSqliteModelTool insertOrUpdateModels:arr uid:@"Chavez" targetId:nil];
NSLog(@"-- %zd-- insert end",result); // 2017-12-23 16:25:48.466352+0800 CWDB[14678:1604328] --1 insert end Dispatch_async (dispatch_get_global_queue(0, 0), ^{NSLog(@"--------------- Group 1 begins"); / / the 2017-12-23 16:25:48. 466587 + 0800 CWDB (14678-1604407) -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- start group 1for (int i = 2000; i < 4000; i++) {
@autoreleasepool {
Student *stu = [self studentWithId:i];
BOOL result = [CWSqliteModelTool insertOrUpdateModel:stu uid:@"Chavez" targetId:nil];
NSLog(@"result : %d %zd",result,stu.stuId);
}
}
NSLog(@"End of --------------- Group 1"); // 2017-12-23 16:25:56.247631+0800 CWDB[14678:1604407] --------------- end Total 8 seconds (and autoReleasepool releases temporary variables)}; }Copy the code
Finally, we compared the time of batch insertion with that of 2000 pieces of data, and found that the time consumed by batch insertion was much lower than that consumed by single insertion.
After writing the function, we made a small part of cache optimization for the project. Without considering dynamically adding attributes to the model, we must get the same model member variables and types every time. Therefore, we first used NSCache here to cache all the model member variable names and types. So you don’t have to parse the model every time. Secondly, in the judgment whether to need to update the database table structure, we also do the cache, during the program is running, as long as the updated once, update later all need not to judge, because member variables constant, table structure will not change, this is to optimize performance of small, can be appropriate when do think about it.
This end
Here, we package the database function has been developed (in fact, it is not as difficult to package a database, you can also ~) back to the first article of the military command: our package is simple to use, safe and reliable, comprehensive features, we do what we say. Add, delete, check, change all operations need only one line of code. Even if you add an object to an array, it takes two steps: initialize an array, and then add an object to the array, whereas we insert a model into the database in one step, insertOrUpdateModel:.
In the following, we will encapsulate the code, a small amount of reconstruction and optimization, remove some unnecessary exposure method and the corresponding unit tests, as far as possible let the API simple and remove some duplicate code encapsulation, the annotation completion after a lot of test scenarios to test again, and we will get our CWDB recommended for everyone to use, If you’re interested in learning more or wrapping a database yourself, head over to the first article in this series (the link is at the beginning of this article). The code for each article has a separate tag under Github release, which you can find and download.
Github: CWDB ——tag is 1.4.0——-
(Note: if you want to run the database directly, you must change the database storage path at the beginning of cwDatabase. m. I wrote it on the desktop of my computer during the development and debugging phase. If you do not change the path, the database will fail to open.)
Finally, if you feel useful, I hope you can like this article and give Github a star to encourage you. Thank you. Welcome everyone to throw the issue to me, there are better ideas welcome everyone to leave a message.
To give you a 0 coupling imitation QQ sideslip framework, a real line of code implementation, more you pull me 😁: a line of code integration with ultra-low coupling sideslip function