The basic content

This section mainly introduces the content related to multi-threading in iOS. Firstly, it briefly introduces the use of NSOperation, and then realizes the management between tasks by combining GCD, such as the processing after each task is completed, the dependency between tasks, and the processing after all tasks are completed. AFNetworking source code is used to analyze its implementation.

NSoperation

There are three ways to complete a task through NSOpeartion:

  • NSInvocationOperation
  • NSBlockOperation
  • It inherits NSOperation to implement specific task operations

NSInvocationOperation

- (void)viewDidLoad {

    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.
    NSOperationQueue *queue = [NSOperationQueue new];

    NSInvocationOperation *ope = [[NSInvocationOperation alloc] initWithTarget:
    self selector:@selector(invocationOperationCall1) object:nil];

    NSInvocationOperation *ope2 = [[NSInvocationOperation alloc] initWithTarget
    :self selector:@selector(invocationOperationCall2) object:nil]; [queue addOperation:ope]; [queue addOperation:ope2]; } - (void)invocationOperationCall1{

    NSLog(@ "% @".NSStringFromSelector(_cmd));

    NSLog(@ "% @"[NSThreadcurrentThread]); } - (void)invocationOperationCall2{

    NSLog(@ "% @".NSStringFromSelector(_cmd));

    NSLog(@ "% @"[NSThread currentThread]);

}
Copy the code

Output:

2016- 01- 13 14:23:54.791 operation+dispatch_group[6032:2162368]
invocationOperationCall1------<NSThread: 0x7fb6ad8019a0>{number = 3, name = (
    null)}

2016- 01- 13 14:23:54.791 operation+dispatch_group[6032:2162371] 
invocationOperationCall2------<NSThread: 0x7fb6adaec8f0>{number = 2, name = (
    null)}
Copy the code

You can see from the logs that the tasks are executed on different threads

NSInvocationOperation *ope = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationCall1) object:nil];
NSInvocationOperation *ope2 = [[NSInvocationOperation alloc] initWithTarget:
self selector:@selector(invocationOperationCall2) object:nil];
[ope start];
[ope2 start];
Copy the code

Output:

2016- 01- 13 14:27:38.226 operation+dispatch_group[6053:2164444] 
invocationOperationCall1------<NSThread: 0x7fae69508370>{number = 1, name = 
    main}

2016- 01- 13 14:27:38.226 operation+dispatch_group[6053:2164444]
invocationOperationCall2------<NSThread: 0x7fae69508370>{number = 1, name = 
    main}
Copy the code

As you can see from the log, if the task is not added to the queue, it is executed synchronously in the current thread.

You can also add dependencies between NSOperation objects so that each task is executed in a certain order

NSOperationQueue *queue = [NSOperationQueue new];

NSInvocationOperation *ope = [[NSInvocationOperation alloc] initWithTarget:self
 selector:@selector(invocationOperationCall1) object:nil];

NSInvocationOperation *ope2 = [[NSInvocationOperation alloc] initWithTarget:
self selector:@selector(invocationOperationCall2) object:nil];

[ope addDependency:ope2];

[queue addOperation:ope];

[queue addOperation:ope2];
Copy the code

Output:

2016- 01- 13 19:27:49.182 operation+dispatch_group[13865:2284382] 
invocationOperationCall2------<NSThread: 0x7fa26300ff40>{number = 2, name = (
    null)}

2016- 01- 13 19:27:49.183 operation+dispatch_group[13865:2284382] 
invocationOperationCall1------<NSThread: 0x7fa26300ff40>{number = 2, name = (
    null)}
Copy the code

Although ope and ope2 are parallel, ope waits until ope2 completes execution after adding dependencies.

NSBlockOperation

NSBlockOperation *ope = [NSBlockOperation blockOperationWithBlock:^{

    NSLog(@"task A, %@"[NSThread currentThread]);

}];

[ope addExecutionBlock:^{

    NSLog(@"task B, %@"[NSThread currentThread]);

}];

[ope addExecutionBlock:^{

    NSLog(@"task C, %@"[NSThread currentThread]);

}];

[ope start];
Copy the code

Output:


2016- 01- 13 14:17:36.594 operation+dispatch_group[5990:2158464] 
task A, <NSThread: 0x7ff1217073d0>{number = 1, name = main}

2016- 01- 13 14:17:36.594 operation+dispatch_group[5990:2158531]
task B, <NSThread: 0x7ff1215029f0>{number = 2, name = (null)}

2016- 01- 13 14:17:36.594 operation+dispatch_group[5990:2158532]
task C, <NSThread: 0x7ff1217a6af0>{number = 3, name = (null)}
Copy the code

Custom NSOperation(AFNetworking source code)

AFNetworking source code:

AFURLConnectionOperation.m

@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDelegate.
NSURLConnectionDataDelegate.NSSecureCoding.NSCopying>
Copy the code

AFURLConnectionOperation is inherited from NSOperation

AFURLConnectionOperation.m


- (void)start {

    [self.lock lock];

    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class
        ] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.
        runLoopModes allObjects]];

    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;
// Use the AFNetworking thread to call OperationDidStart
        [self performSelector:@selector(operationDidStart) onThread:[[self 
        class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[
        self.runLoopModes allObjects]];
    }

    [self.lock unlock];
}
// The class method gets the network thread globally unique
+ (NSThread *)networkRequestThread {

    static NSThread *_networkRequestThread = nil;

    static dispatch_once_t oncePredicate;

    dispatch_once(&oncePredicate, ^{

        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:
        @selector(networkRequestThreadEntryPoint:) object:nil];

        [_networkRequestThread start];

    });
    return _networkRequestThread;

}

// Initialize the network thread
+ (void)networkRequestThreadEntryPoint:(id)__unused object {

    @autoreleasepool{[[NSThread currentThread] setName:@"AFNetworking"];
// Create a runloop object for the network thread
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// Keep network threads active when there are no port messages to prevent them from going to sleep and processing network requests or data returns at any time.
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// Start running the network thread runloop[runLoop run]; }} - (void)operationDidStart {

    [self.lock lock];

    if(! [self isCancelled]) {

        self.connection = [[NSURLConnection alloc] initWithRequest:self.request
         delegate:self startImmediately:NO];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

        for (NSString *runLoopMode in self.runLoopModes) {
// Add the event source associated with the network request to the network thread runloop. The network callback and data return depend on the network thread Runloop to listen for
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
// Add stream-related event sources to the network thread's runloop. Stream reads and writes depend on the network thread's Runloop to listen
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }
        [self.outputStream open];
// Start the network request
        [self.connection start];

    }
    [self.lock unlock];

// Let the main thread trigger the "network request has started" event.
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:
        AFNetworkingOperationDidStartNotification object:self];
    });
}
Copy the code

AFURLConnectionOperation itself overrides the start method in NSOperation, specifying the task to perform. When multiple requests are made simultaneously, the network data callback is handled by AFNetworking’s network thread.

Management between multiple threaded tasks

Sometimes, one thing may need to be done after all network requests are completed, or different network requests have certain dependencies or order relationships directly. In this case, the GCD group needs to be used to deal with.

GCD Group

Groups can be used to centrally manage multiple tasks. For example, you need to perform final processing after multiple tasks are completed:


dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_queue_create("com.ConcurrentQueue", 
DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{

    NSLog(@"task A");

});

dispatch_group_async(group, queue, ^{

    NSLog(@"task B");

});

dispatch_group_notify(group, queue, ^{

    NSLog(@"task LAST");

});

dispatch_group_async(group, queue, ^{

    NSLog(@"task C");

});
Copy the code

The output

2016- 01- 13 19:18:13.814 operation+dispatch_group[13783:2280382] task B

2016- 01- 13 19:18:13.814 operation+dispatch_group[13783:2280385] task C

2016- 01- 13 19:18:13.814 operation+dispatch_group[13783:2280383] task A

2016- 01- 13 19:18:13.815 operation+dispatch_group[13783:2280383] task LAST
Copy the code

Make task LAST wait until the ABC task is finished by group.

Implementation of management between multiple tasks in AFNetworking


+ (NSArray *)batchOfRequestOperations:(NSArray *)operations
                        progressBlock:(void(^) (NSUInteger 
                            numberOfFinishedOperations, NSUInteger 
                            totalNumberOfOperations))progressBlock

                      completionBlock:(void(^) (NSArray *operations))
                      completionBlock

{

    if(! operations || [operations count] ==0) {

        return@ [[NSBlockOperation blockOperationWithBlock:^{

            dispatch_async(dispatch_get_main_queue(), ^{

                if(completionBlock) { completionBlock(@[]); }}); }]]. } __block dispatch_group_t group = dispatch_group_create();// The last task to be processed after all tasks are completed
    NSBlockOperation *batchedOperation = [NSBlockOperation 
    blockOperationWithBlock:^{
// Ensure that the completionBlock current group is complete with dispatch_group_notify
// All originalCompletionBlock tasks on the main thread queue are processed after they are finished
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{

            if(completionBlock) { completionBlock(operations); }}); }];for (AFURLConnectionOperation *operation in operations) {

        operation.completionGroup = group;

        void (^originalCompletionBlock)(void) = [operation.completionBlock copy];

        __weak __typeof(operation)weakOperation = operation;
// Change the completionBlock for each Operation by adding the ability to display the progress of the task.
        operation.completionBlock = ^{

            __strong __typeof(weakOperation)strongOperation = weakOperation;

// Ignore line-level compiler warnings
#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wgnu"

            dispatch_queue_t queue = strongOperation.completionQueue ?: dispatch_get_main_queue();

#pragma clang diagnostic pop

            dispatch_group_async(group, queue, ^{
/ / originalCompletionBlock tasks may be put in dispatch_main_queue processing.
                if (originalCompletionBlock) {

                    originalCompletionBlock();

                }

// Filter out completed tasks
                NSUInteger numberOfFinishedOperations = [[operations 
                indexesOfObjectsPassingTest:^BOOL(id op, NSUInteger __unused 
                    idx,  BOOL __unused *stop) {

                    return [op isFinished];

                }] count];

                if (progressBlock) {
// Displays the progress of the task completion
                    progressBlock(numberOfFinishedOperations, [operations count
                        ]);

                }
                dispatch_group_leave(group);

            });

        };

        dispatch_group_enter(group);
// Add batchedOperation dependencies on all Operation tasks to ensure that batchedOperation is processed last.
        [batchedOperation addDependency:operation];

    }
    return [operations arrayByAddingObject:batchedOperation];
}
Copy the code

This function packages multiple tasks and displays progress, and finally waits for all tasks to complete.