转载

KZWFoudation系列之WKWebView的封装

在iOS 8.0以后苹果推出WKWebView,之前有性能问题的UIWebView基本就被弃用了,这里整理下我的WKWebView之旅和怎么封装的。

1、WKWebView有个绕不过去的问题就是Cookie.

我们先来看下Cookie到底是个什么东西:

简单地说,cookie 就是浏览器储存在用户电脑上的一小段文本文件。cookie 是纯文本格式,不包含任何可执行的代码。一个 Web 页面或服务器告知浏览器按照一定规范来储存这些信息,并在随后的请求中将这些信息发送至服务器,Web 服务器就可以使用这些信息来识别不同的用户。大多数需要登录的网站在用户验证成功之后都会设置一个 cookie,只要这个 cookie 存在并可以,用户就可以自由浏览这个网站的任意页面。再次说明,cookie 只包含数据,就其本身而言并不有害。紧跟 cookie 值后面的每个选项都以分号和空格分开,每个选择都指定了 cookie 在什么情况下应该被发送至服务器。

第一个选项是过期时间(expires),指定了 cookie 何时不会再被发送至服务器,随后浏览器将删除该 cookie。该选项的值是一个 Wdy, DD-Mon-YYYY HH:MM:SS GMT 日期格式的值。下一个选项是 domain,指定了 cookie 将要被发送至哪个或哪些域中。默认情况下,domain会被设置为创建该 cookie 的页面所在的域名,所以当给相同域名发送请求时该 cookie 会被发送至服务器。另一个控制 Cookie 消息头发送时机的选项是 path 选项,和 domain 选项类似,path选项指定了请求的资源 URL 中必须存在指定的路径时,才会发送Cookie 消息头。

这个比较通常是将 path 选项的值与请求的 URL 从头开始逐字符比较完成的。如果字符匹配,则发送 Cookie 消息头。最后一个选项是 secure。不像其它选项,该选项只是一个标记而没有值。只有当一个请求通过 SSL 或 HTTPS 创建时,包含 secure 选项的 cookie 才能被发送至服务器。这种 cookie 的内容具有很高的价值,如果以纯文本形式传递很有可能被篡改。

UIWebView Cookie

同一个应用,不同UIWebView之间的Cookie是自动同步的。并且可以被其他网络类访问比如NSURLConnection,AFNetworking。

它们都是保存在NSHTTPCookieStorage容器中。 当UIWebView加载一个URL的时候,在加载完成时候,Http Response,对Cookie进行写入,更新或者删除,结果更新Cookie到NSHTTPCookieStorage存储容器中。

WKWebView Cookie

NSURLCache和NSHTTPCookieStroage无法操作(WKWebView)WebCore进程的缓存和Cookie。

WKWebView实例将会忽略任何的默认网络存储器(NSURLCache, NSHTTPCookieStorage, NSCredentialStorage) 和一些标准的自定义网络请求类(NSURLProtocol,等等.)。

WKWebView实例不会把Cookie存入到App标准的的Cookie容器(NSHTTPCookieStorage)中,因为 NSURLSession/NSURLConnection等网络请求使用NSHTTPCookieStorage进行访问Cookie,所以不能访问WKWebView的Cookie,现象就是WKWebView存了Cookie,其他的网络类如NSURLSession/NSURLConnection却看不到。,

与Cookie相同的情况就是WKWebView的缓存,凭据等。WKWebView都拥有自己的私有存储,因此和标准Cocoa网络类兼容的不是那么好。

你也不能自定义requests(增加自己的http header,更改已经存在的header)使用自定义的 URL schemes等等,因为NSURLProtocol也是不支持WKWebView的。

WKWebView Cookie 写入

1、JS注入:
WKUserContentController* userContentController = WKUserContentController.new;
    WKUserScript * cookieScript = [[WKUserScript alloc]
                                   initWithSource:[NSString stringWithFormat:@"document.cookie = '%@'", [self setCurrentCookie]]
                                   injectionTime:WKUserScriptInjectionTimeAtDocumentStart forMainFrameOnly:NO];
    [userContentController addUserScript:cookieScript];
- (NSString *)setCurrentCookie {
    return @"";
}
2、NSMutableURLRequest 注入
- (void)loadURLRequest {
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:self.url];
    [request addValue:[self readCurrentCookie] forHTTPHeaderField:@"Cookie"];
    [self.webView loadRequest:request];
}
- (NSString *)setCurrentCookie {
    return @"";
}

划重点:坑一:JS注入的Cookie,比如PHP代码在Cookie容器中取是取不到的, javascript document.cookie能读取到,浏览器中也能看到。

NSMutableURLRequest 注入的PHP等动态语言直接能从$_COOKIE对象中获取到,但是js读取不到,浏览器也看不到

所以合理的办法让js,php,浏览器都能读取到相同的Cookie方法就是创建WebView的时候javascript注入Cookie,一开始发送NSMutableURLRequest请求的时候也要加上Cookie,并且保证两个地方的设置的cookie一致。

坑二:WKWebView的cookie需要设置domain和path默认情况下会带进去不是通用的。(今天刚发现的o)

坑三:网页登录跳原生之后登录成功后dimis后你需要重新注入和刷新把cookie塞进去,所以你的viewWillAppear每次都需要重新加载WKWebView和重新loadRequest,简直了。。。

KZWWebViewController 是如何做的呢?

我们知道,app里经常跳各种网页,我们不可能每个网页都去单独处理,所以我们写一个通用的可配置的KZWWebViewController,只需要传url进来就可以,其他你不要管了,是不是完美。

所以我们需要来设计一个这样的KZWWebViewController,首先必须的是跳转的url和其他的配置参数,然后是接入jsbridge,方便我们和网页的换下调用,这样我们的这个KZWWebViewController就基本满足需求了。

所以我的做法是抽出一个类来管理,它叫KZWRouterHelper,暴露一个方法

+ (void)pushbyPath:(NSString *)path xxx(xxx)xx .....

里面的操作是把你需要的配置的加上,然后转成字典塞入router

NSDictionary *params = @{
                             @"path": [path kzw_urlEncode],
                             @"timestimp":[NSString stringWithFormat:@"%g", [[NSDate date] timeIntervalSince1970]]
                             };
    NSString *url =
    [NSString stringWithFormat:@"%@?%@", KZWWebViewControllerRouterPath, [NSURL elm_queryStringFromParameters:params]];
    return [[ELMRouter sharedRouter] open:url animated:NO showStyle:ELMPageShowStylePush];

param里包含了你所以的配置,类如:

NSDictionary *params = @{
                             @"path": path,
                             @"fullScreen": @(NO),
                             @"fullUrl": @(NO),
                             @"title": string?string:@"",
                             @"timestimp":[NSString stringWithFormat:@"%g", [[NSDate date] timeIntervalSince1970]]
                             };

这个根据业务需求来配置就好,然后在controller里根据不同的参数做相应的处理就可以了,这样你的整个项目里所有的网页跳转就一行代码就好了:

[KZWRouterHelper pushbyURL:@"xxxx" ];

接入的jsbirdge最好是选择jscore的方式的,这样是同步,网页也可以加个配置方法,这个的主要目的,有的网页需要由网页自己来控制一些显示,原理同上我们自己的配置都是根据参数做不同的处理,具体看KZWWebViewController,然后很多时候产品想要跳二级页面的时候可以有2个返回,这时候如果你的leftBarButtonItems是统配的话最好在页面加载的时候就先设置一个返回,然后在didFinishNavigation代理设置成2个返回,一个返回上一个网页一个返回我们上一个控制器。这样做主要是你开始设置成统配后来直接配2个会闪一下。

还有就是WKWebView中的进度条,WKWebView的进度条比较简单你只要写一个UIProgressView然后监听WKWebView的加载进度就好了,然后title的显示也是监听就好了,如果没取到记得设置一个默认的。

 [self.webView addObserver:self
                   forKeyPath:NSStringFromSelector(@selector(estimatedProgress))
                      options:0
                      context:nil];
    [self.webView addObserver:self forKeyPath:NSStringFromSelector(@selector(title)) options:NSKeyValueObservingOptionNew context:NULL];

然后是适配iPhone X的记得加这行代码:

if (KZW_iPhoneX) {
        self.webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
    }

最后记得释放你的监听:

- (NSString *)fullString:(NSString *)path {
    NSString *domain = nil;
    switch ([ELMEnvironmentManager environment]) {
        case ELMEnvBeta:
            domain = @"xxxxx";
            break;
        case ELMEnvAlpha:
            domain = @"xxxxx";
            break;
        case ELMEnvProduction:
            domain = @"xxxxx";
            break;
        default:
            domain = @"xxxxx";
            break;
    }
    if ([path containsString:@"http"]) {
        return path;
    }else {
       return [domain stringByAppendingString:path];
    }
}

- (void)dealloc {
    self.webView.UIDelegate = nil;
    [self.webView stopLoading];
    [self.webView removeObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
    [self.webView removeObserver:self forKeyPath:NSStringFromSelector(@selector(title))];
    self.webView = nil;
}

组url的时候可以加个环境的配置。

就完啦,希望对你有用。具体看KZWFoudation中的KZWWebViewController,KZWRouterHelper和KZWDSJavaScripInterface。https://github.com/ouyrp/KZWFoundation

哦还有一个cookie的清空和网页清缓存我也加上吧,前2篇关于WKWebView的文章就删了。

- (NSString *)readCurrentCookie {
    return @"";
}

- (NSString *)setCurrentCookie {
    return @"";
}

cookie清空只要这个2个方法里面参数清了就好了
if ([[UIDevice currentDevice].systemVersion floatValue] >= 9.0) {
        WKWebsiteDataStore *dateStore = [WKWebsiteDataStore defaultDataStore];
        [dateStore fetchDataRecordsOfTypes:[WKWebsiteDataStore allWebsiteDataTypes]
                         completionHandler:^(NSArray * __nonnull records) {
                             for (WKWebsiteDataRecord *record  in records)
                             {
                                 if ( [record.displayName containsString:@"xxxxx"])
                                 {
                                     [[WKWebsiteDataStore defaultDataStore] removeDataOfTypes:record.dataTypes
                                                                               forDataRecords:@[record]
                                                                            completionHandler:^{
                                                                                NSLog(@"Cookies for %@ deleted successfully",record.displayName);
                                                                            }];
                                 }
                             }
                         }];
    }else {
        NSString *librarypath = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES).firstObject;
        NSString *cookiesFolderPath = [librarypath stringByAppendingString:@"/Cookies"];
        [[NSFileManager defaultManager] removeItemAtPath:cookiesFolderPath error:nil];
    }
    NSHTTPCookieStorage *cookieJar = [NSHTTPCookieStorage sharedHTTPCookieStorage];
    for (NSHTTPCookie *cookie in [cookieJar cookies]) {
        [cookieJar deleteCookie:cookie];
    }

这是清缓存,亲测有效

之后还是会出KZWFoudation的系列文章,写下自己的封装思路。

作者:moonCoder

链接:https://www.jianshu.com/p/0f69030e9cb3

正文到此结束
Loading...