AFNetworking-NSOperation

基本内容

本节主要介绍iOS中多线程相关的内容,先简单介绍NSOperation的使用,然后结合GCD实现任务之间的管理,比如每个任务完成之后的处理以及任务与任务之间的依赖,所有任务完成之后的处理等,通过AFNetworking源码分析其具体实现。

NSoperation

通过NSOpeartion去完成某个任务的时候,有3种方式:

  • NSInvocationOperation
  • NSBlockOperation
  • 自己继承NSOperation实现具体的任务操作

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(@"%@", [NSThread currentThread]);

}

-(void)invocationOperationCall2{

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

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

}

输出:

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)}

从日志中看出任务分别在不同的线程中执行

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];

输出:

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}

从日志中看出,如果不添加到queue中,则任务是同步在当前线程中执行的。

还可以添加NSOperation对象之间的依赖,让每个任务之间按一定的顺序执行

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];

输出:

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)}

虽然ope和ope2之间是并行的,但是添加依赖之后ope会等到ope2执行完毕之后才开始执行。

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];

输出:


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)}

自定义NSOperation(结合AFNetworking源码)

AFNetworking源代码:

AFURLConnectionOperation.m

@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDelegate, 
NSURLConnectionDataDelegate, NSSecureCoding, NSCopying>

AFURLConnectionOperation是继承自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;
//使用AFNetworking网络线程去调用OperationDidStart函数
        [self performSelector:@selector(operationDidStart) onThread:[[self 
        class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[
        self.runLoopModes allObjects]];
    }

    [self.lock unlock];
}
//类方法 获取网络线程 全局唯一的
+ (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;

}

//初始化网络线程
+ (void)networkRequestThreadEntryPoint:(id)__unused object {

    @autoreleasepool {

        [[NSThread currentThread] setName:@"AFNetworking"];
//新建网络线程的runloop对象
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//保证网络线程在没有任何port消息的时候一直处于活跃状态,防止进入休眠,随时处理网络请求或者数据返回。
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
//开始运行网络线程的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) {
//将网络请求相关的事件源添加到网络线程的runloop中,网络回调和数据返回都依赖于网络线程runloop去监听
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
//将流相关的事件源添加到网络线程的runloop中,流的读写都依赖于网络线程runloop去监听
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }
        [self.outputStream open];
//开始网络请求
        [self.connection start];

    }
    [self.lock unlock];

//让主线程去触发”已经开始网络请求“这一事件。
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:
        AFNetworkingOperationDidStartNotification object:self];
    });
}

AFURLConnectionOperation自己重写了NSOperation中的start方法,具体指定了需要执行的任务。多个请求同时进行的时候,对网络数据回调的处理都是由AFNetworking的网络线程进行的。

多个线程任务之间的管理

有时候,可能需要在所有网络请求完成之后需要做一件事情或者是不同的网络请求直接还有一定的依赖关系或者是顺序关系,这种情况下需要使用GCD的group去处理。

GCD group简单介绍:

通过group可以对多个task进行统一管理,比如现在需要在多个任务完成之后再去做最后的处理:


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");

});

输出

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

通过group让task LAST等到ABC任务执行完之后最后进行处理。

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();


//等待所有任务都完成之后最后需要处理的任务
    NSBlockOperation *batchedOperation = [NSBlockOperation 
    blockOperationWithBlock:^{
//通过dispatch_group_notify保证completionBlock当前group
//中的主线程队列上所有的originalCompletionBlock任务处理完之后再处理
        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;
//改变每个Operation的completionBlock,主要是添加了能显示任务完成进度的功能。
        operation.completionBlock = ^{

            __strong __typeof(weakOperation)strongOperation = weakOperation;

//忽略行级别的编译器警告
#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任务可能会被放到dispatch_main_queue中处理。
                if (originalCompletionBlock) {

                    originalCompletionBlock();

                }

//过滤出已经完成的任务
                NSUInteger numberOfFinishedOperations = [[operations 
                indexesOfObjectsPassingTest:^BOOL(id op, NSUInteger __unused 
                    idx,  BOOL __unused *stop) {

                    return [op isFinished];

                }] count];

                if (progressBlock) {
//显示任务完成的进度
                    progressBlock(numberOfFinishedOperations, [operations count
                        ]);

                }
                dispatch_group_leave(group);

            });

        };

        dispatch_group_enter(group);
//添加batchedOperation对所有Operation任务的依赖,保证batchedOperation最后处理。
        [batchedOperation addDependency:operation];

    }
    return [operations arrayByAddingObject:batchedOperation];
}

这个函数是将多个任务进行打包处理,并能显示出完成进度,以及最后等待所有任务完成之后进行最后的处理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容

  • 本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。这大概是史上最详细、清晰的关于 GCD 的详细讲...
    花花世界的孤独行者阅读 495评论 0 1
  • AFNetworking多线程分析 AFNetworking是目前最常用的iOS的网络开发框架。它是对Apple系...
    施展君阅读 9,863评论 5 9
  • 1.设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类...
    司马DE晴空阅读 1,278评论 0 7
  • 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的...
    iOS菜鸟大大阅读 700评论 0 1
  • 1、简介 NSOperation是苹果提供给我们的一套多线程解决方案。实际上NSOperation是基于GCD更高...
    WQ_UESTC阅读 953评论 0 6