美文网首页
龟速学习AFNetworking--01

龟速学习AFNetworking--01

作者: 元昊 | 来源:发表于2017-09-22 17:32 被阅读26次

前言

学习一下大神的编码思想是很有必要的,另外用了怎么久的开源库不认真对待有点说不过去。这里借助部分博客以及源码来学习。如有不足,多多指点!

目录

  • AFNetworking架构及主要模块的功能
  • AFNetworking具体模块的探究

AFNetworking架构及主要模块的功能

AFNetworking的导入这里就不多介绍,直接放目录


屏幕快照 2017-09-22 17.03.55.png

可以清楚的看到AFNetworking大致分为以下几个模块

  • 网络通信模块
    • AFHTTPSessionManager
    • AFURLSessionManager
  • 网络状态监听模块
    • AFNetworkReachabilityManager
  • 网络通信安全策略模块(有关https的部分)
    • AFSecurityPolicy
  • 网络通信信息序列化/反序列化模块
    • AFURLRequestSerialization
    • AFURLResponseSerialization
  • 对于iOS UIKit库的扩展(UIKit)
  • UIKit
    UIKit与其他的没有直接关系。除去后
屏幕快照 2017-09-22 17.29.58.png

其中AFHTTPSessionManager是继承于AFURLSessionManager的,我们一般做网络请求都是用这个类,但是它本身是没有做实事的,只是做了一些简单的封装,把请求逻辑分发给父类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;
    }

    // Ensure terminal slash for baseURL path, so that NSURL+URLWithString:relativeToURL: works as expected
    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最最重要的AFURLSessionManager。几乎所有的类都是围绕着这个类在处理业务逻辑。
  • 除此之外,方法中把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];
    self.operationQueue.maxConcurrentOperationCount = 1;
//初始化session,设置代理
    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了。
    • 第二就是我们初始化了一些属性,其中包括self.mutableTaskDelegatesKeyedByTaskIdentifier,这个是用来让每一个请求task和我们自定义的AF自定义代理来建立映射用的,其实对task的代理运行了一个封装,并且转发代理到AF代理来建立映射用,其实AF对task的代理进行了一个封装,并且转发代理到AF自定义的代理,这是AF比较重要的一部分,接下来我们会具体讲这一块。
    • 第三就是下面这个方法:
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { 
}];

首先说说这个方法是干什么的:这个方法用来异步的获取当前session的所有未完成task。其实讲道理来说在初始化中调用这个方法应该里面一个task都不会有。我们打断点 去看看,也确实如此,里面的数组都是空的。
- **原来这是无力从后台回来,重新初始化session,防止一些之前的后台请求任务,导致程序crash。

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

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

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

    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实例,并且开始网络请求。
我们继续往父类里看,看看这个方法到底做了什么:

- (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;
    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 搭配,来忽略一些编译器的警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop

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

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

接着我们跑到AFURLRequestSerialization类中:

- (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函数,返回一个数组,我们来看看这个函数:
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,这个是当前类的一个属性:

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

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

//每次都会重置变化:
 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触发的方法:

- (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属性值的集合。
接下来调用:

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

用KVC的方式,把属性值都设置到我们请求的request中去。
3)把需要传递的参数进行编码,并且设置到request中去:

//将传入的parameters进行编码,并添加到request中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
#pragma mark - AFURLRequestSerialization

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
//到自己的header中遍历,如果有值则设置给request的header
    [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 {
//AF默认解析方式
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    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 {
        // #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默认的转码方式。自定义的方式没什么的由自己决定。下面看看默认的方式:

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

//根据需要排列的对象的descript来进行升序排列,并且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对象,其中只有两个属性和两个方法:
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

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

- (NSString *)URLEncodedStringValue;
@end

@implementation AFQueryStringPair

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

以上方法举个例子梳理下:

@{ 
 @"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中:

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 {
        // #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类中来,回到这个方法:

相关文章

  • 龟速学习AFNetworking--01

    前言 学习一下大神的编码思想是很有必要的,另外用了怎么久的开源库不认真对待有点说不过去。这里借助部分博客以及源码来...

  • 蜗行

    大家听说过龟速,这会我说个比龟速还龟速的,我称作蜗行吧。 高中今天下午放假,四点半,长300米,宽...

  • 我是移动

    移动,龟速,慢速

  • 龟速

  • 龟速

    这几片叶子是一个月前起的稿子,今天才画完,我只能用龟速来形容自己了!

  • 龟速

    刚刚又看见在收书,算了一下子,要求日更3千以上,三天跟新一万字,唉,我的这种乌龟速度,是很难达到的,只能看看,眼馋...

  • 学习方法

    高手都是有学习方法的吧? 今天开始做题,不做怎么不知道,一做吓一跳。那个速度呀,可谓是龟速得不能再龟速了。 汗颜得...

  • 练字篇(十六)

    今昔对比,龟速前行

  • 很久才被记起来的跑步🏃

    老公龟速跟在身后,不怕不怕

  • 大牛为什么高效(欢迎讨论)

    同样一件事, 比如学习一门新的语言, 为什么有的龟速,有的光速!

网友评论

      本文标题:龟速学习AFNetworking--01

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