iOS網絡請求-AFNetworking源碼解析


趁着端午節日,自己沒有什么過多的安排,准備花4-5天左右,針對網絡請求源碼AFNetworking和YTKNetwork進行解析以及這兩年多iOS實際開發經驗(其實YTKNetwork也是對AFNetworking的深度封裝),結合多個實際項目,分別針對這兩個網絡框架,進行封裝使用(可以直接使用)。本篇主要講解AFNetworking源碼解析,都是自己親自看AFNetworking源碼以及心得體會,大約看下來需要20-30分鍾。歡迎指正!!!

 AFNetworking源碼地址:https://github.com/AFNetworking/AFNetworking

針對AFNetworking封裝:https://www.cnblogs.com/guohai-stronger/p/9193465.html

YTKNetwork的源碼詳解:https://www.cnblogs.com/guohai-stronger/p/9194519.html

 

一.AFNetworking的代碼結構:

新的代碼結構將AFNetworking.h放到了Supporting Files里面。

自從AFNetworking結構更改以后,結構可能不夠清晰,以前的版本是這樣的:

其實沒有多少改變,從這張圖可以看出:除去Support Files,可以看到AF分為如下5個功能模塊:

  • 網絡通信模塊(最核心)(AFURLSessionManager、AFHTTPSessionManager)
  • 網絡狀態監聽模塊(Reachability)
  • 網絡通信安全策略模塊(Security)
  • 網絡通信信息序列化/反序列化模塊(Serialization)
  • 對於iOS UIkit庫的拓展(UIKit)

 二.各類講解

1.網絡通信模塊-AFURLSessionManager與AFHTTPSessionManager

AFHTTPSessionManager是繼承AFURLSessionManager的,相當於對AFURLSessionManager的再次封裝。

(1)AFURLSessionManager

1>通過查看AFURLSessionManager類:

發現AFURLSessionManager遵守NSSecureCoding, NSCopying兩個協議,以及遵守

NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDataDelegate,NSURLSessionDownloadDelegate四個代理。在AFURLSessionManager中實現協議里的方法,用來處理網絡請求中不同的情況:例如:暫停,取消,數據保存,更新數據進度條一樣。

2>下面是查看AFURLSessionManager類,所包含的屬性:

3>下面是AFURLSessionManager的方法

(1)

如果入參configuration為nil,則調用NSURLSessionConfiguration的defaultSessionConfiguration方法,創建一個會話配置,並使用該配置創建一個會話對象,同時還初始化了安全策略、鎖、返回數據解析器(JSON 數據解析器)等屬性。

(2)

此方法是取消會話Session,發現cancelPendingTasks是一個BOOL類型,如果返回NO,意思是允許會話中的任務執行完畢后,再取消會話,但是會話一經取消將無法重啟;反之如果返回YES,那么直接取消會話,其相關聯的任務和回調都將會釋放。

(3)

再看一下實現方法:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                            completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
}

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

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

    return dataTask;
}
 

創建數據服務,這里在使用會話對象 session 和入參 request 創建任務時,如果 NSFoundationVersionNumber 的值小於 NSFoundationVersionNumber_iOS_8_0 那么 dataTask 的創建會放在 af_url_session_manager_creation_queue 串行隊列中同步執行,否則就由當前線程執行。接着,會調用這樣的方法:

- (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] initWithTask:dataTask];

    delegate.manager = self;

    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;

    delegate.downloadProgressBlock = downloadProgressBlock;

}

在這個方法中會創建一個AFURLSessionManagerTaskDelegate對象,設置其相關聯的管理器,任務描述以及回調等蘇醒,還會將當前會話注冊為監聽者,監聽 task 任務發出的 AFNSURLSessionTaskDidResumeNotification 和 AFNSURLSessionTaskDidSuspendNotification 通知。當接收到該通知后,分別執行 taskDidResume: 和 taskDidSuspend: 方法,在這兩個方法中又發出了 AFNetworkingTaskDidResumeNotification 和 AFNetworkingTaskDidSuspendNotification 通知。

(4)

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                            completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
}

創建數據服務,這個方法其實是調用了上一個方法,只是uploadProgress和downloadProgress傳nil而已

(5)

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                         fromFile:(NSURL *)fileURL
                                         progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    __block NSURLSessionUploadTask *uploadTask = nil;
    url_session_manager_create_task_safely(^{
        uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
    });
    if (!uploadTask && self.attemptsToRecreateUploadTasksForBackgroundSessions && self.session.configuration.identifier) {
        for (NSUInteger attempts = 0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) {
            uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
        }
    }

    [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];

    return uploadTask;
}

此方法是創建文件上傳任務,如果果后台會話對象創建文件上傳任務失敗時,會根據條件嘗試重新創建,當然 AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask 為 5 ,所以只能嘗試 5次。如果任務創建成功,則進而為任務創建一個 AFURLSessionManagerTaskDelegate 對象,作為任務的代理。請求報文的請求體數據即為根據參數 fileURL 獲取的文件數據。

(6)

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                         fromData:(NSData *)bodyData
                                         progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    __block NSURLSessionUploadTask *uploadTask = nil;
    url_session_manager_create_task_safely(^{
        uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
    });

    [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];

    return uploadTask;
}

此方法上傳數據與上面上傳文件類似,待上傳的數據直接由參數 bodyData 給出。

(7)

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                             progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                          destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                    completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    __block NSURLSessionDownloadTask *downloadTask = nil;
    url_session_manager_create_task_safely(^{
        downloadTask = [self.session downloadTaskWithRequest:request];
    });

    [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];

    return downloadTask;
}

創建下載任務:

  • request 創建任務時使用的請求報文頭信息
  • downloadProgressBlock 下載進度更新時調用的代碼塊,這個代碼會在會話隊列中調用,所以如果更新視圖,需要自己在任務代碼中指定主隊列
  • destination 任務下載結束后,該參數可以返回指定的文件保存地址,緩存數據被移動到該地址,targetPath 為下載的數據緩存地址
  • completionHandler 下載任務結束后的回調

進一步調用下面方法

- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                          progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                       destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                 completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    if (destination) {
        delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
            return destination(location, task.response);
        };
    }

    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:downloadTask];

    delegate.downloadProgressBlock = downloadProgressBlock;
}

(8)

- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
                                                progress:(NSProgress * __autoreleasing *)progress
                                             destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                       completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    __block NSURLSessionDownloadTask *downloadTask = nil;
    dispatch_sync(url_session_manager_creation_queue(), ^{
        downloadTask = [self.session downloadTaskWithResumeData:resumeData];
    });

    [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler];

    return downloadTask;
}

創建重用數據的下載任務:使用已經下載的部分數據 resumeData 創建一個下載任務,繼續進行下載。

(9)

- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task;

獲取任務的數據上傳進度

(10)

- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;

獲取任務的數據下載進度

 AFURLSessionManager 中實現的代理方法

AFURLSessionManager 遵循 NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate 協議,以處理網絡請求過程中的數據。

有些代理方法中所做的任務,完全由 AFURLSessionManager 的代碼塊屬性決定。如果這些屬性並沒有設置,那么相應的代理方法就沒必要響應。所以 AFURLSessionManager 中重寫了 respondsToSelector: 過濾了一些不必響應的代理方法。

NSURLSessionDownloadDelegate

當下載任務結束后,調用該代理方法。

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    //獲取與任務相對應的代理對象
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];

    //如果設置了回調任務,先執行回調任務
    if (self.downloadTaskDidFinishDownloading) {

        //獲取下載數據要保存的地址        
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;

            //移動下載的數據到指定地址
            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }

            return;
        }
    }

    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

從上面的代碼可知,如果會話管理器的 downloadTaskDidFinishDownloading 的代碼塊返回了地址,那么便不會去執行任務本身所對應的代理方法了,並且如果移動文件失敗便會推送一個 AFURLSessionDownloadTaskDidFailToMoveFileNotification 通知。

下面兩個協議方法中,都是先執行任務所關聯的代理對象的方法,再執行會話對象設置的 downloadTaskDidWriteData 或 downloadTaskDidResume 任務。

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes;

從這些代理方法中可知,設置 AFURLSessionManager 會話實例對象的代碼塊任務屬性,那么這些回調任務對於每一個網絡請求任務都是有效的,所以針對於單個特殊的任務回調操作,便不能放在會話管理器的屬性中,而是要放在與任務相關聯的 AFURLSessionManagerTaskDelegate 代理對象中。

實際使用 AFURLSessionManager 的方法創建網絡請求任務時,傳遞的回調任務,都是在與任務相關聯的代理對象的方法中執行的。

以上就是AFURLSessionManager的內容。下面講解他的子類:AFHTTPSessionManager

2>AFHTTPSessionManager

發現AFHTTPSessionManager是繼承AFURLSessionManager並遵守<NSSecureCoding, NSCopying>

(1)

/**
 The URL used to construct requests from relative paths in methods like `requestWithMethod:URLString:parameters:`, and the `GET` / `POST` / et al. convenience methods.
 */
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;

/**
 Requests created with `requestWithMethod:URLString:parameters:` & `multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:` are constructed with a set of default headers using a parameter serialization specified by this property. By default, this is set to an instance of `AFHTTPRequestSerializer`, which serializes query string parameters for `GET`, `HEAD`, and `DELETE` requests, or otherwise URL-form-encodes HTTP message bodies.

 @warning `requestSerializer` must not be `nil`.
 */
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;

/**
 Responses sent from the server in data tasks created with `dataTaskWithRequest:success:failure:` and run using the `GET` / `POST` / et al. convenience methods are automatically validated and serialized by the response serializer. By default, this property is set to an instance of `AFJSONResponseSerializer`.

 @warning `responseSerializer` must not be `nil`.
 */
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;
/**
 The security policy used by created session to evaluate server trust for secure connections. `AFURLSessionManager` uses the `defaultPolicy` unless otherwise specified. A security policy configured with `AFSSLPinningModePublicKey` or `AFSSLPinningModeCertificate` can only be applied on a session manager initialized with a secure base URL (i.e. https). Applying a security policy with pinning enabled on an insecure session manager throws an `Invalid Security Policy` exception.
 */
@property (nonatomic, strong) AFSecurityPolicy *securityPolicy;

暴露出的 baseUrl 屬性是 readonly 
@property (readonly, nonatomic, strong, nullable) NSURL *baseURL;

// 請求序列化 
@property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer;

// 響應序列化 
@property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer;

以及

+ (instancetype)manager; 
- (instancetype)initWithBaseURL:(nullable NSURL *)url; 
- (instancetype)initWithBaseURL:(nullable NSURL *)url 
sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration

三個初始化方法,第一個是類方法,后兩個是需要傳參的實例方法。

(2)請求方式

GET 請求是向服務端發起請求數據,用來獲取或查詢資源信息 
POST 請求是向服務端發送數據的,用來更新資源信息,它可以改變數據的種類等資源 
PUT 請求和POST請求很像,都是發送數據的,但是PUT請求不能改變數據的種類等資源,它只能修改內容 
DELETE 請求就是用來刪除某個資源的 
PATCH 請求和PUT請求一樣,也是用來進行數據更新的,它是HTTP verb推薦用於更新的 
在實際開發過程中,我們還是使用【GET 和 POST】請求是最多的。

其實如果看一下AFHTTPSessionManager,發現里面並沒有需要講什么的,關鍵還是AFURLSessionManager類。

下面以GET請求為例:

//GET請求,調用下面那個方法
- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                      success:(void (^)(NSURLSessionDataTask *task, id responseObject))success
                      failure:(void (^)(NSURLSessionDataTask *task, NSError *error))failure
{

    return [self GET:URLString parameters:parameters progress:nil success:success failure:failure];
}

//GET請求
- (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
{
    //調用另一個方法構造GET請求
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];
    
    /*
    啟動任務
    使用AFHTTPSessionManager創建的任務默認都幫你啟動了,所以不需要手動調用resume方法了
    上一篇中講解的AFURLSessionManager默認沒有啟動,所以獲取任務后要手動啟動
    */
    [dataTask resume];

    return dataTask;
}
- (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) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }

        return nil;
    }

    __block NSURLSessionDataTask *dataTask = nil;
//利用AFURLSessionManager方法
    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;
}

2.AFNetworkReachabilityManager

 AFNetworkReachabilityManager對象用於監聽設備當前連接網絡的狀態。

AFNetworkReachabilityManager提供了4種創建方法:

/**
 Returns the shared network reachability manager.
 */
+ (instancetype)sharedManager;

/**
 Creates and returns a network reachability manager with the default socket address.
 
 @return An initialized network reachability manager, actively monitoring the default socket address.
 */
+ (instancetype)manager;

/**
 Creates and returns a network reachability manager for the specified domain.

 @param domain The domain used to evaluate network reachability.

 @return An initialized network reachability manager, actively monitoring the specified domain.
 */
+ (instancetype)managerForDomain:(NSString *)domain;

/**
 Creates and returns a network reachability manager for the socket address.

 @param address The socket address (`sockaddr_in6`) used to evaluate network reachability.

 @return An initialized network reachability manager, actively monitoring the specified socket address.
 */
+ (instancetype)managerForAddress:(const void *)address;
+ ( instancetype)sharedManager; //創建單例對象
+ (instancetype)manager; //創建實例對象
+ (instancetype)managerForDomain:(NSString*)domain; //根據地址名創建實例對象
+ (instancetype)managerForAddress:(constvoid*)address; //根據sockaddr創建實例對象

(1)+ ( instancetype)sharedManager; //創建單例對象

代碼如下:創建單例對象:
+ (instancetype)shareManager{
    static AFNetworkReachabilityManager *_shareManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken,^{
       _shareManager = [self manager];
)  ;
return _shareManager;
}    

sharedManager調用manager方法創建一個AFNetworkReachabilityManager對象,代碼如下:

//其中sockaddr_in6和sockaddr_in是描述網絡套接字的結構體,包含協議族類型、端口、ip地址等信息,然后調用managerForAddress:方法創建,
+ (instancetype)manager
{
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 90000) || (defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101100)
    struct sockaddr_in6 address;
    bzero(&address, sizeof(address));
    address.sin6_len = sizeof(address);
    address.sin6_family = AF_INET6;
#else
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_len = sizeof(address);
    address.sin_family = AF_INET;
#endif
    return [self managerForAddress:&address];
}

其他的兩種方法: 一個根據地址名創建實例對象,另一個根據sockaddr創建實例對象,可以直接看源代碼,用的並不是特別多。

(2)通過枚舉值查看網絡狀態

typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) {
    AFNetworkReachabilityStatusUnknown          = -1,//未知狀態
    AFNetworkReachabilityStatusNotReachable     = 0, //未連接
    AFNetworkReachabilityStatusReachableViaWWAN = 1, //蜂窩移動網絡(2G/3G/4G)
    AFNetworkReachabilityStatusReachableViaWiFi = 2, //wifi網絡
};

(3)網絡監聽

AFNetworkReachabilityManager通過startMonitoring方法和stopMonitoring開始並停止監聽當前設備連接的網絡狀態。

1>startMonitoring方法

該方法主要通過SystemConfiguration框架提供的API將networkReachability讓對象加入runloop中,開始工作,並且綁定監聽的回調函數處理狀態改變。

- (void)startMonitoring {
    [self stopMonitoring];

    if (!self.networkReachability) {
        return;
    }

    __weak __typeof(self)weakSelf = self;
    AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        strongSelf.networkReachabilityStatus = status;
        if (strongSelf.networkReachabilityStatusBlock) {
            strongSelf.networkReachabilityStatusBlock(status);
        }

    };

    SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL};
    SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context);
    SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{
        SCNetworkReachabilityFlags flags;
        if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) {
            AFPostReachabilityStatusChange(flags, callback);
        }
    });
}

根據查看源碼發現:該方法首先停止之前的監聽,然后調用SCNetworkReachabilitySetCallback方法來設置networkReachability的回調函數AFNetworkReachabilityCallback和上下文context對象。

在回調方法中有一個方法比較重要:

static void AFPostReachabilityStatusChange(SCNetworkReachabilityFlags flags, AFNetworkReachabilityStatusBlock block) {
    AFNetworkReachabilityStatus status = AFNetworkReachabilityStatusForFlags(flags); //根據flags獲取當前網絡連接狀態status
    dispatch_async(dispatch_get_main_queue(), ^{
        if (block) {
            block(status); //block是context中的info指針,調用info將status傳遞給外界
        }
         //status作為通知的值,發通知拋給外界
        NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
        NSDictionary *userInfo = @{ AFNetworkingReachabilityNotificationStatusItem: @(status) };
        [notificationCenter postNotificationName:AFNetworkingReachabilityDidChangeNotification object:nil userInfo:userInfo];
    });
}

此方法首先根據系統回調的AFNetworkReachabilityStatusForFlags方法以及falgs參數,獲取網絡連接狀態,然后進入block,將status拋出外界,同時拋一個通知將status拋給外界,當網絡狀態發生改變,會同事用這兩種方式傳遞給外界。

AFNetworkReachabilityStatusForFlags方法是核心方法,負責根據flag的狀態值,轉化為相應的枚舉值AFNetworkReachabilityStatus。

2>stopMonitoring方法

該方法通過監聽的方法是讓networkReachability對象從runloop中注銷。

- (void)stopMonitoring {
    if (!self.networkReachability) {
        return;
    }

    SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes);
}

(3)網絡屬性狀態

/**
 The current network reachability status.
 */
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;

/**
 Whether or not the network is currently reachable.
 */
@property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable;

/**
 Whether or not the network is currently reachable via WWAN.
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN;

/**
 Whether or not the network is currently reachable via WiFi.
 */
@property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi;

通過上面可發現:外界可通過isReachable方法,isReachableViaWWAN方法以及isReachableViaWiFi等獲取當前的網絡狀態。通過keyPathsForValuesAffectingValueForKey方法設置屬性值的依賴關系,利用KVO方式監聽屬性的變化。

下面是簡單的一個網絡監聽的小demo

1)[[AFNetworkReachabilityManagersharedManager] startMonitoring];

2)判斷網絡連接狀態。

 [[AFNetworkReachabilityManager sharedManager] setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {  
          
        switch (status) {  
                  
            case AFNetworkReachabilityStatusNotReachable:{  
                  
                NSLog(@"無網絡");  
                  
                break;  
                  
            }  
                  
            case AFNetworkReachabilityStatusReachableViaWiFi:{  
                  
                NSLog(@"WiFi網絡");  
                  
                break;  
                  
            }  
                  
            case AFNetworkReachabilityStatusReachableViaWWAN:{  
                  
                NSLog(@"3G網絡");  
                  
                break;  
                  
            }  
                  
            default:  
                  
                break;  
                  
        }  
          
    }];  

3.AFSecurityPolicy

首先看一下AFSecurityPolicy暴露出來的方法。

//https驗證模式 默認是無,還有證書匹配和公鑰匹配
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
//可以去匹配服務端證書驗證的證書 當我們不主動設置pinningMode的時候不會主動該字段是空的,如果主動設置,會調用默認讀取certificatesInBundle .cer的證書進行賦值 源碼里面有
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
//allowInvalidCertificates 是否允許無效證書(也就是自建的證書),默認為NO
//如果是需要驗證自建證書,需要設置為YES 一般測試的時候YES,https開啟就弄為NO
@property (nonatomic, assign) BOOL allowInvalidCertificates;
//validatesDomainName 是否需要驗證域名,默認為YES;
//假如證書的域名與你請求的域名不一致,需把該項設置為NO;如設成NO的話,即服務器使用其他可信任機構頒發的證書,也可以建立連接,這個非常危險,建議打開。
//置為NO,主要用於這種情況:客戶端請求的是子域名,而證書上的是另外一個域名。因為SSL證書上的域名是獨立的,假如證書上注冊的域名是www.google.com,那么mail.google.com是無法驗證通過的;當然,有錢可以注冊通配符的域名*.google.com,但這個還是比較貴的。
//如置為NO,建議自己添加對應域名的校驗邏輯。
@property (nonatomic, assign) BOOL validatesDomainName;

AFNetworking認證核心代碼

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
//挑戰處理類型為 默認
    /*
     NSURLSessionAuthChallengePerformDefaultHandling:默認方式處理
     NSURLSessionAuthChallengeUseCredential:使用指定的證書
     NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑戰
     */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    // 服務器挑戰的證書
    __block NSURLCredential *credential = nil;
// 這個Block是提供給用戶自定義證書挑戰方式的,比如是否需要自定義
    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
    // NSURLAuthenticationMethodServerTrust 單向認證關系 也就是說服務器端需要客戶端返回一個根據認證挑戰的保護空間提供的信任產生的挑戰證書
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        // 基於客戶端的安全策略來決定是否信任該服務器,不信任的話,也就沒必要響應挑戰
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
             // 創建挑戰證書(注:挑戰方式為UseCredential和PerformDefaultHandling都需要新建挑戰證書)
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }
// 完成挑戰
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

4.AFURLRequestSerialization

AFURLRequestSerialization涉及兩個模塊:AFURLRequestSerialization和AFURLResponseSerialization

前者主要是修改請求頭部,提供了接口設置HTTP頭部字段,后者是處理響應的模塊,將請求放回的數據解析成相對應的格式。
5.UIKit+AFNetworking
如果需要使用 AFNetworking 的 UIKit 擴展時可直接在 Prefix.pch 文件中引入,或者在工程的相關文件中引入。
(1)AFAutoPurgingImageCache:用於緩存圖片的類,通過identifier來添加和搜索UIImage。
 
協議中添加圖片
- (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier;

協議中刪除圖片

- (BOOL)removeImageWithIdentifier:(NSString *)identifier;

協議中刪除所有圖片

- (BOOL)removeAllImages;

通過identifier獲取圖片的方法:

- (nullable UIImage *)imageWithIdentifier:(NSString *)identifier;

(2)UIButton+AFNetworking

UIButton跟圖片相關的屬性大概有兩個,ImageBackgroundImage.所以這個分類就是賦予他們異步加載圖片的能力。

核心方法有以下:

- (void)setImageForState:(UIControlState)state
                 withURL:(NSURL *)url;
- (void)setImageForState:(UIControlState)state
                 withURL:(NSURL *)url
        placeholderImage:(nullable UIImage *)placeholderImage;
- (void)setImageForState:(UIControlState)state
          withURLRequest:(NSURLRequest *)urlRequest
        placeholderImage:(nullable UIImage *)placeholderImage
                 success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                 failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;


- (void)setBackgroundImageForState:(UIControlState)state
                           withURL:(NSURL *)url;
- (void)setBackgroundImageForState:(UIControlState)state
                           withURL:(NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholderImage;
- (void)setBackgroundImageForState:(UIControlState)state
                    withURLRequest:(NSURLRequest *)urlRequest
                  placeholderImage:(nullable UIImage *)placeholderImage
                           success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success
                           failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;

(3)UIImageView+AFNetworking

UIImageView+AFNetworking 是AFNetworking中一個實現圖片異步加載的類, 它是為系統中的UIImageView類添加的類目(Category), 這個類目中的方法為遠程異步加載圖片功能提供支持.

思路:

  1. 導入AFNetworking工程文件.
  2. 引入UIKit+AFNetworking/UIImageView+AFNetworking.h頭文件.
  3. 下載圖片
  4. 取消圖片下載(可選)

實現異步加載:

/* 創建NSURL對象 */
    NSURL *urlStr = [NSURL URLWithString:@"http://img2.cache.netease.com/3g/2015/9/18/20150918195439dc844.jpg"];

/* 創建請求對象 */
    NSURLRequest *request = [NSURLRequest requestWithURL:urlStr];

/* 創建一個imageView對象 */
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 100)];

方法一:

/** 
 * 方法一:
 * 直接使用url字符串獲取照片
 * @param urlStr : NSURL對象
 */
[imageView setImageWithURL:urlStr];

方法二:

/**
 * 方法二:
 * 直接使用URL(例如:http://img2.cache.netease.com/3g/2015/9/18/20150918195439dc844.jpg)異步加載圖片, 照片出現之前添加一個占位的背景照片
 * @param urlStr : NSURL對象
 * @param placeholderImage : UIImage圖片對象
 */
[imageView setImageWithURL:urlStr placeholderImage:[UIImage imageNamed:@"discuss"]];

方法三:

/**
  * 方法三:
  * 使用URLRequest對象獲取照片, 照片在請求后的block塊中返回一個UIImage參數用於賦值或其他作.
  * 參數中得兩個block塊:分別在請求成功時執行 success:^{},   
                           請求失敗時執行 failure:^{}。
  * @param request : NSURLRequest請求對象
  * @param placeholderImage : UIImage圖片對象
  */
 [imageView setImageWithURLRequest:request placeholderImage:[UIImage imageNamed:@"discuss"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
        /**
         * 返回值參數
         * @param request : NSURLRequest 請求對象
         * @param response : NSHTTPURLResponse HTTP響應者對象(包含請求對象和圖片信息)
         * @param image : 加載成功圖片UIImage對象(自帶的size尺寸用於自適應高度計算)
         */
        [imageView setImage:image];
    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
        /**< @param error : NSError對象 加載錯誤的原因代號(例如 : 403) */
    }];
 /**
     * 取消圖片請求操作
     * 可以直接取消本段代碼塊中在調用這個方法之前的所有異步加載操作(適當的地方慎重使用)
     */
    [imageView cancelImageRequestOperation];

當然還有其他一些的擴展協議,但使用並不是很多,且看源碼不是很難。上面就是這兩天看AFNetworking源碼的一些自己感悟:因為AFNetworking主要是AFURLSessionManager、AFHTTPSessionManager,自己花7成都在上面,希望對大家對AFNetworking的理解有所加深,歡迎指正。

 

下一篇,我將結合自己的項目對AFNetworking的使用進行封裝,對中小型企業APP的網絡封裝可以直接使用,謝謝大家!!!


免責聲明!

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



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