AFNetworking 3.0 源碼解讀 總結(干貨)(下)


承接上一篇AFNetworking 3.0 源碼解讀 總結(干貨)(上)

21.網絡服務類型NSURLRequestNetworkServiceType

示例代碼:

typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType)
{
    NSURLNetworkServiceTypeDefault = 0,    // Standard internet traffic
    NSURLNetworkServiceTypeVoIP = 1,    // Voice over IP control traffic
    NSURLNetworkServiceTypeVideo = 2,    // Video traffic
    NSURLNetworkServiceTypeBackground = 3, // Background traffic
    NSURLNetworkServiceTypeVoice = 4       // Voice data
};

可以通過這個值來指定當前的網絡類型,系統會跟據制定的網絡類型對很多方面進行優化,這個就設計到很細微的編程技巧了,可作為一個優化的點備用。

22.Authorization字段

在請求頭中可以添加Authorization字段。

Authorization: Basic YWRtaW46YWRtaW4= 其中Basic表示基礎認證,當然還有其他認證,如果感興趣,可以看看本文開始提出的那本書。后邊的YWRtaW46YWRtaW4= 是根據username:password 拼接后然后在經過Base64編碼后的結果。

如果header中有 Authorization這個字段,那么服務器會驗證用戶名和密碼,如果不正確的話會返回401錯誤。

23.使用流寫數據

下邊的代碼可以說是使用流寫數據的經典案例。可直接拿來使用。

示例代碼:

- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(void (^)(NSError *error))handler
{
    NSParameterAssert(request.HTTPBodyStream);
    NSParameterAssert([fileURL isFileURL]);

    // 加上上邊的兩個判斷,下邊的這些代碼就是把文件寫到另一個地方的典型使用方法了
    NSInputStream *inputStream = request.HTTPBodyStream;
    NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
    __block NSError *error = nil;

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

        [inputStream open];
        [outputStream open];

        // 讀取數據
        while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
            uint8_t buffer[1024];

            NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
            if (inputStream.streamError || bytesRead < 0) {
                error = inputStream.streamError;
                break;
            }

            NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
            if (outputStream.streamError || bytesWritten < 0) {
                error = outputStream.streamError;
                break;
            }

            if (bytesRead == 0 && bytesWritten == 0) {
                break;
            }
        }

        [outputStream close];
        [inputStream close];

        if (handler) {
            dispatch_async(dispatch_get_main_queue(), ^{
                handler(error);
            });
        }
    });

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    mutableRequest.HTTPBodyStream = nil;

    return mutableRequest;
}

24.NSIndexSet

定義:NSIndexSet是一個有序的,唯一的,無符號整數的集合。

我們先看個例子:

NSMutableIndexSet *indexSetM = [NSMutableIndexSet indexSet];
    [indexSetM addIndex:19];
    [indexSetM addIndex:4];
    [indexSetM addIndex:6];
    [indexSetM addIndex:8];
    [indexSetM addIndexesInRange:NSMakeRange(20, 10)];
    
    [indexSetM enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) {
        NSLog(@"%lu",idx);
    }];

打印結果如下:

2016-08-10 11:39:00.826 xxxx[3765:100078] 4
2016-08-10 11:39:00.827 xxxx[3765:100078] 6
2016-08-10 11:39:00.827 xxxx[3765:100078] 8
2016-08-10 11:39:00.827 xxxx[3765:100078] 19
2016-08-10 11:39:00.827 xxxx[3765:100078] 20
2016-08-10 11:39:00.828 xxxx[3765:100078] 21
2016-08-10 11:39:00.828 xxxx[3765:100078] 22
2016-08-10 11:39:00.828 xxxx[3765:100078] 23
2016-08-10 11:39:00.828 xxxx[3765:100078] 24
2016-08-10 11:39:00.828 xxxx[3765:100078] 25
2016-08-10 11:39:00.828 xxxx[3765:100078] 26
2016-08-10 11:39:00.828 xxxx[3765:100078] 27
2016-08-10 11:39:00.828 xxxx[3765:100078] 28
2016-08-10 11:39:00.829 xxxx[3765:100078] 29

這充分說明了一下幾點

  • 它是一個無符號整數的集合
  • 用addIndex方法可以添加單個整數值,使用addIndexesInRange可以添加一個范圍,比如NSMakeRange(20, 10) 取20包括20后邊十個整數
  • 唯一性,集合內的整數不會重復出現
  • 具體用法就不再次做詳細的解釋了,有興趣的朋友可以看看這篇文章: NSIndexSet 用法

25.NSUnderlyingErrorKey優先錯誤

當出現可能會有兩個錯誤的情況下,可以考慮使用NSUnderlyingErrorKey處理這種情況。

示例代碼:

static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
    if (!error) {
        return underlyingError;
    }

    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }

    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

26.NSJSONReadingOptions

這個選項可以設置json的讀取選項,我們點進去可以看到:

typedef NS_OPTIONS(NSUInteger, NSJSONReadingOptions) {
    NSJSONReadingMutableContainers = (1UL << 0),
    NSJSONReadingMutableLeaves = (1UL << 1),
    NSJSONReadingAllowFragments = (1UL << 2)
} NS_ENUM_AVAILABLE(10_7, 5_0);
  • NSJSONReadingMutableContainers 這個解析json成功后返回一個容器
  • NSJSONReadingMutableLeaves 返回中的json對象中字符串為NSMutableString
  • NSJSONReadingAllowFragments 允許JSON字符串最外層既不是NSArray也不是NSDictionary,但必須是有效的JSON Fragment。例如使用這個選項可以解析 @“123” 這樣的字符串

我們舉個例子說明一下NSJSONReadingMutableContainers:

NSString *str = @"{\"name\":\"zhangsan\"}";

NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
// 應用崩潰,不選用NSJSONReadingOptions,則返回的對象是不可變的,NSDictionary
[dict setObject:@"male" forKey:@"sex"];

NSMutableDictionary *dict = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:nil];
// 沒問題,使用NSJSONReadingMutableContainers,則返回的對象是可變的,NSMutableDictionary
[dict setObject:@"male" forKey:@"sex"];

NSLog(@"%@", dict);

如果服務器返回的json的最外層並不是以NSArray 或者 NSDictionary ,而是一個有效的json fragment ,比如 就返回了一個@"abc"。 那么我們使用NSJSONReadingAllowFragments這個選項也能夠解析出來。

27.圖片解壓雜談

AFJSONResponseSerializer使用系統內置的NSJSONSerialization解析json,NSJSON只支持解析UTF8編碼的數據(還有UTF-16LE之類的,都不常用),所以要先把返回的數據轉成UTF8格式。這里會嘗試用HTTP返回的編碼類型和自己設置的stringEncoding去把數據解碼轉成字符串NSString,再把NSString用UTF8編碼轉成NSData,再用NSJSONSerialization解析成對象返回。

上述過程是NSData->NSString->NSData->NSObject,這里有個問題,如果你能確定服務端返回的是UTF8編碼的json數據,那NSData->NSString->NSData這兩步就是無意義的,而且這兩步進行了兩次編解碼,很浪費性能,所以如果確定服務端返回utf8編碼數據,就建議自己再寫個JSONResponseSerializer,跳過這兩個步驟。

此外AFJSONResponseSerializer專門寫了個方法去除NSNull,直接把對象里值是NSNull的鍵去掉,還蠻貼心,若不去掉,上層很容易忽略了這個數據類型,判斷了數據是否nil沒判斷是否NSNull,進行了錯誤的調用導致core。

圖片解壓

當我們調用UIImage的方法imageWithData:方法把數據轉成UIImage對象后,其實這時UIImage對象還沒准備好需要渲染到屏幕的數據,現在的網絡圖像PNG和JPG都是壓縮格式,需要把它們解壓轉成bitmap后才能渲染到屏幕上,如果不做任何處理,當你把UIImage賦給UIImageView,在渲染之前底層會判斷到UIImage對象未解壓,沒有bitmap數據,這時會在主線程對圖片進行解壓操作,再渲染到屏幕上。這個解壓操作是比較耗時的,如果任由它在主線程做,可能會導致速度慢UI卡頓的問題。

AFImageResponseSerializer除了把返回數據解析成UIImage外,還會把圖像數據解壓,這個處理是在子線程(AFNetworking專用的一條線程,詳見AFURLConnectionOperation),處理后上層使用返回的UIImage在主線程渲染時就不需要做解壓這步操作,主線程減輕了負擔,減少了UI卡頓問題。

具體實現上在AFInflatedImageFromResponseWithDataAtScale里,創建一個畫布,把UIImage畫在畫布上,再把這個畫布保存成UIImage返回給上層。只有JPG和PNG才會嘗試去做解壓操作,期間如果解壓失敗,或者遇到CMKY顏色格式的jpg,或者圖像太大(解壓后的bitmap太占內存,一個像素3-4字節,搞不好內存就爆掉了),就直接返回未解壓的圖像。

另外在代碼里看到iOS才需要這樣手動解壓,MacOS上已經有封裝好的對象NSBitmapImageRep可以做這個事。

關於圖片解壓,還有幾個問題不清楚:

1.本來以為調用imageWithData方法只是持有了數據,沒有做解壓相關的事,后來看到調用堆棧發現已經做了一些解壓操作,從調用名字看進行了huffman解碼,不知還會繼續做到解碼jpg的哪一步。
UIImage_jpg

2.以上圖片手動解壓方式都是在CPU進行的,如果不進行手動解壓,把圖片放進layer里,讓底層自動做這個事,是會用GPU進行的解壓的。不知用GPU解壓與用CPU解壓速度會差多少,如果GPU速度很快,就算是在主線程做解壓,也變得可以接受了,就不需要手動解壓這樣的優化了,不過目前沒找到方法檢測GPU解壓的速度。

28.獲取系統版本

#ifndef NSFoundationVersionNumber_iOS_8_0
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug 1140.11
#else
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
#endif

上邊的這個宏的目的是通過NSFoundation的版本來判斷當前ios版本,關鍵是這個宏的調試目標是IOS,來看看系統是怎么定義的:

那么我們就能夠聯想到,目前我們能夠判斷系統版本號的方法有幾種呢?最少三種:

  • [UIDevice currentDevice].systemVersion
  • 通過比較Foundation框架的版本號,iOS系統升級的同時Foundation框架的版本也會提高
  • 通過在某系版本中新出現的方法來判斷,UIAlertController 這個類是iOS8之后才出現的 NS_CLASS_AVAILABLE_IOS(8_0),如果當前系統版本沒有這個類NSClassFromString(@"UIAlertController" == (null),從而判斷當前版本是否大於等於iOS8

這篇博文寫的很詳細,關於獲取當前版本

29.dispatch_queue_create()

AFNetworking中所有的和創建任務相關的事件都放到了一個單例的隊列中,我們平時可能會使用這些方法,但還是可能會忽略一些內容,dispatch_queue_create()這個是隊列的方法,第一個參數是隊列的identifier,第二個參數則表示這個隊列是串行隊列還是並行隊列。

如果第二個參數為DISPATCH_QUEUE_SERIAL或NULL 則表示隊列為串行隊列。如果為DISPATCH_QUEUE_CONCURRENT則表示是並行隊列。
關於隊列的小的知識點,參考了這篇文章 Objective C 高級進階— GCD隊列淺析(一).

30.dispatch_block_t

這個方法還有一個小知識點:dispatch_block_t ,點擊去可以看到:

typedef void (^dispatch_block_t)(void);

關於這個Block我們應該注意幾點:

  • 非ARC情況下,Block被allocated或者copied到堆后,一定要記得釋放它,通過[release]或者Block_release()
  • 非ARC情況下,Block被allocated或者copied到堆后,一定要記得釋放它,通過[release]或者Block_release()

31.NSKeyValueObservingOptions

  • NSKeyValueObservingOptionNew 把更改之前的值提供給處理方法
  • NSKeyValueObservingOptionOld 把更改之后的值提供給處理方法
  • NSKeyValueObservingOptionInitial 把初始化的值提供給處理方法,一旦注冊,立馬就會調用一次。通常它會帶有新值,而不會帶有舊值
  • NSKeyValueObservingOptionPrior 分2次調用。在值改變之前和值改變之后

32.task delegate出發問題

task一共有4個delegate,只要設置了一個,就代表四個全部設置,有時候一些delegate不會被觸發的原因在於這四種delegate是針對不同的URLSession類型和URLSessionTask類型來進行響應的,也就是說不同的類型只會觸發這些delegate中的一部分,而不是觸發所有的delegate。

舉例說明如下:

  1. 觸發NSURLSessionDataDelegate

      //使用函數dataTask來接收數據
     -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
     
     //則NSURLSession部分的代碼如下  
     NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration];
     NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
     NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"];  
     NSURLSessionDataTask* dataTask=[session dataTaskWithURL:url];
     [dataTask resume];
    
  2. 觸發NSURLSessionDownloadDelegate

     //使用函數downloadTask來接受數據
     -(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
     
     //則NSURLSession部分的代碼如下
     NSURLSessionConfiguration* ephConfiguration=[NSURLSessionConfiguration defaultSessionConfiguration];
     NSURLSession* session=[NSURLSession sessionWithConfiguration:ephConfiguration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
     NSURL* url=[NSURL URLWithString:@"http://www.example.com/external_links/01.png"];  
     NSURLSessionDownloadTask* dataTask=[session downloadTaskWithURL:url];
     [dataTask resume];
    

這兩段代碼的主要區別在於NSURLSessionTask的類型的不同,造成了不同的Delegate被觸發.

33.@unionOfArrays

 tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];

這么使用之前確實不太知道,如果是我,可能就直接賦值給數組了。那么@unionOfArrays.self又是什么意思呢?

  • @distinctUnionOfObjects 清楚重復值
  • unionOfObjects 保留重復值

34.相對路徑(relative)

假如有一個基礎路徑NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];我們暫時命名為baseURL.所謂 相對 肯定跟這個baseURL有關系

我們可以通過 NSURL +URLWithString:relativeToUL:這個方法來獲取一個路徑,至於怎么使用,我們通過一個例子來說明:

NSURL *baseURL = [NSURL URLWithString:@"http://example.com/v1/"];
[NSURL URLWithString:@"foo" relativeToURL:baseURL];                  // http://example.com/v1/foo
[NSURL URLWithString:@"foo?bar=baz" relativeToURL:baseURL];          // http://example.com/v1/foo?bar=baz
[NSURL URLWithString:@"/foo" relativeToURL:baseURL];                 // http://example.com/foo
[NSURL URLWithString:@"foo/" relativeToURL:baseURL];                 // http://example.com/v1/foo
[NSURL URLWithString:@"/foo/" relativeToURL:baseURL];                // http://example.com/foo/
[NSURL URLWithString:@"http://example2.com/" relativeToURL:baseURL]; // http://example2.com/

至於 絕對路徑相對路徑 的定義,使用方法,優缺點,這里就不提了,大家可以自行了解。說一下為什么使用相對路徑吧。

在真實開發中,一般都會有一個線上的服務器和一下測試服務器,當然也可能多個。在ios開發中切換開發環境有好幾種方法

  • 通過target
  • 自定義一個字段HTTPURL,用它來控制路徑
  • 通過相對路徑來切換接口

35.dispatch_barrier_async

barrier 這個單詞的意思是障礙,攔截的意思,也即是說 dispatch_barrier_async 一定是有攔截事件的作用。

看下邊這段代碼:

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-1");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-2");
    });
    dispatch_barrier_async(concurrentQueue, ^(){
        NSLog(@"dispatch-barrier");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-3");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-4");
    });

打印結果:

2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-1
2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2
2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier
2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3
2016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4

這個說明了 dispatch_barrier_async 能夠攔截它前邊的異步事件,等待兩個異步方法都完成之后,調用 dispatch_barrier_async

我們稍微改動一下:

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^(){
    NSLog(@"dispatch-1");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"dispatch-2");
});
dispatch_barrier_sync(concurrentQueue, ^(){
    NSLog(@"dispatch-barrier");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"dispatch-3");
});
dispatch_async(concurrentQueue, ^(){
    NSLog(@"dispatch-4");
});

打印結果:

2016-08-22 16:43:20.554 xxx[26805:271426] dispatch-1
2016-08-22 16:43:20.555 xxx[26805:271422] dispatch-2
2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-barrier
2016-08-22 16:43:20.556 xxx[26805:271422] dispatch-3
2016-08-22 16:43:20.556 xxx[26805:271426] dispatch-4

36.dispatch_sync() 和 dispatch_async()

大概說下 dispatch_sync() 和 dispatch_async() 這兩個方法。

示例代碼:

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_sync(concurrentQueue, ^(){
        NSLog(@"2");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"3");
    });
    NSLog(@"4");

輸出為:

2016-08-25 11:50:51.601 xxxx[1353:102804] 1
2016-08-25 11:50:51.601 xxxx[1353:102804] 2
2016-08-25 11:50:56.603 xxxx[1353:102804] 3
2016-08-25 11:50:56.603 xxxx[1353:102804] 4

再看:

dispatch_queue_t concurrentQueue = dispatch_queue_create("my.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"1");
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"2");
        [NSThread sleepForTimeInterval:5];
        NSLog(@"3");
    });
    NSLog(@"4");

輸出為:

2016-08-25 11:52:29.022 xxxx[1392:104246] 1
2016-08-25 11:52:29.023 xxxx[1392:104246] 4
2016-08-25 11:52:29.023 xxxx[1392:104284] 2
2016-08-25 11:52:34.029 xxxx[1392:104284] 3

通過上邊的兩個例子,我們可以總結出:

  • dispatch_sync(),同步添加操作。他是等待添加進隊列里面的操作完成之后再繼續執行
  • dispatch_async ,異步添加進任務隊列,它不會做任何等待

37.NSURLCache

我們簡單介紹下NSURLCache。NSURLCache 為您的應用的 URL 請求提供了內存中以及磁盤上的綜合緩存機制。網絡緩存減少了需要向服務器發送請求的次數,同時也提升了離線或在低速網絡中使用應用的體驗。當一個請求完成下載來自服務器的回應,一個緩存的回應將在本地保存。下一次同一個請求再發起時,本地保存的回應就會馬上返回,不需要連接服務器。NSURLCache 會 自動 且 透明 地返回回應。

為了好好利用 NSURLCache,你需要初始化並設置一個共享的 URL 緩存。在 iOS 中這項工作需要在 -application:didFinishLaunchingWithOptions: 完成,而 OS X 中是在 –applicationDidFinishLaunching::

示例代碼:

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
                                                       diskCapacity:20 * 1024 * 1024
                                                           diskPath:nil];
  [NSURLCache setSharedURLCache:URLCache];
}

NSURLRequest 有個 cachePolicy 屬性,我們平時最常用的有四個屬性:

  • NSURLRequestUseProtocolCachePolicy: 對特定的 URL 請求使用網絡協議中實現的緩存邏輯。這是默認的策略
  • NSURLRequestReloadIgnoringLocalCacheData:數據需要從原始地址加載。不使用現有緩存。
  • NSURLRequestReturnCacheDataElseLoad:無論緩存是否過期,先使用本地緩存數據。如果緩存中沒有請求所對應的數據,那么從原始地址加載數據
  • NSURLRequestReturnCacheDataDontLoad:無論緩存是否過期,先使用本地緩存數據。如果緩存中沒有請求所對應的數據,那么放棄從原始地址加載數據,請求視為失敗(即:“離線”模式)。

38.@synchronized()鎖

synchronized是一種鎖,這種鎖不管是在oc中還是java中用的都挺多的,而且這種鎖鎖得是對象。具體原理,可以看這篇文章后邊的 參考 那一部分。
總結一下,鎖一般用於多線程環境下對數據的操作中。在 AFNetworking 中我們見到了3種不同的鎖,分別是:

  1. NSLock
  2. dispatch_semaphore_wait
  3. @synchronized

39.UIWebView+AFNetworking

UIWebView的這個分類是這幾個分類中最讓我驚訝的一個。讓我真正認識到條條大路通羅馬到底是什么意思。有時候人的思想確實會被固有的思維所束縛。這里只是用了UIWebView的loadData:(NSData )data MIMEType:(NSString )MIMEType textEncodingName:(NSString )textEncodingName baseURL:(NSURL )baseURL方法

你會發現使用這個分類配合UIWebView,所有的事情都變得很簡單。

示例代碼

- (void)loadRequest:(NSURLRequest *)request
           MIMEType:(NSString *)MIMEType
   textEncodingName:(NSString *)textEncodingName
           progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
            success:(NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
            failure:(void (^)(NSError *error))failure
{
    // 檢查參數
    NSParameterAssert(request);

    // 如果正處於運行或者暫停裝狀態,就取消之前的任務task並設置為nil
    if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
        [self.af_URLSessionTask cancel];
    }
    self.af_URLSessionTask = nil;

    __weak __typeof(self)weakSelf = self;
    NSURLSessionDataTask *dataTask;
    dataTask = [self.sessionManager
            GET:request.URL.absoluteString
            parameters:nil
            progress:nil
            success:^(NSURLSessionDataTask * _Nonnull task, id  _Nonnull responseObject) {
                __strong __typeof(weakSelf) strongSelf = weakSelf;
                
                // 請求成功后,調用success block
                if (success) {
                    success((NSHTTPURLResponse *)task.response, responseObject);
                }
                // 顯示數據
                [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[task.currentRequest URL]];

                // 調用webViewDidFinishLoad
                if ([strongSelf.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
                    [strongSelf.delegate webViewDidFinishLoad:strongSelf];
                }
            }
            failure:^(NSURLSessionDataTask * _Nonnull task, NSError * _Nonnull error) {
                if (failure) {
                    failure(error);
                }
            }];
    self.af_URLSessionTask = dataTask;
    
    // 設置progress,這個來自於self.sessionManager
    if (progress != nil) {
        *progress = [self.sessionManager downloadProgressForTask:dataTask];
    }
    
    // 開啟任務
    [self.af_URLSessionTask resume];

    // 調用webViewDidStartLoad方法
    if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
        [self.delegate webViewDidStartLoad:self];
    }
}

總結

AFNetworking 的源碼解讀到此就結束了。我曾經說要寫一個網絡框架,但隨着對網絡的更進一步的了解。一個好的框架不是隨隨便便寫的。需要對使用者負責。因此我又找了幾個目前比較流行的框架,對他們的思想研究研究。實屬拿來主義。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM