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:opAction tasks that process transactions have been queued and let the thread run
  • op startRunning an already running thread again causes thread clutter

2.NSBlockOperation

The difference between NSInvocationOperation and NSBlockOperation is:

  • The former is similar totargetIn the form of
  • The latter is similar toblockFormal – 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 usesleep— High priorityTask aPrior 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
  • usesleepDelay — high priorityTask aSlower 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

  • inGCDTo make network requests asynchronously, then go back to the main thread to refresh the UI
  • NSOperationThere 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

  • inGCDYou can only use semaphores to set the number of concurrent requests
  • whileNSOperationYou can easily set the number of concurrent requests
    • By setting themaxConcurrentOperationCountTo control the number of tasks to be executed in a single queue
- (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 4Waiting to be dispatched
  • Suspension moment:Task 3,Task 4They cannot be suspended when they have been scheduled out of the queue for execution
  • After the pending:Task 3,Task 4Is 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

  • ifmodelIf there is data inmodelTake out the picture load, save memory consumption
  • If none is available then asynchronously download the image data tomodelIn 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

  • ifmemoryIf there is data inmemoryTake out the picture load, save memory consumption
  • If none is available then asynchronously download the image data toGlobal 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 dataThe local cacheIn the
  • The image can be loaded directly the second time it is loadedThe local cacheTo 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

  • ifmemoryIf there is data inmemoryTake out the picture to show
  • ifsandboxIf there is data insandboxExtract the picture to display and store a copy in memory
  • If none is available, asynchronous downloads write the image dataThe local cacheandMemory cacheIn 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