AFNetworking 3.x 阅读笔记(三)

前面两篇笔记已经介绍了AFHTTPSessionManager,本文将会深入到整个网络库的核心类AFURLSessionManager.

我们指定AFNetworking3.x使用的是NSURLSession,根据请求的不同通过它创建不同的task(dataTaskXXX,uploadTaskXXX,downloadTaskXXX),不论是session层还是task层,产生的事件都需要异步通过delegate处理.相关的delegate:

  • NSURLSessionDelegate
  • NSURLSessionTaskDelegate
  • NSURLSessionDataDelegate
  • NSURLSessionDownloadDelegate

AFURLSessionManager就充当着管理session,创建task,处理上述的各种delegate事件的作用.

核心类的初始化

AFURLSessionManager的指定init方法中:

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 1 创建session的配置类
    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }
    self.sessionConfiguration = configuration;
    // 2 创建session 的delegate运行的queue
    self.operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue.maxConcurrentOperationCount = 1;
    // 3 创建session实例,并且强引用,delegate 是自己
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    // 4 创建response 接受的数据序列化的辅助类,默认接受JSON数据
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    // 5 配置安全策略 -> 主要在delegate中收到鉴权请求时候使用
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
    // 6 创建网络状态监听类 -> 但是实际中框架里面好像没怎么使用???
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif

    // 7 创建dict,用来管理task对应的delegate,因为有部分内容交给 delegate helper 类去完成的
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    // 8 [task:delegateHelper] 访问时候的锁
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    // 9 为什么要调用这个方法??????有点奇怪-> 方法的主要作用是获取session中所有的真该执行的tasks
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        for (NSURLSessionDataTask *task in dataTasks) {
            [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
        }

        for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
            [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
        }

        for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
            [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
        }
    }];

    return self;
}

task delegate helper类

几乎核心类所有的方法都是围绕session的delegate方法展开的.不论是session层的delegate,还是session产生的task层的delegate方法的实现都在AFURLSessionManager实现.

为了区分不同task产生的delegate事件,AFNetworking引入了AFURLSessionManagerTaskDelegate类.该类主要帮助每个task完成两大块的任务:

  • 1 task执行下载完成任务以后的completionHanlder由delegate helper类执行的
  • 2 在以下几个delegate中帮助特定的task处理delegate事件
  • 3 完成关于NSProgress相关上传或者下载的进度的progressBlock
// NSURLSessionTaskDelegate, 这个帮助方法也是dataTask完成请求以后,responseSerializer的入口
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;

// NSURLSessionDataDelegate
- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend;

//NSURLSessionDownloadDelegate
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes;
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;

再了解框架是如何使用该类来帮助完成这两块任务之前,我们先来看下AFURLSessionManager是如何使用该类来绑定不同的task的.

AFURLSessionManagerdataTaskWithRequest:xxxxx方法使用session创建Task(dataTask,uploadTask,downloadTask)以后,都会调用该类的另外一簇方法,将刚刚创建的task实例当做参数传递进去.

下面是addDelegateForXXXTask:XXXProgress:...:completionHanlder方法:

- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler;

- (void)addDelegateForUploadTask:(NSURLSessionUploadTask *)uploadTask
                        progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
               completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler;

- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                          progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                       destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                 completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler;

为了说明delegate helper 是如何与task绑定,又如何在代理方法中分发的,这里举一个简单的例子:

该方法簇是创建一个该类的实例,然后将它与刚刚创建的task关联起来,存放到AFURLSessionManagermutableTaskDelegatesKeyedByTaskIdentifier的NSDictionary实例中,当调用上文提到的几个delegate方法时候,就会根据不同的task调用不同task.identifier为key的AFURLSessionManagerTaskDelegate对象完成处理.简而言之,AFURLSessionManager就犹如一个路由,给不同的task任务派发不同的事件.具体事件的处理函数都在各自task对应的task delegate helper方法中去完成,具体的实例代码如下:

/**
 *  将dataTask和一个 AFURLSessionManagerTaskDelegate 关联起来,将其中部分progressBlock交给delegate去管理
 */
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
              downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
// 1 创建task delegate helper 类
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
// 2 delegate helper 也会在task收到执行完毕的回调以后执行completionHanlder
    delegate.completionHandler = completionHandler;
// 3 用来标识task唯一性!!!!用的task的内存地址,将dataTask <--> delegate helper关联起来
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask];
// 4 NSProgress相关的Block都是由delegate helper去更新的
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

// 通知delegate, task已经完成, 会直接调用 taskDelegateHelper -> 将数据通过responseSerializer处理
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    // 1 获取delegate -> 2 调用delegate 的相似方法 -> 3 从dict中删除(删除以后,注意清理现场)
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // delegate may be nil when completing a task in the background
    if (delegate) {
        // 2 使用task对应的delegate helper 执行该代理任务
        [delegate URLSession:session task:task didCompleteWithError:error];
        // 3 将task从[task:delegate]中删除
        [self removeDelegateForTask:task];
    }

    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

task delegate helper 的存取

由于session创建的每个task都有一个对应的AFURLSessionManagerTaskDelegate,因此在访问manager的mutableTaskDelegatesKeyedByTaskIdentifier字典时候,不可避免的遇到字典的读写同步问题.框架中通过一个NSLock保证读写时可能面临的同步问题.对数据只有 -- 增, 删, 查

// 增
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    // 访问 mutableTaskDelegatesKeyedByTaskIdentifier 字典时候需要加锁,在其他存储时候也需要加锁
    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;//使用task的identifier作为key
    [delegate setupProgressForTask:task];// 为task设置NSProgress的重要内容
    [self addNotificationObserverForTask:task];// 监听task完成或者恢复时候发送的通知
    [self.lock unlock];
}
// 删
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    [self.lock lock];
    [delegate cleanUpProgressForTask:task];
    [self removeNotificationObserverForTask:task];
    [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
    [self.lock unlock];
}

// 改 -> 没有对task进行修改 -> 代码里面用的先删除然后设置的方法
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    if (delegate) {
        [self removeDelegateForTask:dataTask];
        [self setDelegate:delegate forTask:downloadTask];
    }

    if (self.dataTaskDidBecomeDownloadTask) {
        self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
    }
}

// 查
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    // 使用加锁的方式获取task对应的delegate
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];// 通过taskIdentifier完成
    [self.lock unlock];

    return delegate;
}

其他学习的地方

AFURLSessionManager中除了delegate方法,还有一组关于正在运行的session task属性的方法簇:

///----------------------------
/// @name Getting Session Tasks
///----------------------------

/**
 The data, upload, and download tasks currently run by the managed session.
 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks;

/**
 The data tasks currently run by the managed session.
 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks;

/**
 The upload tasks currently run by the managed session.
 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks;

/**
 The download tasks currently run by the managed session.
 */
@property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks;

这些方法簇的getter实现的也比较巧妙:

- (NSArray *)tasks {
    return [self tasksForKeyPath:NSStringFromSelector(_cmd)];// _cmd 表示方法名称
}

- (NSArray *)dataTasks {
    return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}

- (NSArray *)uploadTasks {
    return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}

- (NSArray *)downloadTasks {
    return [self tasksForKeyPath:NSStringFromSelector(_cmd)];
}

// 通过keyPath 获取对应的tasks array. -> 使用dispatch_semephore_t 等待异步调用完成,然后执行的方法
- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    // 利用信号量异步等待的方法!!!!
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {// 异步调用block
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

其中,在tasksForKeyPath方法中犹豫,session获取正在执行的tasks是使用的异步block返回查询结果的.因此框架中使用了dispatch_semaphore_t来等待异步结果,可以在以后工作中学以致用:

// 1 创建信号量 -> 初始计数是0
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

block(){//异步block方法中给信号量发信息
        ...
         // 2 在block异步结果返回时候发送信号
        dispatch_semaphore_signal(semaphore);
     }

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

推荐阅读更多精彩内容