KZWFoundation系列之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<WKWebsiteDataRecord *> * __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的系列文章,写下自己的封装思路。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容