11.第三方源碼-AFNetworking解析

AFNetWorking
AFNetWorking一款輕量級(jí)網(wǎng)絡(luò)請(qǐng)求開(kāi)源框架眯漩,基于iOS和mac os 網(wǎng)絡(luò)進(jìn)行擴(kuò)展的高性能框架赫模,大大降低了iOS開(kāi)發(fā)工程師處理網(wǎng)絡(luò)請(qǐng)求的難度慎宾,讓iOS開(kāi)發(fā)變成一件愉快的事情。

GitHub地址:https://github.com/AFNetworking/AFNetworking

AFN優(yōu)點(diǎn):

1.原有基礎(chǔ)urlsesson上封裝了一層甸赃,在傳參方面更靈活柿汛,
2.回調(diào)更友好,
3.支持返回?cái)?shù)據(jù)序列化
4.支持文件上傳埠对,斷點(diǎn)下載络断,
5.自帶多線程,防死鎖
6.處理了Https證書流程项玛,節(jié)省移動(dòng)端開(kāi)發(fā)
7.支持網(wǎng)絡(luò)狀態(tài)判斷

先從最新的AF3.x講起吧:

  • 首先貌笨,我們就一起分析一下該框架的組成。
    將AF下載導(dǎo)入工程后襟沮,下面是其包結(jié)構(gòu)躁绸,相對(duì)于2.x變得非常簡(jiǎn)單了:
image.png

除去Support Files,可以看到AF分為如下5個(gè)功能模塊:

  • 網(wǎng)絡(luò)通信模塊(AFURLSessionManager臣嚣、AFHTTPSessionManger)
  • 網(wǎng)絡(luò)狀態(tài)監(jiān)聽(tīng)模塊(Reachability)
  • 網(wǎng)絡(luò)通信安全策略模塊(Security)
  • 網(wǎng)絡(luò)通信信息序列化/反序列化模塊(Serialization)
  • 對(duì)于iOS UIKit庫(kù)的擴(kuò)展(UIKit)


    image.png
其核心當(dāng)然是網(wǎng)絡(luò)通信模塊AFURLSessionManager净刮。大家都知道,AF3.x是基于NSURLSession來(lái)封裝的硅则。所以這個(gè)類圍繞著NSURLSession做了一系列的封裝淹父。而其余的四個(gè)模塊,均是為了配合網(wǎng)絡(luò)通信或?qū)σ延蠻IKit的一個(gè)擴(kuò)展工具包怎虫。

這五個(gè)模塊所對(duì)應(yīng)的類的結(jié)構(gòu)關(guān)系圖如下所示:

image.png

其中AFHTTPSessionManager是繼承于AFURLSessionManager的暑认,我們一般做網(wǎng)絡(luò)請(qǐng)求都是用這個(gè)類,但是它本身是沒(méi)有做實(shí)事的大审,只是做了一些簡(jiǎn)單的封裝蘸际,把請(qǐng)求邏輯分發(fā)給父類AFURLSessionManager或者其它類去做。

首先我們簡(jiǎn)單的寫個(gè)get請(qǐng)求:
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) {

}];

首先我們我們調(diào)用了初始化方法生成了一個(gè)manager徒扶,我們點(diǎn)進(jìn)去看看初始化做了什么:

- (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;
    }
    //對(duì)傳過(guò)來(lái)的BaseUrl進(jìn)行處理粮彤,如果有值且最后不包含/,url加上"/"
  //--經(jīng)一位熱心讀者更正...以后注釋也一定要走心啊...不能誤導(dǎo)大家...
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    self.baseURL = url;

    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

  • 初始化都調(diào)用到- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法中來(lái)了。
  • 其實(shí)初始化方法都調(diào)用父類的初始化方法导坟。父類也就是AF3.x最最核心的類AFURLSessionManager屿良。幾乎所有的類都是圍繞著這個(gè)類在處理業(yè)務(wù)邏輯。
  • 除此之外惫周,方法中把baseURL存了起來(lái)尘惧,還生成了一個(gè)請(qǐng)求序列對(duì)象和一個(gè)響應(yīng)序列對(duì)象。后面再細(xì)說(shuō)這兩個(gè)類是干什么用的递递。

直接來(lái)到父類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并發(fā)線程數(shù)設(shè)置為1
    self.operationQueue.maxConcurrentOperationCount = 1;

    //注意代理喷橙,代理的繼承,實(shí)際上NSURLSession去判斷了登舞,你實(shí)現(xiàn)了哪個(gè)方法會(huì)去調(diào)用重慢,包括子代理的方法!
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

    //各種響應(yīng)轉(zhuǎn)碼
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    //設(shè)置默認(rèn)安全策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

#if !TARGET_OS_WATCH
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
    // 設(shè)置存儲(chǔ)NSURL task與AFURLSessionManagerTaskDelegate的詞典(重點(diǎn)逊躁,在AFNet中似踱,每一個(gè)task都會(huì)被匹配一個(gè)AFURLSessionManagerTaskDelegate 來(lái)做task的delegate事件處理) ===============
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    //  設(shè)置AFURLSessionManagerTaskDelegate 詞典的鎖,確保詞典在多線程訪問(wèn)時(shí)的線程安全
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    // 置空task關(guān)聯(lián)的代理
    [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;
}

  • 這個(gè)就是最終的初始化方法了稽煤,注釋應(yīng)該寫的很清楚核芽,唯一需要說(shuō)的就是三點(diǎn):
    • self.operationQueue.maxConcurrentOperationCount = 1;這個(gè)operationQueue就是我們代理回調(diào)的queue。這里把代理回調(diào)的線程并發(fā)數(shù)設(shè)置為1了酵熙。至于這里為什么要這么做轧简,我們先留一個(gè)坑,等我們講完AF2.x之后再來(lái)分析這一塊匾二。
  • 第二就是我們初始化了一些屬性哮独,其中包括self.mutableTaskDelegatesKeyedByTaskIdentifier,這個(gè)是用來(lái)讓每一個(gè)請(qǐng)求task和我們自定義的AF代理來(lái)建立映射用的察藐,其實(shí)AF對(duì)task的代理進(jìn)行了一個(gè)封裝皮璧,并且轉(zhuǎn)發(fā)代理到AF自定義的代理,這是AF比較重要的一部分分飞,接下來(lái)我們會(huì)具體講這一塊悴务。
  • 第三就是下面這個(gè)方法:
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { 
}];

首先說(shuō)說(shuō)這個(gè)方法是干什么用的:這個(gè)方法用來(lái)異步的獲取當(dāng)前session的所有未完成的task。其實(shí)講道理來(lái)說(shuō)在初始化中調(diào)用這個(gè)方法應(yīng)該里面一個(gè)task都不會(huì)有譬猫。我們打斷點(diǎn)去看讯檐,也確實(shí)如此,里面的數(shù)組都是空的染服。
但是想想也知道别洪,AF大神不會(huì)把一段沒(méi)用的代碼放在這吧。輾轉(zhuǎn)多處柳刮,終于從AF的issue中找到了結(jié)論:github 挖垛。

  • 原來(lái)這是為了防止后臺(tái)回來(lái)痒钝,重新初始化這個(gè)session,一些之前的后臺(tái)請(qǐng)求任務(wù)晕换,導(dǎo)致程序的crash午乓。

初始化方法到這就全部完成了站宗。

image.png

接著我們來(lái)看看網(wǎng)絡(luò)請(qǐng)求:

- (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
{
     //生成一個(gè)task
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];

    //開(kāi)始網(wǎng)絡(luò)請(qǐng)求
    [dataTask resume];

    return dataTask;
}

方法走到類AFHTTPSessionManager中來(lái)闸准,調(diào)用父類,也就是我們整個(gè)AF3.x的核心類AFURLSessionManager的方法梢灭,生成了一個(gè)系統(tǒng)的NSURLSessionDataTask實(shí)例夷家,并且開(kāi)始網(wǎng)絡(luò)請(qǐng)求。
我們繼續(xù)往父類里看敏释,看看這個(gè)方法到底做了什么:

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

    //把參數(shù)库快,還有各種東西轉(zhuǎn)化為一個(gè)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"
            //如果解析錯(cuò)誤,直接返回
            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;
}

  • 這個(gè)方法做了兩件事:
    1.用self.requestSerializer和各種參數(shù)去獲取了一個(gè)我們最終請(qǐng)求網(wǎng)絡(luò)需要的NSMutableURLRequest實(shí)例钥顽。
    2.調(diào)用另外一個(gè)方法dataTaskWithRequest去拿到我們最終需要的NSURLSessionDataTask實(shí)例义屏,并且在完成的回調(diào)里,調(diào)用我們傳過(guò)來(lái)的成功和失敗的回調(diào)蜂大。
  • 注意下面這個(gè)方法闽铐,我們常用來(lái) push pop搭配,來(lái)忽略一些編譯器的警告:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#pragma clang diagnostic pop

這里是用來(lái)忽略:奶浦?帶來(lái)的警告兄墅,具體的各種編譯器警告描述,可以參考這篇:各種編譯器的警告澳叉。

  • 說(shuō)到底這個(gè)方法還是沒(méi)有做實(shí)事隙咸,我們繼續(xù)到requestSerializer方法里去看,看看AF到底如何拼接成我們需要的request的:

接著我們跑到AFURLRequestSerialization類中:

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    //斷言成洗,debug模式下五督,如果缺少改參數(shù),crash
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    //將request的各種屬性循環(huán)遍歷
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        //如果自己觀察到的發(fā)生變化的屬性瓶殃,在這些方法里
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
           //把給自己設(shè)置的屬性給request設(shè)置
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    //將傳入的parameters進(jìn)行編碼概荷,并添加到request中
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

  • 講一下這個(gè)方法,這個(gè)方法做了3件事:
    1)設(shè)置request的請(qǐng)求類型碌燕,get,post,put...等
    2)往request里添加一些參數(shù)設(shè)置误证,其中AFHTTPRequestSerializerObservedKeyPaths()是一個(gè)c函數(shù),返回一個(gè)數(shù)組修壕,我們來(lái)看看這個(gè)函數(shù):
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))];
    });
    //就是一個(gè)數(shù)組里裝了很多方法的名字,
    return _AFHTTPRequestSerializerObservedKeyPaths;
}

其實(shí)這個(gè)函數(shù)就是封裝了一些屬性的名字,這些都是NSUrlRequest的屬性。
再來(lái)看看self.mutableObservedChangedKeyPaths,這個(gè)是當(dāng)前類的一個(gè)屬性:

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

在-init方法對(duì)這個(gè)集合進(jìn)行了初始化譬巫,并且對(duì)當(dāng)前類的和NSUrlRequest相關(guān)的那些屬性添加了KVO監(jiān)聽(tīng)

 //每次都會(huì)重置變化
    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觸發(fā)的方法:

-(void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    //當(dāng)觀察到這些set方法被調(diào)用了芦昔,而且不為Null就會(huì)添加到集合里诱贿,否則移除
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}

至此我們知道self.mutableObservedChangedKeyPaths其實(shí)就是我們自己設(shè)置的request屬性值的集合。
接下來(lái)調(diào)用:

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

用KVC的方式咕缎,把屬性值都設(shè)置到我們請(qǐng)求的request中去珠十。

3)把需要傳遞的參數(shù)進(jìn)行編碼,并且設(shè)置到request中去:

//將傳入的parameters進(jìn)行編碼凭豪,并添加到request中
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

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

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    //從自己的head里去遍歷焙蹭,如果有值則設(shè)置給request的head
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    //來(lái)把各種類型的參數(shù),array dic set轉(zhuǎn)化成字符串嫂伞,給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 {
            //默認(rèn)解析方式
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    //最后判斷該request中是否包含了GET孔厉、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)帖努。因?yàn)檫@幾個(gè)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請(qǐng)求

        // #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"];
        }
        //設(shè)置請(qǐng)求體
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}

這個(gè)方法做了3件事:
1.從self.HTTPRequestHeaders中拿到設(shè)置的參數(shù)郑趁,賦值要請(qǐng)求的request里去
2.把請(qǐng)求網(wǎng)絡(luò)的參數(shù),從array dic set這些容器類型轉(zhuǎn)換為字符串姿搜,具體轉(zhuǎn)碼方式寡润,我們可以使用自定義的方式,也可以用AF默認(rèn)的轉(zhuǎn)碼方式舅柜。自定義的方式?jīng)]什么好說(shuō)的梭纹,想怎么去解析由你自己來(lái)決定。我們可以來(lái)看看默認(rèn)的方式:

NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];

    //把參數(shù)給AFQueryStringPairsFromDictionary致份,拿到AF的一個(gè)類型的數(shù)據(jù)就一個(gè)key变抽,value對(duì)象,在URLEncodedStringValue拼接keyValue氮块,一個(gè)加到數(shù)組里
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    //拆分?jǐn)?shù)組返回參數(shù)字符串
    return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    //往下調(diào)用
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    // 根據(jù)需要排列的對(duì)象的description來(lái)進(jìn)行升序排列绍载,并且selector使用的是compare:
    // 因?yàn)閷?duì)象的description返回的是NSString,所以此處compare:使用的是NSString的compare函數(shù)
    // 即@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    //判斷vaLue是什么類型的滔蝉,然后去遞歸調(diào)用自己击儡,直到解析的是除了array dic set以外的元素,然后把得到的參數(shù)數(shù)組返回蝠引。
    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;
}

  • 轉(zhuǎn)碼主要是以上三個(gè)函數(shù)阳谍,配合著注釋應(yīng)該也很好理解:主要是在遞歸調(diào)用AFQueryStringPairsFromKeyAndValue蛀柴。判斷vaLue是什么類型的,然后去遞歸調(diào)用自己矫夯,直到解析的是除了array dic set以外的元素鸽疾,然后把得到的參數(shù)數(shù)組返回。
  • 其中有個(gè)AFQueryStringPair對(duì)象训貌,其只有兩個(gè)屬性和兩個(gè)方法:
@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])];
    }
}

方法很簡(jiǎn)單制肮,現(xiàn)在我們也很容易理解這整個(gè)轉(zhuǎn)碼過(guò)程了,我們舉個(gè)例子梳理下旺订,就是以下這3步:

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

至此弄企,我們?cè)瓉?lái)的容器類型的參數(shù)超燃,就這樣變成字符串類型了区拳。

緊接著這個(gè)方法還根據(jù)該request中請(qǐng)求類型,來(lái)判斷參數(shù)字符串應(yīng)該如何設(shè)置到request中去意乓。如果是GET樱调、HEAD、DELETE届良,則把參數(shù)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請(qǐng)求

    // #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"];
    }
    //設(shè)置請(qǐng)求體
    [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}

至此士葫,我們生成了一個(gè)request乞而。

image.png
我們?cè)倩氐紸FHTTPSessionManager類中來(lái),回到這個(gè)方法:
- (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;
    //把參數(shù),還有各種東西轉(zhuǎn)化為一個(gè)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"
            //如果解析錯(cuò)誤慢显,直接返回
            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;
}

繞了一圈我們又回來(lái)了爪模。。

  • 我們繼續(xù)往下看:當(dāng)解析錯(cuò)誤荚藻,我們直接調(diào)用傳進(jìn)來(lái)的fauler的Block失敗返回了屋灌,這里有一個(gè)self.completionQueue,這個(gè)是我們自定義的,這個(gè)是一個(gè)GCD的Queue如果設(shè)置了那么從這個(gè)Queue中回調(diào)結(jié)果应狱,否則從主隊(duì)列回調(diào)共郭。

  • 實(shí)際上這個(gè)Queue還是挺有用的,之前還用到過(guò)疾呻。我們公司有自己的一套數(shù)據(jù)加解密的解析模式除嘹,所以我們回調(diào)回來(lái)的數(shù)據(jù)并不想是主線程,我們可以設(shè)置這個(gè)Queue,在分線程進(jìn)行解析數(shù)據(jù)岸蜗,然后自己再調(diào)回到主線程去刷新UI尉咕。

言歸正傳,我們接著調(diào)用了父類的生成task的方法散吵,并且執(zhí)行了一個(gè)成功和失敗的回調(diào)龙考,我們接著去父類AFURLSessionManger里看(總算到我們的核心類了..):

- (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;
    //第一件事蟆肆,創(chuàng)建NSURLSessionDataTask,里面適配了Ios8以下taskIdentifiers晦款,函數(shù)創(chuàng)建task對(duì)象炎功。
    //其實(shí)現(xiàn)應(yīng)該是因?yàn)閕OS 8.0以下版本中會(huì)并發(fā)地創(chuàng)建多個(gè)task對(duì)象,而同步有沒(méi)有做好缓溅,導(dǎo)致taskIdentifiers 不唯一…這邊做了一個(gè)串行處理蛇损,串行隊(duì)列里面還有用GCD創(chuàng)建了個(gè)單例
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

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

    return dataTask;
}

  • 我們注意到這個(gè)方法非常簡(jiǎn)單,就調(diào)用了一個(gè)url_session_manager_create_task_safely()函數(shù)坛怪,傳了一個(gè)Block進(jìn)去淤齐,Block里就是iOS原生生成dataTask的方法。此外袜匿,還調(diào)用了一個(gè)addDelegateForDataTask的方法更啄。
  • 我們到這先到這個(gè)函數(shù)里去看看:
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:https://github.com/AFNetworking/AFNetworking/issues/2093

      //理解下,第一為什么用sync居灯,因?yàn)槭窍胍骶€程等在這祭务,等執(zhí)行完,在返回怪嫌,因?yàn)楸仨殘?zhí)行完dataTask才有數(shù)據(jù)义锥,傳值才有意義。
      //第二岩灭,為什么要用串行隊(duì)列拌倍,因?yàn)檫@塊是為了防止ios8以下內(nèi)部的dataTaskWithRequest是并發(fā)創(chuàng)建的,
      //這樣會(huì)導(dǎo)致taskIdentifiers這個(gè)屬性值不唯一噪径,因?yàn)楹罄m(xù)要用taskIdentifiers來(lái)作為Key對(duì)應(yīng)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;
    //保證了即使是在多線程的環(huán)境下,也不會(huì)創(chuàng)建其他隊(duì)列
    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;
}

  • 方法非常簡(jiǎn)單熄云,關(guān)鍵是理解這么做的目的:為什么我們不直接去調(diào)用
    dataTask = [self.session dataTaskWithRequest:request];
    非要繞這么一圈膨更,我們點(diǎn)進(jìn)去bug日志里看看,原來(lái)這是為了適配iOS8的以下缴允,創(chuàng)建session的時(shí)候荚守,偶發(fā)的情況會(huì)出現(xiàn)session的屬性taskIdentifier這個(gè)值不唯一,而這個(gè)taskIdentifier是我們后面來(lái)映射delegate的key,所以它必須是唯一的练般。
  • 具體原因應(yīng)該是NSURLSession內(nèi)部去生成task的時(shí)候是用多線程并發(fā)去執(zhí)行的矗漾。想通了這一點(diǎn),我們就很好解決了薄料,我們只需要在iOS8以下同步串行的去生成task就可以防止這一問(wèn)題發(fā)生(如果還是不理解同步串行的原因敞贡,可以看看注釋)。
  • 題外話:很多同學(xué)都會(huì)抱怨為什么sync我從來(lái)用不到摄职,看誊役,有用到的地方了吧获列,很多東西不是沒(méi)用,而只是你想不到怎么用蛔垢。

我們接著看到:

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

調(diào)用到:

- (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建立相互關(guān)系
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    //這個(gè)taskDescriptionForSessionTasks用來(lái)發(fā)送開(kāi)始和掛起通知的時(shí)候會(huì)用到,就是用這個(gè)值來(lái)Post通知击孩,來(lái)兩者對(duì)應(yīng)
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;

    // ***** 將AF delegate對(duì)象與 dataTask建立關(guān)系
    [self setDelegate:delegate forTask:dataTask];

    // 設(shè)置AF delegate的上傳進(jìn)度,下載進(jìn)度塊鹏漆。
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

  • 總結(jié)一下:
    1)這個(gè)方法巩梢,生成了一個(gè)AFURLSessionManagerTaskDelegate,這個(gè)其實(shí)就是AF的自定義代理。我們請(qǐng)求傳來(lái)的參數(shù)艺玲,都賦值給這個(gè)AF的代理了括蝠。
    2)delegate.manager = self;代理把AFURLSessionManager這個(gè)類作為屬性了,我們可以看到:
@property (nonatomic, weak) AFURLSessionManager *manager;

這個(gè)屬性是弱引用的,所以不會(huì)存在循環(huán)引用的問(wèn)題饭聚。
3)我們調(diào)用了[self setDelegate:delegate forTask:dataTask];

我們進(jìn)去看看這個(gè)方法做了什么:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
            forTask:(NSURLSessionTask *)task
{
    //斷言忌警,如果沒(méi)有這個(gè)參數(shù),debug下crash在這
    NSParameterAssert(task);
    NSParameterAssert(delegate);

    //加鎖保證字典線程安全
    [self.lock lock];
    // 將AF delegate放入以taskIdentifier標(biāo)記的詞典中(同一個(gè)NSURLSession中的taskIdentifier是唯一的)
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;

    // 為AF delegate 設(shè)置task 的progress監(jiān)聽(tīng)
    [delegate setupProgressForTask:task];

    //添加task開(kāi)始和暫停的通知
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

  • 這個(gè)方法主要就是把AF代理和task建立映射若治,存在了一個(gè)我們事先聲明好的字典里慨蓝。
  • 而要加鎖的原因是因?yàn)楸旧砦覀冞@個(gè)字典屬性是mutable的碟刺,是線程不安全的疏唾。而我們對(duì)這些方法的調(diào)用幸斥,確實(shí)是會(huì)在復(fù)雜的多線程環(huán)境中,后面會(huì)仔細(xì)提到線程問(wèn)題婆跑。
  • 還有個(gè)[delegate setupProgressForTask:task];我們到方法里去看看:
- (void)setupProgressForTask:(NSURLSessionTask *)task {

    __weak __typeof__(task) weakTask = task;

    //拿到上傳下載期望的數(shù)據(jù)大小
    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;

    //將上傳與下載進(jìn)度和 任務(wù)綁定在一起,直接cancel suspend resume進(jìn)度條庭呜,可以cancel...任務(wù)
    [self.uploadProgress setCancellable:YES];
    [self.uploadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.uploadProgress setPausable:YES];
    [self.uploadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];

    if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.uploadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    [self.downloadProgress setCancellable:YES];
    [self.downloadProgress setCancellationHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask cancel];
    }];
    [self.downloadProgress setPausable:YES];
    [self.downloadProgress setPausingHandler:^{
        __typeof__(weakTask) strongTask = weakTask;
        [strongTask suspend];
    }];

    if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
        [self.downloadProgress setResumingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask resume];
        }];
    }

    //觀察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];

    //觀察progress這兩個(gè)屬性
    [self.downloadProgress addObserver:self
                            forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                               options:NSKeyValueObservingOptionNew
                               context:NULL];
    [self.uploadProgress addObserver:self
                          forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                             options:NSKeyValueObservingOptionNew
                             context:NULL];
}

  • 這個(gè)方法也非常簡(jiǎn)單滑进,主要做了以下幾件事:
    1)設(shè)置 downloadProgressuploadProgress的一些屬性,并且把兩者和task的任務(wù)狀態(tài)綁定在了一起募谎。注意這兩者都是NSProgress的實(shí)例對(duì)象扶关,(這里可能又一群小伙伴楞在這了,這是個(gè)什么...)簡(jiǎn)單來(lái)說(shuō)数冬,這就是iOS7引進(jìn)的一個(gè)用來(lái)管理進(jìn)度的類节槐,可以開(kāi)始,暫停拐纱,取消铜异,完整的對(duì)應(yīng)了task的各種狀態(tài),當(dāng)progress進(jìn)行各種操作的時(shí)候秸架,task也會(huì)引發(fā)對(duì)應(yīng)操作揍庄。
    2)給task和progress的各個(gè)屬及添加KVO監(jiān)聽(tīng),至于監(jiān)聽(tīng)了干什么用东抹,我們接著往下看:
 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {

    //是task
    if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
        //給進(jìn)度條賦新值
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
            self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
            self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
            self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
            self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
        }
    }
    //上面的賦新值會(huì)觸發(fā)這兩個(gè)蚂子,調(diào)用block回調(diào)沃测,用戶拿到進(jìn)度
    else if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

  • 方法非常簡(jiǎn)單直觀,主要就是如果task觸發(fā)KVO,則給progress進(jìn)度賦值食茎,應(yīng)為賦值了芽突,所以會(huì)觸發(fā)progress的KVO,也會(huì)調(diào)用到這里董瞻,然后去執(zhí)行我們傳進(jìn)來(lái)的downloadProgressBlockuploadProgressBlock寞蚌。主要的作用就是為了讓進(jìn)度實(shí)時(shí)的傳遞。

  • 主要是觀摩一下大神的寫代碼的結(jié)構(gòu)钠糊,這個(gè)解耦的編程思想挟秤,不愧是大神...

  • 還有一點(diǎn)需要注意:我們之前的setProgress和這個(gè)KVO監(jiān)聽(tīng),都是在我們AF自定義的delegate內(nèi)的抄伍,是有一個(gè)task就會(huì)有一個(gè)delegate的艘刚。所以說(shuō)我們是每個(gè)task都會(huì)去監(jiān)聽(tīng)這些屬性,分別在各自的AF代理內(nèi)截珍。看到這攀甚,可能有些小伙伴會(huì)有點(diǎn)亂,沒(méi)關(guān)系岗喉。等整個(gè)講完之后我們還會(huì)詳細(xì)的去講捋一捋manager秋度、task、還有AF自定義代理三者之前的對(duì)應(yīng)關(guān)系钱床。

到這里我們整個(gè)對(duì)task的處理就完成了荚斯。

image.png

接著task就開(kāi)始請(qǐng)求網(wǎng)絡(luò)了,還記得我們初始化方法中:

self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

我們把AFUrlSessionManager作為了所有的task的delegate查牌。當(dāng)我們請(qǐng)求網(wǎng)絡(luò)的時(shí)候事期,這些代理開(kāi)始調(diào)用了:

image.png
  • AFUrlSessionManager一共實(shí)現(xiàn)了如上圖所示這么一大堆NSUrlSession相關(guān)的代理。(小伙伴們的順序可能不一樣纸颜,樓主根據(jù)代理隸屬重新排序了一下)

  • 而只轉(zhuǎn)發(fā)了其中3條到AF自定義的delegate中:

image.png

這就是我們一開(kāi)始說(shuō)的兽泣,AFUrlSessionManager對(duì)這一大堆代理做了一些公共的處理,而轉(zhuǎn)發(fā)到AF自定義代理的3條胁孙,則負(fù)責(zé)把每個(gè)task對(duì)應(yīng)的數(shù)據(jù)回調(diào)出去唠倦。

又有小伙伴問(wèn)了,我們?cè)O(shè)置的這個(gè)代理不是NSURLSessionDelegate嗎浊洞?怎么能響應(yīng)NSUrlSession這么多代理呢牵敷?我們點(diǎn)到類的聲明文件中去看看:

@protocol NSURLSessionDelegate <NSObject>
@protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
@protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
@protocol NSURLSessionStreamDelegate <NSURLSessionTaskDelegate>

  • 我們可以看到這些代理都是繼承關(guān)系,而在NSURLSession實(shí)現(xiàn)中法希,只要設(shè)置了這個(gè)代理枷餐,它會(huì)去判斷這些所有的代理,是否respondsToSelector這些代理中的方法苫亦,如果響應(yīng)了就會(huì)去調(diào)用毛肋。
  • 而AF還重寫了respondsToSelector方法:
 - (BOOL)respondsToSelector:(SEL)selector {

    //復(fù)寫了selector的方法怨咪,這幾個(gè)方法是在本類有實(shí)現(xiàn)的,但是如果外面的Block沒(méi)賦值的話润匙,則返回NO诗眨,相當(dāng)于沒(méi)有實(shí)現(xiàn)!
    if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
        return self.taskWillPerformHTTPRedirection != nil;
    } else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
        return self.dataTaskDidReceiveResponse != nil;
    } else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
        return self.dataTaskWillCacheResponse != nil;
    } else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
        return self.didFinishEventsForBackgroundURLSession != nil;
    }
    return [[self class] instancesRespondToSelector:selector];
}

這樣如果沒(méi)實(shí)現(xiàn)這些我們自定義的Block也不會(huì)去回調(diào)這些代理孕讳。因?yàn)楸旧砟承┐斫吵粓?zhí)行了這些自定義的Block,如果Block都沒(méi)有賦值厂财,那我們調(diào)用代理也沒(méi)有任何意義芋簿。
講到這,我們順便看看AFUrlSessionManager的一些自定義Block:

@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;

各自對(duì)應(yīng)的還有一堆這樣的set方法:

 - (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
    self.sessionDidBecomeInvalid = block;
}

方法都是一樣的璃饱,就不重復(fù)粘貼占篇幅了与斤。
主要談?wù)勥@個(gè)設(shè)計(jì)思路

  • 作者用@property把這個(gè)些Block屬性在.m文件中聲明,然后復(fù)寫了set方法。
  • 然后在.h中去聲明這些set方法:
   - (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;

為什么要繞這么一大圈呢荚恶?原來(lái)這是為了我們這些用戶使用起來(lái)方便撩穿,調(diào)用set方法去設(shè)置這些Block,能很清晰的看到Block的各個(gè)參數(shù)與返回值谒撼。大神的精髓的編程思想無(wú)處不體現(xiàn)...

接下來(lái)我們就講講這些代理方法做了什么(按照順序來(lái)):

NSURLSessionDelegate
代理1:
//當(dāng)前這個(gè)session已經(jīng)失效時(shí)食寡,該代理方法被調(diào)用。
/*
 如果你使用finishTasksAndInvalidate函數(shù)使該session失效嗤栓,
 那么session首先會(huì)先完成最后一個(gè)task冻河,然后再調(diào)用URLSession:didBecomeInvalidWithError:代理方法,
 如果你調(diào)用invalidateAndCancel方法來(lái)使session失效茉帅,那么該session會(huì)立即調(diào)用上面的代理方法。
 */
- (void)URLSession:(NSURLSession *)session
didBecomeInvalidWithError:(NSError *)error
{
    if (self.sessionDidBecomeInvalid) {
        self.sessionDidBecomeInvalid(session, error);
    }

    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

  • 方法調(diào)用時(shí)機(jī)注釋寫的很清楚锭弊,就調(diào)用了一下我們自定義的Block,還發(fā)了一個(gè)失效的通知堪澎,至于這個(gè)通知有什么用。很抱歉味滞,AF沒(méi)用它做任何事樱蛤,只是發(fā)了...目的是用戶自己可以利用這個(gè)通知做什么事吧。
  • 其實(shí)AF大部分通知都是如此剑鞍。當(dāng)然昨凡,還有一部分通知AF還是有自己用到的,包括配合對(duì)UIKit的一些擴(kuò)展來(lái)使用蚁署,后面我們會(huì)有單獨(dú)篇幅展開(kāi)講講這些UIKit的擴(kuò)展類的實(shí)現(xiàn)便脊。
代理2:
//2、https認(rèn)證
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //挑戰(zhàn)處理類型為 默認(rèn)
    /*
     NSURLSessionAuthChallengePerformDefaultHandling:默認(rèn)方式處理
     NSURLSessionAuthChallengeUseCredential:使用指定的證書
     NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰(zhàn)
     */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    // sessionDidReceiveAuthenticationChallenge是自定義方法光戈,用來(lái)如何應(yīng)對(duì)服務(wù)器端的認(rèn)證挑戰(zhàn)

    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        // 此處服務(wù)器要求客戶端的接收認(rèn)證挑戰(zhàn)方法是NSURLAuthenticationMethodServerTrust
        // 也就是說(shuō)服務(wù)器端需要客戶端返回一個(gè)根據(jù)認(rèn)證挑戰(zhàn)的保護(hù)空間提供的信任(即challenge.protectionSpace.serverTrust)產(chǎn)生的挑戰(zhàn)證書哪痰。

        // 而這個(gè)證書就需要使用credentialForTrust:來(lái)創(chuàng)建一個(gè)NSURLCredential對(duì)象
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

            // 基于客戶端的安全策略來(lái)決定是否信任該服務(wù)器遂赠,不信任的話,也就沒(méi)必要響應(yīng)挑戰(zhàn)
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {

                // 創(chuàng)建挑戰(zhàn)證書(注:挑戰(zhàn)方式為UseCredential和PerformDefaultHandling都需要新建挑戰(zhàn)證書)
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                // 確定挑戰(zhàn)的方式
                if (credential) {
                    //證書挑戰(zhàn)
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    //默認(rèn)挑戰(zhàn)  唯一區(qū)別晌杰,下面少了這一步跷睦!
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                //取消挑戰(zhàn)
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            //默認(rèn)挑戰(zhàn)方式
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }
    //完成挑戰(zhàn)
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

  • 函數(shù)作用:
    web服務(wù)器接收到客戶端請(qǐng)求時(shí),有時(shí)候需要先驗(yàn)證客戶端是否為正常用戶肋演,再?zèng)Q定是夠返回真實(shí)數(shù)據(jù)抑诸。這種情況稱之為服務(wù)端要求客戶端接收挑戰(zhàn)(NSURLAuthenticationChallenge *challenge)。接收到挑戰(zhàn)后爹殊,客戶端要根據(jù)服務(wù)端傳來(lái)的challenge來(lái)生成completionHandler所需的NSURLSessionAuthChallengeDisposition disposition和NSURLCredential *credential(disposition指定應(yīng)對(duì)這個(gè)挑戰(zhàn)的方法哼鬓,而credential是客戶端生成的挑戰(zhàn)證書,注意只有challenge中認(rèn)證方法為NSURLAuthenticationMethodServerTrust的時(shí)候边灭,才需要生成挑戰(zhàn)證書)异希。最后調(diào)用completionHandler回應(yīng)服務(wù)器端的挑戰(zhàn)。
  • 函數(shù)討論:
    該代理方法會(huì)在下面兩種情況調(diào)用:
  1. 當(dāng)服務(wù)器端要求客戶端提供證書時(shí)或者進(jìn)行NTLM認(rèn)證(Windows NT LAN Manager绒瘦,微軟提出的WindowsNT挑戰(zhàn)/響應(yīng)驗(yàn)證機(jī)制)時(shí)称簿,此方法允許你的app提供正確的挑戰(zhàn)證書。
  2. 當(dāng)某個(gè)session使用SSL/TLS協(xié)議惰帽,第一次和服務(wù)器端建立連接的時(shí)候憨降,服務(wù)器會(huì)發(fā)送給iOS客戶端一個(gè)證書,此方法允許你的app驗(yàn)證服務(wù)期端的證書鏈(certificate keychain)
    注:如果你沒(méi)有實(shí)現(xiàn)該方法该酗,該session會(huì)調(diào)用其NSURLSessionTaskDelegate的代理方法URLSession:task:didReceiveChallenge:completionHandler: 授药。

這里,我把官方文檔對(duì)這個(gè)方法的描述翻譯了一下呜魄。
總結(jié)一下悔叽,這個(gè)方法其實(shí)就是做https認(rèn)證的【粜幔看看上面的注釋娇澎,大概能看明白這個(gè)方法做認(rèn)證的步驟,我們還是如果有自定義的做認(rèn)證的Block睹晒,則調(diào)用我們自定義的趟庄,否則去執(zhí)行默認(rèn)的認(rèn)證步驟,最后調(diào)用完成認(rèn)證:

//完成挑戰(zhàn) 
if (completionHandler) { 
      completionHandler(disposition, credential); 
}

代理3:
//3伪很、 當(dāng)session中所有已經(jīng)入隊(duì)的消息被發(fā)送出去后戚啥,會(huì)調(diào)用該代理方法。
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (self.didFinishEventsForBackgroundURLSession) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.didFinishEventsForBackgroundURLSession(session);
        });
    }
}

官方文檔翻譯:

函數(shù)討論:

  • 在iOS中锉试,當(dāng)一個(gè)后臺(tái)傳輸任務(wù)完成或者后臺(tái)傳輸時(shí)需要證書猫十,而此時(shí)你的app正在后臺(tái)掛起,那么你的app在后臺(tái)會(huì)自動(dòng)重新啟動(dòng)運(yùn)行,并且這個(gè)app的UIApplicationDelegate會(huì)發(fā)送一個(gè)application:handleEventsForBackgroundURLSession:completionHandler:消息炫彩。該消息包含了對(duì)應(yīng)后臺(tái)的session的identifier匾七,而且這個(gè)消息會(huì)導(dǎo)致你的app啟動(dòng)。你的app隨后應(yīng)該先存儲(chǔ)completion handler江兢,然后再使用相同的identifier創(chuàng)建一個(gè)background configuration昨忆,并根據(jù)這個(gè)background configuration創(chuàng)建一個(gè)新的session。這個(gè)新創(chuàng)建的session會(huì)自動(dòng)與后臺(tái)任務(wù)重新關(guān)聯(lián)在一起杉允。
  • 當(dāng)你的app獲取了一個(gè)URLSessionDidFinishEventsForBackgroundURLSession:消息邑贴,這就意味著之前這個(gè)session中已經(jīng)入隊(duì)的所有消息都轉(zhuǎn)發(fā)出去了,這時(shí)候再調(diào)用先前存取的completion handler是安全的叔磷,或者因?yàn)閮?nèi)部更新而導(dǎo)致調(diào)用completion handler也是安全的拢驾。
NSURLSessionTaskDelegate
代理4:
//被服務(wù)器重定向的時(shí)候調(diào)用
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest *))completionHandler
{
    NSURLRequest *redirectRequest = request;

    // step1\. 看是否有對(duì)應(yīng)的user block 有的話轉(zhuǎn)發(fā)出去,通過(guò)這4個(gè)參數(shù)改基,返回一個(gè)NSURLRequest類型參數(shù)繁疤,request轉(zhuǎn)發(fā)、網(wǎng)絡(luò)重定向.
    if (self.taskWillPerformHTTPRedirection) {
        //用自己自定義的一個(gè)重定向的block實(shí)現(xiàn)秕狰,返回一個(gè)新的request稠腊。
        redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
    }

    if (completionHandler) {
        // step2\. 用request重新請(qǐng)求
        completionHandler(redirectRequest);
    }
}

  • 一開(kāi)始我以為這個(gè)方法是類似NSURLProtocol,可以在請(qǐng)求時(shí)自己主動(dòng)的去重定向request鸣哀,后來(lái)發(fā)現(xiàn)不是架忌,這個(gè)方法是在服務(wù)器去重定向的時(shí)候,才會(huì)被調(diào)用我衬。為此我寫了段簡(jiǎn)單的PHP測(cè)了測(cè):
<?php
defined('BASEPATH') OR exit('No direct script access allowed');

class Welcome extends CI_Controller {
    public function index()
    {
        header("location: http://www.huixionghome.cn/");
    }
}

證實(shí)確實(shí)如此叹放,當(dāng)我們服務(wù)器重定向的時(shí)候,代理就被調(diào)用了挠羔,我們可以去重新定義這個(gè)重定向的request井仰。

  • 關(guān)于這個(gè)代理還有一些需要注意的地方:

此方法只會(huì)在default session或者ephemeral session中調(diào)用,而在background session中褥赊,session task會(huì)自動(dòng)重定向糕档。

這里指的模式是我們一開(kāi)始Init的模式:

if (!configuration) {
    configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;

這個(gè)模式總共分為3種:

對(duì)于NSURLSession對(duì)象的初始化需要使用NSURLSessionConfiguration,而NSURLSessionConfiguration有三個(gè)類工廠方法:
+defaultSessionConfiguration 返回一個(gè)標(biāo)準(zhǔn)的 configuration拌喉,這個(gè)配置實(shí)際上與 NSURLConnection 的網(wǎng)絡(luò)堆棧(networking stack)是一樣的,具有相同的共享 NSHTTPCookieStorage俐银,共享 NSURLCache 和共享NSURLCredentialStorage尿背。
+ephemeralSessionConfiguration 返回一個(gè)預(yù)設(shè)配置,這個(gè)配置中不會(huì)對(duì)緩存捶惜,Cookie 和證書進(jìn)行持久性的存儲(chǔ)田藐。這對(duì)于實(shí)現(xiàn)像秘密瀏覽這種功能來(lái)說(shuō)是很理想的。
+backgroundSessionConfiguration:(NSString *)identifier 的獨(dú)特之處在于,它會(huì)創(chuàng)建一個(gè)后臺(tái) session汽久。后臺(tái) session 不同于常規(guī)的鹤竭,普通的 session,它甚至可以在應(yīng)用程序掛起景醇,退出或者崩潰的情況下運(yùn)行上傳和下載任務(wù)臀稚。初始化時(shí)指定的標(biāo)識(shí)符,被用于向任何可能在進(jìn)程外恢復(fù)后臺(tái)傳輸?shù)氖刈o(hù)進(jìn)程(daemon)提供上下文三痰。

代理5:
//https認(rèn)證
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.taskDidReceiveAuthenticationChallenge) {
        disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                disposition = NSURLSessionAuthChallengeUseCredential;
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

  • 鑒于篇幅吧寺,就不去貼官方文檔的翻譯了,大概總結(jié)一下:
    之前我們也有一個(gè)https認(rèn)證散劫,功能一樣稚机,執(zhí)行的內(nèi)容也完全一樣。
  • 區(qū)別在于這個(gè)是non-session-level級(jí)別的認(rèn)證获搏,而之前的是session-level級(jí)別的赖条。
  • 相對(duì)于它,多了一個(gè)參數(shù)task,然后調(diào)用我們自定義的Block會(huì)多回傳這個(gè)task作為參數(shù)常熙,這樣我們就可以根據(jù)每個(gè)task去自定義我們需要的https認(rèn)證方式纬乍。
代理6:
//當(dāng)一個(gè)session task需要發(fā)送一個(gè)新的request body stream到服務(wù)器端的時(shí)候,調(diào)用該代理方法症概。

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{

    NSInputStream *inputStream = nil;

    //有自定義的taskNeedNewBodyStream,用自定義的蕾额,不然用task里原始的stream
    if (self.taskNeedNewBodyStream) {
        inputStream = self.taskNeedNewBodyStream(session, task);
    } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
        inputStream = [task.originalRequest.HTTPBodyStream copy];
    }

    if (completionHandler) {
        completionHandler(inputStream);
    }
}

  • 該代理方法會(huì)在下面兩種情況被調(diào)用:
    1. 如果task是由uploadTaskWithStreamedRequest:創(chuàng)建的,那么提供初始的request body stream時(shí)候會(huì)調(diào)用該代理方法彼城。
    2. 因?yàn)檎J(rèn)證挑戰(zhàn)或者其他可恢復(fù)的服務(wù)器錯(cuò)誤诅蝶,而導(dǎo)致需要客戶端重新發(fā)送一個(gè)含有body stream的request,這時(shí)候會(huì)調(diào)用該代理募壕。
代理7:
/*
 //周期性地通知代理發(fā)送到服務(wù)器端數(shù)據(jù)的進(jìn)度调炬。
 */

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
     // 如果totalUnitCount獲取失敗,就使用HTTP header中的Content-Length作為totalUnitCount

    int64_t totalUnitCount = totalBytesExpectedToSend;
    if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
        NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
        if(contentLength) {
            totalUnitCount = (int64_t) [contentLength longLongValue];
        }
    }

    if (self.taskDidSendBodyData) {
        self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
    }
}

  • 就是每次發(fā)送數(shù)據(jù)給服務(wù)器舱馅,會(huì)回調(diào)這個(gè)方法缰泡,通知已經(jīng)發(fā)送了多少,總共要發(fā)送多少代嗤。
  • 代理方法里也就是僅僅調(diào)用了我們自定義的Block而已棘钞。
未完總結(jié):
  • 其實(shí)寫了這么多,還沒(méi)有講到真正重要的地方干毅,但是因?yàn)橐呀?jīng)接近簡(jiǎn)書最大篇幅宜猜,所以只能先在這里結(jié)個(gè)尾了。
  • 如果能看到這里硝逢,說(shuō)明你是個(gè)非常有耐心姨拥,非常好學(xué)绅喉,非常nice的iOS開(kāi)發(fā)。樓主為你點(diǎn)個(gè)贊叫乌。那么相信你也不吝嗇手指動(dòng)一動(dòng)柴罐,給本文點(diǎn)個(gè)喜歡...順便關(guān)注一下樓主...畢竟寫了這么多...也很辛苦...咳咳,我不小心說(shuō)出心聲了么憨奸?
  • 最后革屠,萬(wàn)一如果本文有人轉(zhuǎn)載,麻煩注明出處~謝謝膀藐!

參考:
iOS--AFN實(shí)現(xiàn)原理
AFNetworking到底做了什么屠阻?(終)

AFNetworking到底做了什么(二)?
AFNetworking之于https認(rèn)證
AFNetworking之UIKit擴(kuò)展與緩存實(shí)現(xiàn)
iOS 網(wǎng)絡(luò)框架-AFNetworking 3.1.0 源碼解讀

AFNetworking的基本使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市额各,隨后出現(xiàn)的幾起案子国觉,更是在濱河造成了極大的恐慌,老刑警劉巖虾啦,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件麻诀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡傲醉,警方通過(guò)查閱死者的電腦和手機(jī)蝇闭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)硬毕,“玉大人呻引,你說(shuō)我怎么就攤上這事⊥驴龋” “怎么了逻悠?”我有些...
    開(kāi)封第一講書人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)韭脊。 經(jīng)常有香客問(wèn)我童谒,道長(zhǎng),這世上最難降的妖魔是什么沪羔? 我笑而不...
    開(kāi)封第一講書人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任饥伊,我火速辦了婚禮,結(jié)果婚禮上蔫饰,老公的妹妹穿的比我還像新娘琅豆。我一直安慰自己,他們只是感情好篓吁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布趋距。 她就那樣靜靜地躺著,像睡著了一般越除。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,144評(píng)論 1 285
  • 那天摘盆,我揣著相機(jī)與錄音翼雀,去河邊找鬼。 笑死孩擂,一個(gè)胖子當(dāng)著我的面吹牛狼渊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播类垦,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼狈邑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蚤认?” 一聲冷哼從身側(cè)響起米苹,我...
    開(kāi)封第一講書人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎砰琢,沒(méi)想到半個(gè)月后蘸嘶,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡陪汽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年训唱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挚冤。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡况增,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出训挡,到底是詐尸還是另有隱情澳骤,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布舍哄,位于F島的核電站宴凉,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏表悬。R本人自食惡果不足惜弥锄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蟆沫。 院中可真熱鬧籽暇,春花似錦、人聲如沸饭庞。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)舟山。三九已至绸狐,卻和暖如春卤恳,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背寒矿。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工突琳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人符相。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓拆融,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親啊终。 傳聞我的和親對(duì)象是個(gè)殘疾皇子镜豹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容