读读 AFNetworking

Posted 想名真难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了读读 AFNetworking相关的知识,希望对你有一定的参考价值。

以前看过源码,但是看得很迷,还是这篇文章好,作者很厉害,其他文章也可以看看,原文(是一系列文章,此处是第一篇,后续直接看原文就好)   : AFNetworking到底做了什么? - 简书

理解了  NSURLSession  能更好的理解AFN.  NSURLSession

写在开头:

  • 作为一个ios开发,也许你不知道NSUrlRequest、不知道NSUrlConnection、也不知道NSURLSession...(说不下去了...怎么会什么都不知道...)但是你一定知道AFNetworking。

  • 大多数人习惯了只要是请求网络都用AF,但是你真的知道AF做了什么吗?为什么我们不用原生的NSURLSession而选择AFNetworking?

本文将从源码的角度去分析AF的实际作用。或许看完这篇文章,你心里会有一个答案。

先从最新的AF3.x讲起吧:

  • 首先,我们就一起分析一下该框架的组成。

     将AF下载导入工程后,下面是其包结构,相对于2.x变得非常简单了:

AF代码结构图.png

        除去Support Files,可以看到AF分为如下5个功能模块:

  • 网络通信模块(AFURLSessionManager、AFHTTPSessionManger)

  • 网络状态监听模块(Reachability)

  • 网络通信安全策略模块(Security)

  • 网络通信信息序列化/反序列化模块(Serialization)

  • 对于iOS UIKit库的扩展(UIKit)

其核心当然是网络通信模块NSURLSessionManager。大家都知道,AF3.x是基于NSURLSession来封装的。所以这个类围绕着NSURLSession做了一系列的封装。而其余的四个模块,均是为了配合网络通信或对已有UIKit的一个扩展工具包。

这五个模块所对应的类的结构关系图如下所示:

AF架构图.png

其中AFHTTPSessionManager是继承于NSURLSessionManager的,我们一般做网络请求都是用AFHTTPSessionManager,但是它本身是没有做实事的,只是把一些请求逻辑分发给父类AFURLSessionManager或者其它类去做。

首先我们简单的写个get请求:

​
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc]init];

[manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) 

 failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) 

];

​

首先我们我们调用了初始化方法生成了一个manager,我们点进去看看初始化做了什么:

- (instancetype)init 
    return [self initWithBaseURL:nil];


- (instancetype)initWithBaseURL:(NSURL *)url 
    return [self initWithBaseURL:url sessionConfiguration:nil];


- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration 
    return [self initWithBaseURL:nil sessionConfiguration:configuration];


- (instancetype)initWithBaseURL:(NSURL *)url
           sessionConfiguration:(NSURLSessionConfiguration *)configuration

    self = [super initWithSessionConfiguration:configuration];
    if (!self) 
        return nil;
    

    //对传过来的BaseUrl进行处理,截去url最后的/
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) 
        url = [url URLByAppendingPathComponent:@""];
    

    self.baseURL = url;
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    return self;
  • 初始化都调用到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中来了。
  • 其实初始化方法都调用父类的初始化方法。父类也就是AF3.x最最核心的类AFHTTPSessionManager。几乎所有的类都是围绕着这个类在处理业务逻辑。

  • 除此之外,方法中把baseURL存了起来,还生成了一个请求序列对象和一个响应序列对象。后面再细说这两个类是干什么用的。

直接来到父类AFURLSessionManager的初始化方法:

- (instancetype)init 
    return [self initWithSessionConfiguration:nil];


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

    if (!configuration) 
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    

    self.sessionConfiguration = configuration;
    self.operationQueue = [[NSOperationQueue alloc] init];
    //queue并发线程数设置为1
    self.operationQueue.maxConcurrentOperationCount = 1;
    //注意代理,代理的继承,实际上NSURLSession去判断了,你实现了哪个方法会去调用,包括子代理的方法!
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

    //各种响应转码
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    //设置默认安全策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif

    // 设置存储NSURL task与AFURLSessionManagerTaskDelegate的词典(重点,在AFNet中,每一个task都会被匹配一个AFURLSessionManagerTaskDelegate 来做task的delegate事件处理) ===============
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
    //  设置AFURLSessionManagerTaskDelegate 词典的锁,确保词典在多线程访问时的线程安全
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;
    // 置空task关联的代理
    [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;

 

  • 这个就是最终的初始化方法了,注释应该写的很清楚,唯一需要说的就是三点:

    • self.operationQueue.maxConcurrentOperationCount = 1;这个operationQueue就是我们代理回调的queue。这里把代理回调的线程并发数设置为1了。至于这里为什么要这么做,我们先留一个坑,等我们讲完AF2.x之后再来分析这一块。

    • 第二就是我们初始化了一些属性,其中包括self.mutableTaskDelegatesKeyedByTaskIdentifier,这个是用来让每一个请求task和我们自定义的AF代理来建立映射用的,其实AF对task的代理进行了一个封装,并且转发代理到AF自定义的代理,这是AF比较重要的一部分,接下来我们会具体讲这一块。

    • 第三就是下面这个方法:

[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks)  

];

首先说说这个方法是干什么用的:这个方法用来异步的获取当前session的所有未完成的task。其实讲道理来说在初始化中调用这个方法应该里面一个task都不会有。我们打断点去看,也确实如此,里面的数组都是空的。但是想想也知道,AF大神不会把一段没用的代码放在这吧。所以我大胆猜测,可能是当我们重复去初始化session的时候(当然我们实际也不会这么做),会有新的session指向旧的有未完成task的session。为了排除这种不确定性因素,所以在初始化的时候把里面绑定的代理之类的东西都置为nil了。或许这就是防御性编程思想的一种体现吧。

初始化方法到这就全部完成了。


接着我们来看看网络请求:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

- (NSURLSessionDataTask *)GET:(NSString *)URLString

                   parameters:(id)parameters

                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress

                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success

                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure

     //生成一个task

    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"

                                                        URLString:URLString

                                                       parameters:parameters

                                                   uploadProgress:nil

                                                 downloadProgress:downloadProgress

                                                          success:success

                                                          failure:failure];

    //开始网络请求

    [dataTask resume];

    return dataTask;

方法走到类AFHTTPSessionManager中来,调用父类,也就是我们整个AF3.x的核心类AFURLSessionManager的方法,生成了一个系统的NSURLSessionDataTask实例,并且开始网络请求。

我们继续往父类里看,看看这个方法到底做了什么:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method

                                       URLString:(NSString *)URLString

                                      parameters:(id)parameters

                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress

                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress

                                         success:(void (^)(NSURLSessionDataTask *, id))success

                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure

    NSError *serializationError = nil;

    //把参数,还有各种东西转化为一个request

    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

    if (serializationError) 

        if (failure) 

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wgnu"

            //如果解析错误,直接返回

            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^

                failure(nil, serializationError);

            );

#pragma clang diagnostic pop

        

        return nil;

    

    __block NSURLSessionDataTask *dataTask = nil;

    dataTask = [self dataTaskWithRequest:request

                          uploadProgress:uploadProgress

                        downloadProgress:downloadProgress

                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) 

        if (error) 

            if (failure) 

                failure(dataTask, error);

            

         else 

            if (success) 

                success(dataTask, responseObject);

            

        

    ];

    return dataTask;

  • 这个方法做了两件事:

      1.用self.requestSerializer和各种参数去获取了一个我们最终请求网络需要的NSMutableURLRequest实例。

      2.调用另外一个方法dataTaskWithRequest去拿到我们最终需要的NSURLSessionDataTask实例,并且在完成的回调里,调用我们传过来的成功和失败的回调。

  • 注意下面这个方法,我们常用来 push pop搭配,来忽略一些编译器的警告:

1

2

3

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wgnu"

#pragma clang diagnostic pop

这里是用来忽略:?带来的警告,具体的各种编译器警告描述,可以参考这篇:各种编译器的警告。

  • 说到底这个方法还是没有做实事,我们继续到requestSerializer方法里去看,看看AF到底如何拼接成我们需要的request的:

接着我们跑到AFURLRequestSerialization类中:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method

                                 URLString:(NSString *)URLString

                                parameters:(id)parameters

                                     error:(NSError *__autoreleasing *)error

    //断言,debug模式下,如果缺少改参数,crash

    NSParameterAssert(method);

    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];

    mutableRequest.HTTPMethod = method;

    //将request的各种属性循环遍历

    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) 

        //如果自己观察到的发生变化的属性,在这些方法里

        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) 

           //把给自己设置的属性给request设置

            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];

        

    

    //将传入的parameters进行编码,并添加到request中

    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;

  • 讲一下这个方法,这个方法做了3件事:

1)设置request的请求类型,get,post,put...等

2)往request里添加一些参数设置,其中AFHTTPRequestSerializerObservedKeyPaths()是一个c函数,返回一个数组,我们来看看这个函数:

1

2

3

4

5

6

7

8

9

10

11

static NSArray * AFHTTPRequestSerializerObservedKeyPaths() 

  static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;

  static dispatch_once_t onceToken;

  // 此处需要observer的keypath为allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies

  // HTTPShouldUsePipelining、networkServiceType、timeoutInterval

  dispatch_once(&onceToken, ^

      _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];

  );

  //就是一个数组里装了很多方法的名字,

  return _AFHTTPRequestSerializerObservedKeyPaths;

其实这个函数就是封装了一些属性的名字,这些都是NSUrlRequest的属性。

再来看看self.mutableObservedChangedKeyPaths,这个是当前类的一个属性:

1

@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;

在-init方法对这个集合进行了初始化,并且对当前类的和NSUrlRequest相关的那些属性添加了KVO监听:

1

2

3

4

5

6

7

8

//每次都会重置变化

  self.mutableObservedChangedKeyPaths = [NSMutableSet set];

  //给这自己些方法添加观察者为自己,就是request的各种属性,set方法

  for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) 

      if ([self respondsToSelector:NSSelectorFromString(keyPath)]) 

          [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];

      

  

KVO触发的方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

-(void)observeValueForKeyPath:(NSString *)keyPath

                    ofObject:(__unused id)object

                      change:(NSDictionary *)change

                     context:(void *)context

  //当观察到这些set方法被调用了,而且不为Null就会添加到集合里,否则移除

  if (context == AFHTTPRequestSerializerObserverContext) 

      if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) 

          [self.mutableObservedChangedKeyPaths removeObject:keyPath];

       else 

          [self.mutableObservedChangedKeyPaths addObject:keyPath];

      

  

至此我们知道self.mutableObservedChangedKeyPaths其实就是我们自己设置的request属性值的集合。

接下来调用:

1

[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];

用KVC的方式,把属性值都设置到我们请求的request中去。

3)把需要传递的参数进行编码,并且设置到request中去:

//将传入的parameters进行编码,并添加到request中

1

mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request

                             withParameters:(id)parameters

                                      error:(NSError *__autoreleasing *)error

  NSParameterAssert(request);

  NSMutableURLRequest *mutableRequest = [request mutableCopy];

  //从自己的head里去遍历,如果有值则设置给request的head

  [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) 

      if (![request valueForHTTPHeaderField:field]) 

          [mutableRequest setValue:value forHTTPHeaderField:field];

      

  ];

  //来把各种类型的参数,array dic set转化成字符串,给request

  NSString *query = nil;

  if (parameters) 

      //自定义的解析方式

      if (self.queryStringSerialization) 

          NSError *serializationError;

          query = self.queryStringSerialization(request, parameters, &serializationError);

          if (serializationError) 

              if (error) 

                  *error = serializationError;

              

              return nil;

          

       else 

          //默认解析方式

          switch (self.queryStringSerializationStyle) 

              case AFHTTPRequestQueryStringDefaultStyle:

                  query = AFQueryStringFromParameters(parameters);

                  break;

          

      

  

  //最后判断该request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)。因为这几个method的quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的。

  if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) 

      if (query && query.length > 0) 

          mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];

      

   else 

      //post put请求

      // #2864: an empty string is a valid x-www-form-urlencoded payload

      if (!query) 

          query = @"";

      

      if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) 

          [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

      

      //设置请求体

      [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];

  

  return mutableRequest;

这个方法做了3件事:

1.从self.HTTPRequestHeaders中拿到设置的参数,赋值要请求的request里去

2.把请求网络的参数,从array dic set这些容器类型转换为字符串,具体转码方式,我们可以使用自定义的方式,也可以用AF默认的转码方式。自定义的方式没什么好说的,想怎么去解析由你自己来决定。我们可以来看看默认的方式:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

NSString * AFQueryStringFromParameters(NSDictionary *parameters) 

  NSMutableArray *mutablePairs = [NSMutableArray array];

  //把参数给AFQueryStringPairsFromDictionary,拿到AF的一个类型的数据就一个key,value对象,在URLEncodedStringValue拼接keyValue,一个加到数组里

  for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) 

      [mutablePairs addObject:[pair URLEncodedStringValue]];

  

  //拆分数组返回参数字符串

  return [mutablePairs componentsJoinedByString:@"&"];

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) 

  //往下调用

  return AFQueryStringPairsFromKeyAndValue(nil, dictionary);

NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) 

  NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

  // 根据需要排列的对象的description来进行升序排列,并且selector使用的是compare:

  // 因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数

  // 即@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]

  NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

  //判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array dic set以外的元素,然后把得到的参数数组返回。

  if ([value isKindOfClass:[NSDictionary class]]) 

      NSDictionary *dictionary = value;

      // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries

      //拿到

      for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) 

          id nestedValue = dictionary[nestedKey];

          if (nestedValue) 

              [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];

          

      

   else if ([value isKindOfClass:[NSArray class]]) 

      NSArray *array = value;

      for (id nestedValue in array) 

          [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];

      

   else if ([value isKindOfClass:[NSSet class]]) 

      NSSet *set = value;

      for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) 

          [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];

      

   else 

      [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];

  

  return mutableQueryStringComponents;

  • 转码主要是以上三个函数,配合着注释应该也很好理解:主要是在递归调用AFQueryStringPairsFromKeyAndValue。判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array dic set以外的元素,然后把得到的参数数组返回。

  • 其中有个AFQueryStringPair对象,其只有两个属性和两个方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

@property (readwrite, nonatomic, strong) id field;

@property (readwrite, nonatomic, strong) id value;

- (instancetype)initWithField:(id)field value:(id)value 

self = [super init];

if (!self) 

    return nil;

self.field = field;

self.value = value;

return self;

- (NSString *)URLEncodedStringValue 

if (!self.value || [self.value isEqual:[NSNull null]]) 

    return AFPercentEscapedStringFromString([self.field description]);

 else 

    return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];

方法很简单,现在我们也很容易理解这整个转码过程了,我们举个例子梳理下,就是以下这3步:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

 @"name" : @"bang"

 @"phone": @@"mobile": @"xx", @"home": @"xx"

 @"families": @[@"father", @"mother"], 

 @"nums": [NSSet setWithObjects:@"1", @"2", nil] 

 

-> 

@[ 

 field: @"name", value: @"bang"

 field: @"phone[mobile]", value: @"xx"

 field: @"phone[home]", value: @"xx"

 field: @"families[]", value: @"father"

 field: @"families[]", value: @"mother"

 field: @"nums", value: @"1"

 field: @"nums", value: @"2"

-> 

name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2

至此,我们原来的容器类型的参数,就这样变成字符串类型了。

紧接着这个方法还根据该request中请求类型,来判断参数字符串应该如何设置到request中去。如果是GET、HEAD、DELETE,则把参数quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) 

  if (query && query.length > 0) 

      mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];

  

 else 

  //post put请求

  // #2864: an empty string is a valid x-www-form-urlencoded payload

  if (!query) 

      query = @"";

  

  if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) 

      [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];

  

  //设置请求体

  [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];

至此,我们生成了一个request。


我们再回到AFHTTPSessionManager类中来,回到这个方法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method

                                       URLString:(NSString *)URLString

                                      parameters:(id)parameters

                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress

                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress

                                         success:(void (^)(NSURLSessionDataTask *, id))success

                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure

    NSError *serializationError = nil;

    //把参数,还有各种东西转化为一个request

    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];

    if (serializationError) 

        if (failure) 

#pragma clang diagnostic push

#pragma clang diagnostic ignored "-Wgnu"

            //如果解析错误,直接返回

            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^

                failure(nil, serializationError);

            );

#pragma clang diagnostic pop

        

        return nil;

    

    __block NSURLSessionDataTask *dataTask = nil;

    dataTask = [self dataTaskWithRequest:request

                          uploadProgress:uploadProgress

                        downloadProgress:downloadProgress

                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) 

        if (error) 

            if (failure) 

                failure(dataTask, error);

            

         else 

            if (success) 

                success(dataTask, responseObject);

            

        

    ];

    return dataTask;

绕了一圈我们又回来了。。

  • 我们继续往下看:当解析错误,我们直接调用传进来的fauler的Block失败返回了,这里有一个self.completionQueue,这个是我们自定义的,这个是一个GCD的Queue如果设置了那么从这个Queue中回调结果,否则从主队列回调。

  • 实际上这个Queue还是挺有用的,之前还用到过。我们公司有自己的一套数据加解密的解析模式,所以我们回调回来的数据并不想是主线程,我们可以设置这个Queue,在分线程进行解析数据,然后自己再调回到主线程去刷新UI。

言归正传,我们接着调用了父类的生成task的方法,并且执行了一个成功和失败的回调,我们接着去父类AFURLSessionManger里看(总算到我们的核心类了..):

1

2

3

4

5

6

7

8

9

10

11

12

13

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

    //第一件事,创建NSURLSessionDataTask,里面适配了Ios8以下taskIdentifiers,函数创建task对象。

    //其实现应该是因为iOS 8.0以下版本中会并发地创建多个task对象,而同步有没有做好,导致taskIdentifiers 不唯一…这边做了一个串行处理

    url_session_manager_create_task_safely(^

        dataTask = [self.session dataTaskWithRequest:request];

    );

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;

  • 我们注意到这个方法非常简单,就调用了一个url_session_manager_create_task_safely()函数,传了一个Block进去,Block里就是iOS原生生成dataTask的方法。此外,还调用了一个addDelegateForDataTask的方法。

  • 我们到这先到这个函数里去看看:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

static void url_session_manager_create_task_safely(dispatch_block_t block) 

  if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) 

      // Fix of bug

      // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)

      // Issue about:When tasks are created on a concurrent queue, incorrect completionHandlers can get called. · Issue #2093 · AFNetworking/AFNetworking · GitHub

    //理解下,第一为什么用sync,因为是想要主线程等在这,等执行完,在返回,因为必须执行完dataTask才有数据,传值才有意义。

    //第二,为什么要用串行队列,因为这块是为了防止ios8以下内部的dataTaskWithRequest是并发创建的,

    //这样会导致taskIdentifiers这个属性值不唯一,因为后续要用taskIdentifiers来作为Key对应delegate。

      dispatch_sync(url_session_manager_creation_queue(), block);

   else 

      block();

  

static dispatch_queue_t url_session_manager_creation_queue() 

  static dispatch_queue_t af_url_session_manager_creation_queue;

  static dispatch_once_t onceToken;

  //保证了即使是在多线程的环境下,也不会创建其他队列

  dispatch_once(&onceToken, ^

      af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);

  );

  return af_url_session_manager_creation_queue;

  • 方法非常简单,关键是理解这么做的目的:为什么我们不直接去调用

1

dataTask = [self.session dataTaskWithRequest:request];

非要绕这么一圈,我们点进去bug日志里看看,原来这是为了适配iOS8的以下,创建session的时候,偶发的情况会出现session的属性taskIdentifier这个值不唯一,而这个taskIdentifier是我们后面来映射delegate的key,所以它必须是唯一的。

  • 具体原因应该是NSURLSession内部去生成task的时候是用多线程并发去执行的。想通了这一点,我们就很好解决了,我们只需要在iOS8以下同步串行的去生成task就可以防止这一问题发生(如果还是不理解同步串行的原因,可以看看注释)。

  • 题外话:很多同学都会抱怨为什么sync我从来用不到,看,有用到的地方了吧,很多东西不是没用,而只是你想不到怎么用。

我们接着看到:

1

[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

调用到:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

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

    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];

    // AFURLSessionManagerTaskDelegate与AFURLSessionManager建立相互关系

    delegate.manager = self;

    delegate.completionHandler = completionHandler;

    //这个taskDescriptionForSessionTasks用来发送开始和挂起通知的时候会用到,就是用这个值来Post通知,来两者对应

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;

    // ***** 将AF delegate对象与 dataTask建立关系

    [self setDelegate:delegate forTask:dataTask];

    // 设置AF delegate的上传进度,下载进度块。

    delegate.uploadProgressBlock = uploadProgressBlock;

    delegate.downloadProgressBlock = downloadProgressBlock;

总结一下:

1)这个方法,生成了一个AFURLSessionManagerTaskDelegate,这个其实就是AF的自定义代理。我们请求传来的参数,都赋值给这个AF的代理了。

2)delegate.manager = self;代理把AFURLSessionManager这个类作为属性了,我们可以看到:

1

@property (nonatomic, weak) AFURLSessionManager *manager;

这个属性是弱引用的,所以不会存在循环引用的问题。

3)我们调用了[self setDelegate:delegate forTask:dataTask];

我们进去看看这个方法做了什么:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate

            forTask:(NSURLSessionTask *)task

    //断言,如果没有这个参数,debug下crash在这

    NSParameterAssert(task);

    NSParameterAssert(delegate);

    //加锁保证字典线程安全

    [self.lock lock];

    // 将AF delegate放入以taskIdentifier标记的词典中(同一个NSURLSession中的taskIdentifier是唯一的)

    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;

    // 为AF delegate 设置task 的progress监听

    [delegate setupProgressForTask:task];

    //添加task开始和暂停的通知

    [self addNotificationObserverForTask:task];

    [self.lock unlock];