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.