前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AFNetworking源码探究(二十三) —— UIKit相关之UIWebView+AFNetworking分类(六)

AFNetworking源码探究(二十三) —— UIKit相关之UIWebView+AFNetworking分类(六)

原创
作者头像
conanma
修改2021-09-03 18:03:08
3930
修改2021-09-03 18:03:08
举报
文章被收录于专栏:正则正则

回顾

上一篇讲述了UIButton+AFNetworking的UIButton的一个分类。分析了其下载器的下载、图像的下载以及背景图像的下载。这一篇就继续讲述AFN中UIWebView的分类。


接口API

我们看一下UIWebView分类的API接口。

代码语言:javascript
复制
/**
 This category adds methods to the UIKit framework's `UIWebView` class. The methods in this category provide increased control over the request cycle, including progress monitoring and success / failure handling.

 @discussion When using these category methods, make sure to assign `delegate` for the web view, which implements `–webView:shouldStartLoadWithRequest:navigationType:` appropriately. This allows for tapped links to be loaded through AFNetworking, and can ensure that `canGoBack` & `canGoForward` update their values correctly.
 */
@interface UIWebView (AFNetworking)

/**
 The session manager used to download all requests.
 */
@property (nonatomic, strong) AFHTTPSessionManager *sessionManager;

/**
 Asynchronously loads the specified request.
 // 异步加载指定的请求

 @param request A URL request identifying the location of the content to load. This must not be `nil`.
 @param progress A progress object monitoring the current download progress.
 @param success A block object to be executed when the request finishes loading successfully. This block returns the HTML string to be loaded by the web view, and takes two arguments: the response, and the response string.
 @param failure A block object to be executed when the data task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred.
 */
- (void)loadRequest:(NSURLRequest *)request
           progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
            success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
            failure:(nullable void (^)(NSError *error))failure;

/**
 Asynchronously loads the data associated with a particular request with a specified MIME type and text encoding.
 // 异步加载具有指定MIME类型和文本编码格式的指定请求的数据

 @param request A URL request identifying the location of the content to load. This must not be `nil`.
 @param MIMEType The MIME type of the content. Defaults to the content type of the response if not specified.
 @param textEncodingName The IANA encoding name, as in `utf-8` or `utf-16`. Defaults to the response text encoding if not specified.
@param progress A progress object monitoring the current download progress.
 @param success A block object to be executed when the request finishes loading successfully. This block returns the data to be loaded by the web view and takes two arguments: the response, and the downloaded data.
 @param failure A block object to be executed when the data task finishes unsuccessfully, or that finishes successfully, but encountered an error while parsing the response data. This block has no return value and takes a single argument: the error that occurred.
 */
- (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;

@end

这里接口有一个属性和两个方法。

该类为UIKit框架的UIWebView类添加方法。 此类别中的方法可以更好地控制请求周期,包括进度监视和成功/失败处理。

在使用这些类别方法时,请确保为webView分配delegate,它适当地实现- webView:shouldStartLoadWithRequest:navigationType:。 这允许通过AFNetworking加载引出的链接,并且可以确保canGoBackcanGoForward正确地更新它们的值。


获取数据任务

这里面实现了UIWebView的另外一个分类_AFNetworking,利用runtime获取了数据任务。

代码语言:javascript
复制
- (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);
}

获取AFHTTPSessionManager和AFHTTPResponseSerializer对象

这个是在UIWebView的分类AFNetworking中实现的,实现方式还是使用runtime。

代码语言:javascript
复制
// AFHTTPSessionManager对象的获取
- (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对象的实现
- (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);
}

请求数据的实现

主要就是下面两个方法。

代码语言:javascript
复制
- (void)loadRequest:(NSURLRequest *)request
           progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress
            success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success
            failure:(nullable void (^)(NSError *error))failure;

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

其实看一下源码就知道,上面方法是通过调用下面的方法实现的,传递的参数MIMEType和textEncodingName都为nil,并在数据回来中进行了处理。下面我们就一起看一下。

1. 加载指定请求

主要看一下实现

代码语言:javascript
复制
- (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) {
        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];
        if (success) {
            string = success(response, string);
        }

        return [string dataUsingEncoding:stringEncoding];
    } failure:failure];
}

我们看一下在成功回调做的处理。

代码语言:javascript
复制
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];
if (success) {
    string = success(response, string);
}

return [string dataUsingEncoding:stringEncoding];

这里首先获取编码格式,默认是NSUTF8StringEncoding,如果response.textEncodingName存在,那么就进行相关编码转化,最后就是利用生成的编码格式,生成NSString类型的数据,并作为成功回调的参数进行传递。

2. 加载指定MIME类型、编码格式的请求

下面就是看一下请求

代码语言:javascript
复制
- (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);

    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;
                    if (error) {
                        if (failure) {
                            failure(error);
                        }
                    } else {
                        if (success) {
                            success((NSHTTPURLResponse *)response, responseObject);
                        }
                        [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[dataTask.currentRequest URL]];

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

    if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
        [self.delegate webViewDidStartLoad:self];
    }
}

这个实现主要做了下面几个工作:

  • 任务状态的判断及逻辑处理
  • AFHTTPSessionManager对象开启指定request的请求,并处理成功和失败的回调
  • 处理进度,重新开启任务

(a) 任务状态的判断及逻辑处理

主要对应下面这段代码

代码语言:javascript
复制
NSParameterAssert(request);

if (self.af_URLSessionTask.state == NSURLSessionTaskStateRunning || self.af_URLSessionTask.state == NSURLSessionTaskStateSuspended) {
    [self.af_URLSessionTask cancel];
}
self.af_URLSessionTask = nil;

这里首选使用断言NSParameterAssert进行参数判断,参数为空就崩溃。然后判断任务的状态,如果任务正在进行或者暂停,那么就取消该任务。并将任务指针设置为nil。

(b) 开启指定request的请求

主要对应下面这段代码。

代码语言:javascript
复制
__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;
                if (error) {
                    if (failure) {
                        failure(error);
                    }
                } else {
                    if (success) {
                        success((NSHTTPURLResponse *)response, responseObject);
                    }
                    [strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[dataTask.currentRequest URL]];

                    if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
                        [strongSelf.delegate webViewDidFinishLoad:strongSelf];
                    }
                }
            }];
self.af_URLSessionTask = dataTask;

这里逻辑也是很清晰了吧,如果存在错误,那么就回调failure(error),否则就说明没有失败,那么就进行回调success((NSHTTPURLResponse *)response, responseObject)

代码语言:javascript
复制
[strongSelf loadData:responseObject MIMEType:MIMEType textEncodingName:textEncodingName baseURL:[dataTask.currentRequest URL]];

if ([strongSelf.delegate respondsToSelector:@selector(webViewDidFinishLoad:)]) {
    [strongSelf.delegate webViewDidFinishLoad:strongSelf];
}

接着就是利用上面方法请求数据,并设置了代理方法。

(c) 处理进度,重新开启任务

代码语言:javascript
复制
if (progress != nil) {
    *progress = [self.sessionManager downloadProgressForTask:dataTask];
}
[self.af_URLSessionTask resume];

if ([self.delegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
    [self.delegate webViewDidStartLoad:self];
}

这里如果传入的进度参数progress不为nil,那么就调用方法获得进度参数。并让任务af_URLSessionTask开启,设置了已经开启的代理方法webViewDidStartLoad:

后记

本篇主要讲述AFN中UIWebView的分类,详细的分析了指定request和指定MIME类型和编码的request下的请求。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 回顾
  • 接口API
  • 获取数据任务
  • 获取AFHTTPSessionManager和AFHTTPResponseSerializer对象
  • 请求数据的实现
    • 1. 加载指定请求
      • 2. 加载指定MIME类型、编码格式的请求
      • 后记
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档