美文网首页iOS基本功
AFNetworking3.0后 与2.x 常驻线程的区别

AFNetworking3.0后 与2.x 常驻线程的区别

作者: 9d8c8692519b | 来源:发表于2019-01-09 14:19 被阅读63次

    AF2.x为什么需要常驻线程?

    NSURLConnection

    先来看看 NSURLConnection 发送请求时的线程情况,NSURLConnection 是被设计成异步发送的,调用了start方法后,NSURLConnection 会新建一些线程用底层的 CFSocket 去发送和接收请求,在发送和接收的一些事件发生后通知原来线程的Runloop去回调事件。
    在AF2.x中使用NSURLConnection,选择一条常驻线程。只开辟一条子线程,设置runloop使线程常驻。所有的请求在这个线程上发起、同时也在这个线程上回调来满足需求。

    那有人会问:那网络请求岂不是变成了单线程?

    //networkRequestThread即常驻线程
    [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    
    - (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) {
                [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
                [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];
        });
    }
    

    首先,每一个请求对应一个AFHTTPRequestOperation实例对象(以下简称operation),每一个operation在初始化完成后都会被添加到一个NSOperationQueue中。
    由这个NSOperationQueue来控制并发,系统会根据当前可用的核心数以及负载情况动态地调整最大的并发 operation 数量,我们也可以通过setMaxConcurrentoperationCount:方法来设置最大并发数。注意:并发数并不等于所开辟的线程数。具体开辟几条线程由系统决定。
    也就是说此处执行operation是并发的、多线程的。


    AF2.x .png

    为什么AF2.x需要一条常驻线程? 从上面我们可以知道

    首先需要在子线程去start connection,请求发送后,所在的子线程需要保活以保证正常接收到 NSURLConnectionDelegate 回调方法。如果每来一个请求就开一条线程,并且保活线程,这样开销太大了。所以只需要保活一条固定的线程,在这个线程里发起请求、接收回调。。

    AF3.x为什么不再需要常驻线程?

    NSURLConnection的一大痛点就是: 发起请求后,这条线程并不能随风而去,而需要一直处于等待回调的状态。苹果也是明白了这一痛点,从iOS9.0开始 deprecated 了NSURLConnection。 替代方案就是NSURLSession。当然NSURLSession还解决了很多其他的问题,这里不作赘述。

    self.operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue.maxConcurrentOperationCount = 1;
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    

    为什么说NSURLSession解决了NSURLConnection的痛点,从上面的代码可以看出,NSURLSession发起的请求,不再需要在当前线程进行代理方法的回调!可以指定回调的delegateQueue,这样我们就不用为了等待代理回调方法而苦苦保活线程了。
    同时还要注意一下,指定的用于接收回调的Queue的maxConcurrentOperationCount设为了1,这里目的是想要让并发的请求串行的进行回调。

    为什么要串行回调?
    - (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
        NSParameterAssert(task);
        AFURLSessionManagerTaskDelegate *delegate = nil;
        [self.lock lock];
        //给所要访问的资源加锁,防止造成数据混乱
        delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
        [self.lock unlock];
        return delegate;
    }
    

    这边对 self.mutableTaskDelegatesKeyedByTaskIdentifier 的访问进行了加锁,目的是保证多线程环境下的数据安全。既然加了锁,就算maxConcurrentOperationCount不设为1,当某个请求正在回调时,下一个请求还是得等待一直到上个请求获取完所要的资源后解锁,所以这边并发回调也是没有意义的。相反多task回调导致的多线程并发,还会导致性能的浪费。


    AF3.x.png
    补充1:

    AF3.x会给每个 NSURLSessionTask 绑定一个 AFURLSessionManagerTaskDelegate ,这个TaskDelegate相当于把NSURLSessionDelegate进行了一层过滤,最终只保留类似didCompleteWithError这样对上层调用者输出的回调。

    - (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
    {
    //此处代码进行了大量删减,只是为了让大家清楚的看到这个方法做的最重要的事
    dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, error);
                }
        }
    }
    
    补充2:

    面试官可能会问你:为什么AF3.0中需要设置

    self.operationQueue.maxConcurrentOperationCount = 1;
    
    而AF2.0却不需要?

    这个问题不难,但是却可以帮助面试官判断面试者是否真的认真研读了AF的两个大版本的源码。
    解答:功能不一样:AF3.0的operationQueue是用来接收NSURLSessionDelegate回调的,鉴于一些多线程数据访问的安全性考虑,设置了maxConcurrentOperationCount = 1来达到串行回调的效果。
    而AF2.0的operationQueue是用来添加operation并进行并发请求的,所以不要设置为1。

    - (AFHTTPRequestOperation *)POST:(NSString *)URLString
                          parameters:(id)parameters
                             success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                             failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
    {
        AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithHTTPMethod:@"POST" URLString:URLString parameters:parameters success:success failure:failure];
        [self.operationQueue addOperation:operation];
        return operation;
    }
    
    补充3:AF中常驻线程的实现(经典案例)
    + (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;
    }
    

    首先用NSThread创建了一个线程,并且这个线程是个单例。

    + (void)networkRequestThreadEntryPoint:(id)__unused object {
        @autoreleasepool {
            [[NSThread currentThread] setName:@"AFNetworking"];
    
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
            [runLoop run];
        }
    }
    

    新建的子线程默认是没有添加Runloop的,因此给这个线程添加了一个runloop,并且加了一个NSMachPort,来防止这个新建的线程由于没有活动直接退出。

    相关文章

      网友评论

        本文标题:AFNetworking3.0后 与2.x 常驻线程的区别

        本文链接:https://www.haomeiwen.com/subject/ilmwrqtx.html