Welcome to the iOS Exploration series.
- IOS explores the alloc process
- IOS explores memory alignment &malloc source code
- IOS explores ISA initialization & pointing analysis
- IOS exploration class structure analysis
- IOS explores cache_T analysis
- IOS explores the nature of methods and the method finding process
- IOS explores dynamic method resolution and message forwarding mechanisms
- IOS explores the dyLD loading process briefly
- The loading process of iOS explore classes
- IOS explores the loading process of classification and class extension
- IOS explores isa interview question analysis
- IOS Explore Runtime interview question analysis
- IOS explores KVC principles and customization
- IOS explores KVO principles and customizations
- IOS explores the principle of multithreading
- IOS explores multi-threaded GCD applications
- IOS explores multithreaded GCD underlying analysis
- IOS explores NSOperation for multithreading
- IOS explores multi-threaded interview question analysis
- IOS Explore the locks in iOS
- IOS explores the full range of blocks to read
Writing in the front
Like GCD, NSOperation is a multi-threading technique that we often use in our daily development. This article will introduce the basic use of NSOperation, adding dependencies, and customizing
First use
NSOperation is an abstract class and depends on subclasses NSInvocationOperation and NSBlockOperation to implement it
Here is a description of NSOperation from the developer documentation
1.NSInvocationOperation
- The basic use
- (void)test {
// Process transactions
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(handleInvocation:) object:@"Felix"];
// Create a queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// The operation is queued
[queue addOperation:op];
}
- (void)handleInvocation:(id)operation {
NSLog(@ % @ - % @ "",op, [NSThreadcurrentThread]); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- Felix - <NSThread: 0x6000000422c0>{number = 3Name = (null)} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code
- Transactions are processed directly without adding hidden queues
- (void)test {
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"Felix"];
[op start];
}
Copy the code
This leads to the following mis-use code
- (void)test { NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(handleInvocation:) object:@"Felix"]; NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:op]; [op start]; } -------------------- Error log: ------------------- something is trying to start the receiver simultaneously from more than one thread' -------------------- Error log: -------------------Copy the code
The code above crashes because of the thread lifecycle:
queue addOperation:op
Action tasks that process transactions have been queued and let the thread runop start
Running an already running thread again causes thread clutter
2.NSBlockOperation
The difference between NSInvocationOperation and NSBlockOperation is:
- The former is similar to
target
In the form of - The latter is similar to
block
Formal – Functional programming, more readable business logic code
- (void)test {
// Initialize the add transaction
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(Task 1 -- - % @ "@"[NSThread currentThread]);
}];
// Add transaction
[bo addExecutionBlock:^{
NSLog(Task 2 -- - % @ "@"[NSThread currentThread]);
}];
// Callback listener
bo.completionBlock = ^{
NSLog(@" Done!!");
};
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo];
NSLog(@" Transaction added to NSOperationQueue"); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- added to the transactionNSOperationQueuetask1-- -- -- <NSThread: 0x6000032dc1c0>{number = 5, name = (null)} task2-- -- -- <NSThread: 0x6000032a1880>{number = 4, name = (null)} complete!! -------------------- The command output is -------------------Copy the code
NSOperationQueue is executed asynchronously, so the order of completion of tasks one and two is uncertain
3. Execution sequence
The following code can prove that the operation is executed asynchronously and concurrently with the queue
- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{
NSLog(@"%@---%d"[NSThreadcurrentThread], i); }]; }} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -NSThread: 0x600002771600>{number = 3, name = (null)}--0
<NSThread: 0x60000277ac80>{number = 7, name = (null)}--- 3
<NSThread: 0x600002774840>{number = 6, name = (null)}--2 -
<NSThread: 0x600002776a80>{number = 8, name = (null)}--4 -
<NSThread: 0x60000270c540>{number = 5, name = (null)}--- 1-------------------- The command output is -------------------Copy the code
4. Set the priority
- (void)test {
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
//sleep(1);
NSLog(@first operation %d -- %@, i, [NSThreadcurrentThread]); }}];// Set the highest priority
bo1.qualityOfService = NSQualityOfServiceUserInteractive;
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
for (int i = 0; i < 5; i++) {
NSLog(Second operation %d -- %@, i, [NSThreadcurrentThread]); }}];// Set the lowest priority
bo2.qualityOfService = NSQualityOfServiceBackground;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:bo1];
[queue addOperation:bo2];
}
Copy the code
Setting the priority of NSOperation will only make the CPU more likely to call it
- Do not use
sleep
— High priorityTask a
Prior to low priorityTask 2
<NSThread: 0x600002254280>{number = 6, name = (null)} 0x600002254280>{number = 6, name = (null)} 0x600002254280>{number = 6, name = (null)} 0x600002254280>{number = 6, name = (null)} 0x600002254280>{number = 6, name = (null)} 0x600002240340>{number = 7, name = (null)} 0x600002240340>{number = 7, name = (null)} 0x600002240340>{number = 7, name = (null)} 0x600002240340>{number = 7, name = (null)} NSThread: 0x600002240340>{number = 7, name = (null)}Copy the code
- use
sleep
Delay — high priorityTask a
Slower than lower priorityTask 2
<NSThread: 0x600002B35840 >{number = 7, name = (null)} 0x600002B35840 >{number = 7, name = (null)} 0x600002B35840 >{number = 7, name = (null)} 0x600002B35840 >{number = 7, name = (null)} 0x600002B35840 >{number = 7, name = (null)} 0x600002B3C700 >{number = 5, name = (null)} 0x600002B3C700 >{number = 5, name = (null)} 0x600002B3C700 >{number = 5, name = (null)} 0x600002B3C700 >{number = 5, name = (null)} NSThread: {number = 5, name = (null)}Copy the code
5. Communication between threads
- in
GCD
To make network requests asynchronously, then go back to the main thread to refresh the UI NSOperation
There are also operations similar to communication between threads
- (void)test { NSOperationQueue *queue = [[NSOperationQueue alloc] init]; queue.name = @"Felix"; [queue addOperationWithBlock:^{NSLog(@" request network %@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]); [[NSOperationQueue mainQueue] addOperationWithBlock:^{NSLog(@" refresh UI%@--%@", [NSOperationQueue currentQueue], [NSThread currentThread]); }]; }]; } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- request network < NSOperationQueue: 0 x7ff4a240bae0 > {name = 'Felix'}, < NSThread: 0x6000007DCF00 >{number = 5, name = (null)} Refresh UI<NSOperationQueue: 0x7ff4a24087d0>{name = 'NSOperationQueue Main Queue'}--<NSThread: 0 x60000078c8c0 > {number = 1, name = main} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code
6. Set the number of concurrent requests
- in
GCD
You can only use semaphores to set the number of concurrent requests - while
NSOperation
You can easily set the number of concurrent requests- By setting the
maxConcurrentOperationCount
To control the number of tasks to be executed in a single queue
- By setting the
- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"Felix";
queue.maxConcurrentOperationCount = 2;
for (int i = 0; i < 5; i++) {
[queue addOperationWithBlock:^{ // A task
[NSThread sleepForTimeInterval:2];
NSLog(@"%d-%@",i,[NSThreadcurrentThread]); }]; }} -------------------- The command output is -------------------1-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
0-<NSThread: 0x6000009348c0>{number = 8, name = (null)}
3-<NSThread: 0x6000009290c0>{number = 5, name = (null)}
2-<NSThread: 0x60000094b0c0>{number = 7, name = (null)}
4-<NSThread: 0x6000009348c0>{number = 8Name = (null)} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code
7. Add dependencies
Adding dependencies to NSOperation can control the task execution sequence
- (void)test {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
NSBlockOperation *bo1 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@ "request token");
}];
NSBlockOperation *bo2 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(Take token, request data 1);
}];
NSBlockOperation *bo3 = [NSBlockOperation blockOperationWithBlock:^{
[NSThread sleepForTimeInterval:0.5];
NSLog(@" Take data 1, request data 2");
}];
[bo2 addDependency:bo1];
[bo3 addDependency:bo2];
[self.queue addOperations:@[bo1,bo2,bo3] waitUntilFinished:YES];
NSLog(@" Done? I'm going to do something else."); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the request token with a token, the request data1With the data1, request data2Done? I want to do other things -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the output: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --Copy the code
Be careful not to add dependencies that lead to a loop, which will invalidate the dependency and print “XPC Connection Interrupted “on the console.
8. Task suspension, continuation, and cancellation
/ / hung
queue.suspended = YES;
/ / to continue
queue.suspended = NO;
/ / cancel
[queue cancelAllOperations];
Copy the code
However, there are often some strange problems in the use of the task is suspended, but still continue to execute several tasks before stopping
- Hang up before:
Task 3
,Task 4
Waiting to be dispatched - Suspension moment:
Task 3
,Task 4
They cannot be suspended when they have been scheduled out of the queue for execution - After the pending:
Task 3
,Task 4
Is executed by a thread while the original queue is suspended and cannot be scheduled
2. Customize the NSOperation cache mechanism
In our daily development, we often use SDWebImage to load network images. What is the principle? And how do we do that if we have to do it ourselves?
NSURL *imageURL = [NSURL URLWithString:model.imageUrl];
[cell.imageView sd_setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:@"Felix"]].return cell;
Copy the code
1. Download pictures
Use the image address to download the NSData data and convert it to the corresponding UIImage
cell.imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
Copy the code
Q1: The main thread using NSData to UIImage will cause a lag, which must be resolved
2.NSOperation is processed asynchronously
Use NSBlockOperation to asynchronously process the data and then refresh the UI on the main thread, thus resolving the lag problem
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" To download the image :%@", model.title);
/ / delay
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
/ / update the UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
Copy the code
Q2: Because of the cell’s caching mechanism, images are wasted every time they are downloaded, so try to save them
3. Model cache
- if
model
If there is data inmodel
Take out the picture load, save memory consumption - If none is available then asynchronously download the image data to
model
In the
if (model.image) {
NSLog(@" Get pictures from model :%@",model.title);
cell.imageView.image = model.image;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" To download the image :%@", model.title);
/ / delay
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
/ / update the UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
Copy the code
Q3: However, storing in the model will result in memory explosion. In this case, the model can only be cleaned, but there is more than image data in the model, so we have to find a new way to deal with the cache problem
4. Memory cache
- if
memory
If there is data inmemory
Take out the picture load, save memory consumption - If none is available then asynchronously download the image data to
Global mutable dictionary (memory)
In the
UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage) {
NSLog(Get image from memory :%@, model.title);
cell.imageView.image = cacheImage;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" To download the image :%@", model.title);
/ / delay
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
[self.imageCacheDict setValue:image forKey:model.imageUrl];
/ / update the UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
Copy the code
Q4: However, the memory will be reclaimed when the App is closed, resulting in the re-download of images every time you restart
5. Local cache
- The first asynchronous download writes the image data
The local cache
In the - The image can be loaded directly the second time it is loaded
The local cache
To save performance consumption
// The path is encapsulated and md5 processed
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {
NSLog(Get image from sandbox :%@,model.title);
cell.imageView.image = diskImage;
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" To download the image :%@", model.title);
/ / delay
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
/ / to save memory
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
/ / update the UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
Copy the code
Q5: Sandboxes are not as efficient as memory, so they have to be optimized
6. Local cache + memory cache
- if
memory
If there is data inmemory
Take out the picture to show - if
sandbox
If there is data insandbox
Extract the picture to display and store a copy in memory - If none is available, asynchronous downloads write the image data
The local cache
andMemory cache
In the
UIImage *cacheImage = self.imageCacheDict[model.imageUrl];
if (cacheImage) {
NSLog(Get image from memory :%@, model.title);
cell.imageView.image = cacheImage;
return cell;
}
UIImage *diskImage = [UIImage imageWithContentsOfFile:[model.imageUrl getDowloadImagePath]];
if (diskImage) {
NSLog(Get image from sandbox :%@,model.title);
cell.imageView.image = diskImage;
[self.imageCacheDict setValue:diskImage forKey:model.imageUrl];
return cell;
}
NSBlockOperation *bo = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@" To download the image :%@", model.title);
/ / delay
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:data];
/ / to save memory
[self.imageCacheDict setValue:image forKey:model.imageUrl];
[data writeToFile:[model.imageUrl getDowloadImagePath] atomically:YES];
/ / update the UI
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
cell.imageView.image = image;
}];
}];
[self.queue addOperation:bo];
return cell;
Copy the code
This is the easiest step for SDWebImage
Write in the back
I will package the content of the article into a Demo, you can download and have a look