原始碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking

堯少羽發表於2018-03-16

該文章閱讀的AFNetworking的版本為3.2.0。

這個分類提供了對請求週期進行控制的方法,包括進度監控、成功和失敗的回撥。

1.介面檔案

1.1.屬性

/**
 網路會話管理者物件
 */
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;
複製程式碼

1.2.方法

/**
 非同步載入指定請求
 */
- (void)loadRequest:(NSURLRequest *)request
           progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
            success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
            failure:(nullable void (^)(NSError *error))failure;

/**
 以指定MIME型別和指定文字編碼格式非同步載入指定請求
 */
- (void)loadRequest:(NSURLRequest *)request
           MIMEType:(nullable NSString *)MIMEType
   textEncodingName:(nullable NSString *)textEncodingName
           progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
            success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success
            failure:(nullable void (^)(NSError *error))failure;
複製程式碼

2.UIWebView+_AFNetworking私有分類

2.1.介面

/**
 儲存任務物件
 */
@property (readwrite, nonatomic, strong, setter = af_setURLSessionTask:) NSURLSessionDataTask *af_URLSessionTask;
複製程式碼

2.2.實現

這兩個方法就是通過Runtime的關聯物件為分類新增屬性儲存任務物件

- (NSURLSessionDataTask *)af_URLSessionTask {
    return (NSURLSessionDataTask *)objc_getAssociatedObject(self, @selector(af_URLSessionTask));
}

- (void)af_setURLSessionTask:(NSURLSessionDataTask *)af_URLSessionTask {
    objc_setAssociatedObject(self, @selector(af_URLSessionTask), af_URLSessionTask, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
複製程式碼

3.方法實現

  • 屬性的訪問方法

下面的這四個方法同樣是通過關聯物件為分類新增屬,分別是儲存網路會話管理者物件和響應序列化物件

- (AFHTTPSessionManager  *)sessionManager {
    static AFHTTPSessionManager *_af_defaultHTTPSessionManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _af_defaultHTTPSessionManager = [[AFHTTPSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
        _af_defaultHTTPSessionManager.requestSerializer = [AFHTTPRequestSerializer serializer];
        _af_defaultHTTPSessionManager.responseSerializer = [AFHTTPResponseSerializer serializer];
    });

    return objc_getAssociatedObject(self, @selector(sessionManager)) ?: _af_defaultHTTPSessionManager;
}

- (void)setSessionManager:(AFHTTPSessionManager *)sessionManager {
    objc_setAssociatedObject(self, @selector(sessionManager), sessionManager, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (AFHTTPResponseSerializer <AFURLResponseSerialization> *)responseSerializer {
    static AFHTTPResponseSerializer <AFURLResponseSerialization> *_af_defaultResponseSerializer = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _af_defaultResponseSerializer = [AFHTTPResponseSerializer serializer];
    });

    return objc_getAssociatedObject(self, @selector(responseSerializer)) ?: _af_defaultResponseSerializer;
}

- (void)setResponseSerializer:(AFHTTPResponseSerializer<AFURLResponseSerialization> *)responseSerializer {
    objc_setAssociatedObject(self, @selector(responseSerializer), responseSerializer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
複製程式碼
  • 介面方法實現
- (void)loadRequest:(NSURLRequest *)request
           progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
            success:(NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
            failure:(void (^)(NSError *error))failure
{
    // 呼叫下面的方法
    [self loadRequest:request MIMEType:nil textEncodingName:nil progress:progress success:^NSData *(NSHTTPURLResponse *response, NSData *data) {
        // 設定字元編碼方式為UTF8
        NSStringEncoding stringEncoding = NSUTF8StringEncoding;
        // 如果響應物件有文字編碼方式,就將字元編碼方式設定為響應的文字編碼方式
        if (response.textEncodingName) {
            CFStringEncoding encoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
            if (encoding != kCFStringEncodingInvalidId) {
                stringEncoding = CFStringConvertEncodingToNSStringEncoding(encoding);
            }
        }

        // 將返回的資料進行編碼
        NSString *string = [[NSString alloc] initWithData:data encoding:stringEncoding];
        // 如果設定了成功回撥就呼叫block傳遞資料
        if (success) {
            string = success(response, string);
        }

        // 將字串編碼成二進位制資料後返回
        return [string dataUsingEncoding:stringEncoding];
    } failure:failure];
}

- (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
{
    // 在debug模式下缺少引數就crash
    NSParameterAssert(request);

    // 如果當前已經有任務正在進行或者已經暫停,就取消掉這個任務,並將儲存它的屬性置空
    if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
        [self.af_URLSessionTask cancel];
    }
    self.af_URLSessionTask = nil;

    // 生成任務
    __weak __typeof(self)weakSelf = self;
    __block NSURLSessionDataTask *dataTask;
    dataTask = [self.sessionManager
                dataTaskWithRequest:request
                uploadProgress:nil
                downloadProgress:nil
                completionHandler:^(NSURLResponse * _Nonnull response, id  _Nonnull responseObject, NSError * _Nullable error) {
                    __strong __typeof(weakSelf) strongSelf = weakSelf;
                    // 如果出錯就呼叫失敗回撥block
                    if (error) {
                        if (failure) {
                            failure(error);
                        }
                    // 如果成功
                    } else {
                        // 先呼叫成功回撥block
                        if (success) {
                            success((NSHTTPURLResponse *)response, responseObject);
                        }
                        // 呼叫UIWebView載入本地資料的方式進行載入頁面
                        [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[dataTask.currentRequest URL]];
                        
                        // 呼叫UIWebView代理中的完成載入方法
                        if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
                            [strongSelf.delegate webViewDidFinishLoad:strongSelf];
                        }
                    }
                }];
    // 用屬性儲存任務物件
    self.af_URLSessionTask = dataTask;
    // 如果設定了進度物件,就獲取到網路會話管理者的下載程式物件
    if (progress != nil) {
        *progress = [self.sessionManager downloadProgressForTask:dataTask];
    }
    // 啟動任務
    [self.af_URLSessionTask resume];

    // 呼叫UIWebView代理中的開始載入方法
    if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
        [self.delegate webViewDidStartLoad:self];
    }

複製程式碼

4.總結

看完這個分類的原始碼,我們可以看到這個分類做的事就是,手動實現了UIWebView載入網路資料的過程,從而可以監聽進度和通過block回撥處理成功與失敗的結果。

通常,我們會使用loadRequest:方法載入指定頁面,然後通過UIWebViewDelegate中的方法監聽網頁載入的開始、結束與失敗。

而這個分類,通過AFHTTPSessionManager類手動生成NSURLSessionDataTask物件下載網頁的二進位制資料,然後通過loadData: MIMEType: textEncodingName: baseURL:方法載入已經下載到本地的網頁的二進位制資料。在這個過程中通過AFHTTPSessionManager類中已經實現的方法實現進度的監控、成功和失敗的回撥。

原始碼閱讀系列:AFNetworking

原始碼閱讀:AFNetworking(一)——從使用入手

原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization

原始碼閱讀:AFNetworking(三)——AFURLResponseSerialization

原始碼閱讀:AFNetworking(四)——AFSecurityPolicy

原始碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager

原始碼閱讀:AFNetworking(六)——AFURLSessionManager

原始碼閱讀:AFNetworking(七)——AFHTTPSessionManager

原始碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache

原始碼閱讀:AFNetworking(九)——AFImageDownloader

原始碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager

原始碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking

原始碼閱讀:AFNetworking(十二)——UIButton+AFNetworking

原始碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking

原始碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking

原始碼閱讀:AFNetworking(十五)——UIRefreshControl+AFNetworking

原始碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking

相關文章