iOS AFNetWorking源码详解(一)

[中文教程]
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看248 回复回复2 收藏收藏 分享淘帖 转播转播 分享分享 微信
查看: 248|回复: 2
收起左侧

[中文教程] iOS AFNetWorking源码详解(一)

[复制链接]
代码买卖 发表于 2016-7-25 02:21:35 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑
本帖最后由 代码买卖 于 2016-7-27 22:04 编辑

首先来介绍下AFNetWorking,官方介绍如下:

AFNetworking is a delightful networking library for iOS and Mac OS X. It’s built on top of the Foundation URL Loading System, extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use.

Perhaps the most important feature of all, however, is the amazing community of developers who use and contribute to AFNetworking every day. AFNetworking powers some of the most popular and critically-acclaimed apps on the iPhone, iPad, and Mac.

Choose AFNetworking for your next project, or migrate over your existing projects—you’ll be happy you did!

iOS AFNetWorking源码详解

iOS AFNetWorking源码详解(一) - 敏捷大拇指 - iOS AFNetWorking源码详解


翻译过来简单来说就是

AFNetworking是一个适用于iOS和Mac OS X两个平台的网络库,它是基于Foundation URL Loading System上进行了一套封装,并且提供了丰富且优美的API接口给使用者使用

iOS AFNetWorking源码详解(一) 1

iOS AFNetWorking源码详解(一) - 敏捷大拇指 - iOS AFNetWorking源码详解(一) 1


相信从star数和fork数来看,大家都能明白这个库是多么的受欢迎了,所以了解这个库对于一个iOS开发来说是极为重要的!

这个是AFNetworking的github地址:GitHub - AFNetworking/AFNetworking: A delightful networking framework for iOS

在使用前阅读README是非常重要的,里面往往包括了这个库的介绍、安装和使用等等,对于快速了解一个库来说,这是非常有帮助的

首先我们在AFNetWorking源码地址里download下来,打开工程文件,可以看到里面内容分为两个部分,一个是AFNetworking,另一个是UIKit+AFNetworking

iOS AFNetWorking源码详解(一) 2

iOS AFNetWorking源码详解(一) - 敏捷大拇指 - iOS AFNetWorking源码详解(一) 2


很明显,第一个是用来做网络请求相关的,第二个则是和UI使用相关的,我们先看第一个

在看完头文件和README之后,你会发现AFURLSessionManager和AFHTTPSessionManager是里面比较重要的两个类



这里我先讲AFURLSessionManager这个类

首先浏览完这个类从API,发现其主要提供了数据的请求、上传和下载功能

在属性方面:

[Objective-C] 纯文本查看 复制代码
@property(readonly,nonatomic,strong)NSArray *tasks;

@property(readonly,nonatomic,strong)NSArray *dataTasks;

@property(readonly,nonatomic,strong)NSArray *uploadTasks;

@property(readonly,nonatomic,strong)NSArray *downloadTasks;


通过这四个属性,我们分别可以拿到总的任务集合、数据任务集合、上传任务集合和下载任务集合

[Objective-C] 纯文本查看 复制代码
@property(nonatomic,assign)BOOL attemptsToRecreateUploadTasksForBackgroundSessions;


这个属性非常重要,注释里面写到,在iOS7中存在一个bug,在创建后台上传任务时,有时候会返回nil,所以为了解决这个问题,AFNetworking遵照了苹果的建议,在创建失败的时候,会重新尝试创建,次数默认为3次,所以你的应用如果有场景会有在后台上传的情况的话,记得将该值设为YES,避免出现上传失败的问题

[Objective-C] 纯文本查看 复制代码
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidResumeNotification;


在对外提供的notification key里面,使用了FOUNDATION_EXPORT来定义常量,使用FOUNDATION_EXPORT和extern或者define有什么区别呢?

FOUNDATION_EXPORT在c文件编译下是和extern等同,在c++文件编译下是和extern “C”等同,在32位机的环境下又是另外编译情况,在兼容性方面,FOUNDATION_EXPORT做的会更好。

这里还提到了效率方面的问题:iOS开发的一些奇巧淫技3

进入到实现文件里面,我们可以看到在外部API调用dataTask、uploadTask、downloadTask方法实际上都是completionHanlder block返回出来的,但是我们知道网络请求是delegate返回结果的,AF内部做了巧妙的操作,他对每个task都增加代理设置

[Objective-C] 纯文本查看 复制代码
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
        __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });
    
        // 每个task里面都会调用addDelegate方法
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}


在设置里面,每个task会在内部创建AFURLSessionManagerTaskDelegate对象,并设置completionHandler、uploadProgressBlock、downloadProgressBlock回调

[Objective-C] 纯文本查看 复制代码
- (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
{
    // 初始化delegate对象
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    // 将task的completionHandler赋给delegate,系统网络请求delegate 调用该block,返回结果
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    // 对task进行delegate
    [self setDelegate:delegate forTask:dataTask];
        // 设置上传和下载进度回调
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}


然后delegate对象利用kvo将task对一些方法进行监听,并且监听到变化时,通过block返回,将delegate转成block出去

[Objective-C] 纯文本查看 复制代码
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    // 断言
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    // task使用kvo对一些方法监听,返回上传或者下载的进度
    [delegate setupProgressForTask:task];
    // sessionManager对暂停task和恢复task进行注册通知
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}


在原先IM的设计时,因为接口的数量并不多,所以在AsyncSocket的delegate回调后,我们依旧是采用delegate回调给业务层,但是随着接口数量的增加,业务层对于回调的处理更加困难和不可控,在重构IM的时候,我们也参考学习了AF的做法,我们通过对唯一标识和每个请求做一一绑定,将请求的上下文关联起来,这样让socket长连接的请求的也想http请求一样,都由block回去,对于业务层的处理也方便更多

setupProgressForTask方法主要是对task和progress设置监听

[Objective-C] 纯文本查看 复制代码
- (void)setupProgressForTask:(NSURLSessionTask *)task {
    __weak __typeof__(task) weakTask = task;

    // 设置上传和下载的大小
    
    // 设置上传和下载中允许取消和暂停
  
          // 设置上传和下载响应恢复处理方法后恢复上传或下载
    
    // task对接收到的字节数、期望接收到的字节数、发送的字节数、期望发送的字节数设置监听
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
          forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
              options:NSKeyValueObservingOptionNew
              context:NULL];

    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
              options:NSKeyValueObservingOptionNew
              context:NULL];
    [task addObserver:self
           forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
              options:NSKeyValueObservingOptionNew
              context:NULL];
        // 上传和下载设置完成的分数监听
    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}


[Objective-C] 纯文本查看 复制代码
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
        // 设置上传和下载的新值
    }
    else if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}


在第一个if判断里面,object判断是否是NSURLSessionTask类或者是否是NSURLSessionDownloadTask类,但是进到NSURLSessionDownloadTask的时候,我们可以看到NSURLSessionDownloadTask是NSURLSessionTask的子类,那为什么还要判断这个呢?

NSURLSessionTask实际上是Class cluster,通过NSURLSession生成的task返回的并不一定是指定的task类型。因此kindOfClass并不总会生效,具体可以参见AFURLSessionManager.m在load方法中的说明。
特定于当前问题,是由于iOS 7上NSCFURLSessionDownloadTask的基类并不是NSCFURLSessionTask,因此isKindOfClass会出错。查看对应的commit就可以知道了。

在NSURLSessionTaskDelegate的代理里面,只是做了两件事情,第一个是获取数据,将responseSerializer和downloadFileURL或data存到userInfo里面,第二个是根据error是否为空值,做下一步处理

[Objective-C] 纯文本查看 复制代码
#pragma mark - NSURLSessionTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    // 获取数据,将responseSerializer和downloadFileURL或data存到userInfo里面
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }

    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }

    if (error) {
      // 有error时处理
    } else {
      // 无error时正常处理
    }
#pragma clang diagnostic pop
}


在有error时,userInfo先存储error,然后检查manager是否有completionGroup和completionQueue,没有的话,就创建一个dispatch_group_t和在主线程上做completionHandler的操作,并在主线程中发送一个AFNetworkingTaskDidCompleteNotification通知,这个通知在UIKit+AFNetworking里UIRefreshControl +AFNetworking里也会接收到,用来停止刷新,如果你不使用AF的UI部分,你可以通过接收这个通知来做操作

[Objective-C] 纯文本查看 复制代码
userInfo[AFNetworkingTaskDidCompleteErrorKey] = 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);
            }

            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });


在没有error时,会先对数据进行一次序列化操作,然后下面的处理就和有error的那部分一样了

[Objective-C] 纯文本查看 复制代码
dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }

            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }

            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }

            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, serializationError);
                }

                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });


一开始我们就看到了clang命令,这个的作用是用来消除特定区域的clang的编译警告,-Wgnu则是消除?:警告,这个是clang的警告message列表Which Clang Warning Is Generating This Message?

[Objective-C] 纯文本查看 复制代码
- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wgnu"

// some codes

#pragma clang diagnostic pop
}


再下面两个则是收到数据和下载文件的回调处理

[Objective-C] 纯文本查看 复制代码
#pragma mark - NSURLSessionDataTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    [self.mutableData appendData:data];
}

#pragma mark - NSURLSessionDownloadTaskDelegate

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;

    if (self.downloadTaskDidFinishDownloading) {
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];

            if (fileManagerError) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}


在刚才说到的load方法里面,对系统的resume和suspend方法进行了替换

[Objective-C] 纯文本查看 复制代码
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
    Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
    Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

    if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
        af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
    }

    if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
        af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
    }
}


替换之后,只是增加了通知处理而已

[Objective-C] 纯文本查看 复制代码
- (void)af_resume {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_resume];
    
    if (state != NSURLSessionTaskStateRunning) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
    }
}

- (void)af_suspend {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_suspend];
    
    if (state != NSURLSessionTaskStateSuspended) {
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
    }
}


在调用替换和增加方法时候,用到了关键字inline,inline是为了防止反汇编之后,在符号表里面看不到你所调用的该方法,否则别人可以通过篡改你的返回值来造成攻击,iOS安全–使用static inline方式编译函数,防止静态分析,特别是在使用swizzling的时候,那除了使用swizzling动态替换函数方法之外,还有别的方法么?有,修改IMP指针指向的方法,轻松学习之 IMP指针的作用

[Objective-C] 纯文本查看 复制代码
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
    return class_addMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
}


在+ load方法中,我们又看到了GCC命令,那clang和GCC在使用的时机有没有什么区别?通常情况下,在GCC特有的处理或者是在GCC,clang和其他兼容GCC的编译器时,尽量使用#pragma GCC,clang特有的处理时,使用#pragma clang,这个是GCC的message表

[Objective-C] 纯文本查看 复制代码
+ (void)load {
    // ...
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
        NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
    // ...
}


看完之后,有个疑问,查了资料也没有找到:

在NSURLSessionDelegate的URLSession:didReceiveChallenge:completionHandler:方法里面disposition会对credential对象做非空判断然后再赋值校验类型,但是NSURLSessionTaskDelegate的- [URLSession:task:didReceiveChallenge:completionHandler:]方法里面disposition并不对credential对象做判断,而是直接就赋值校验类型,有知道的,欢迎留言交流





相关内容

iOS AFNetWorking源码详解(一)

iOS AFNetWorking源码详解(二)

iOS AFNetWorking源码详解(三)

iOS AFNetWorking源码详解(四)

iOS AFNetWorking源码详解(五)

iOS AFNetWorking源码详解(六)

AFNetworking 3.0迁移指南 AFNetworking 3.0 Migration Guide




作者:zeeyang

都看到这里了,就把这篇资料推荐给您的好朋友吧,让他们也感受一下。

回帖是一种美德,也是对楼主发帖的尊重和支持。

*声明:敏捷大拇指是全球最大的Swift开发者社区、苹果粉丝家园、智能移动门户,所载内容仅限于传递更多最新信息,并不意味赞同其观点或证实其描述;内容仅供参考,并非绝对正确的建议。本站不对上述信息的真实性、合法性、完整性做出保证;转载请注明来源并加上本站链接,敏捷大拇指将保留所有法律权益。如有疑问或建议,邮件至marketing@swifthumb.com

*联系:微信公众平台:“swifthumb” / 腾讯微博:@swifthumb / 新浪微博:@swifthumb / 官方QQ一群:343549891(满) / 官方QQ二群:245285613 ,需要报上用户名才会被同意进群,请先注册敏捷大拇指

嗯,不错!期待更多好内容,支持一把:
支持敏捷大拇指,用支付宝支付10.24元 支持敏捷大拇指,用微信支付10.24元

评分

参与人数 1金钱 +10 贡献 +10 专家分 +10 收起 理由
Anewczs + 10 + 10 + 10 32个赞!专家给力!

查看全部评分

本帖被以下淘专辑推荐:

3rdev 发表于 2016-7-25 15:54:40 | 显示全部楼层
超级赞!
SwiftRobot 发表于 2016-7-26 09:31:30 | 显示全部楼层
辛苦了,总结这么多~
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

做任务,领红包。
我要发帖

分享扩散

都看到这里了,就把这资料推荐给您的好朋友吧,让他们也感受一下。
您的每一位朋友访问此永久链接后,您都将获得相应的金钱积分奖励

相关标签

关闭

站长推荐 上一条 /3 下一条

热门推荐

合作伙伴

Swift小苹果

  • 北京治世天下科技有限公司
  • ©2014-2016 敏捷大拇指
  • 京ICP备14029482号
  • Powered by Discuz! X3.1 Licensed
  • swifthumb Wechat Code
  •   
快速回复 返回顶部 返回列表