源码阅读:AFNetworking(十六)——UIWebView+AFNetworking

1,410 阅读4分钟

该文章阅读的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