WKWebView筆記

前言

iOS8開始,蘋果引入了新的web控件WKWebView替代UIWebView,WKWebView屬于WebKit框架,WebKit框架的API極為豐富,可以從WKWebView入手逐個了解。WebKit框架也在持續(xù)更新中,iOS9,iOS 10都引入了新的API,趨勢就是趕緊廢棄UIWebView使用WKWebView吧。本文是升級項目中的UIWebView的一些經(jīng)驗和遇到的坑,希望可以幫助到大家。

1. WKWebView簡介

一個WKWebView用來展示可交互的網(wǎng)頁內(nèi)容,就像一個APP內(nèi)的瀏覽器。你可以使用WKWebView在你的APP中嵌入網(wǎng)頁內(nèi)容。

1.1.WKUserContentController

這個屬性非常重要,js->oc的交互全靠它。

  • 1.動態(tài)注入js,注入的既可以是js代碼,也可以是一個js文件。
WKUserScript *script = [[WKUserScript alloc] initWithSource:@"alert('哈哈');" injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:NO];
[controller addUserScript:script];
  • 2.JavaScript向WKWebView發(fā)送消息,通過識別不同的消息和消息的內(nèi)容,可以執(zhí)行不同的native操作。
- (void)addScriptMessageHandler:(id <WKScriptMessageHandler>)scriptMessageHandler name:(NSString *)name;

遵循WKScriptMessageHandler協(xié)議的對象可以在以下代理方法接收J(rèn)avaScript發(fā)送的消息。

- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message;

1.2.customUserAgent

@property (nullable, nonatomic, copy) NSString *customUserAgent API_AVAILABLE(macosx(10.11), ios(9.0));

用來自定義瀏覽器UserAgent,可惜的是9.0之后才可以使用,所以還是與UIWebView一樣通過NSUserDefaults來設(shè)置:

[[NSUserDefaults standardUserDefaults] registerDefaults:@{@"UserAgent": newUserAgent}];

1.3.屬性支持kvo

WKWebView的大部分屬性是支持kvo的,但是并沒有提供代理方法,需要自己添加監(jiān)聽。例如監(jiān)聽titleestimatedProgress。

[self.wkWebView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
[self.wkWebView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];

這樣就可以展示進度條,無需等待web完全加載完畢才顯示標(biāo)題

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    if ([keyPath isEqualToString:@"estimatedProgress"]) {
        CGFloat progress = [[change valueForKey:NSKeyValueChangeNewKey] floatValue];
        if (progress >= 1) {
            [self.progressView setProgress:progress animated:NO];
            self.progressView.hidden = YES;
            [self.progressView setProgress:0 animated:NO];
        } else {
            self.progressView.hidden = NO;
            [self.progressView setProgress:progress animated:YES];
        }
    }
    if ([keyPath isEqualToString:@"title"] && !self.defaultTitle) {
        NSString *title = [change valueForKey:NSKeyValueChangeNewKey];
        if (title) {
            self.mTitleLabel.text = title;
        }
    }
}

記得刪除監(jiān)聽

- (void)dealloc{
    NSLog(@"%@",NSStringFromSelector(_cmd));
    [self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress" context:nil];
    [self.wkWebView removeObserver:self forKeyPath:@"title" context:nil];
}

1.4.識別網(wǎng)頁內(nèi)容

長按網(wǎng)頁,會彈出一個UIActionSheet。例如長按一張圖片會提示你保存圖片。如果想在UIWebView實現(xiàn)這個功能只能自定義一個手勢然后通過js獲取網(wǎng)頁內(nèi)容再彈出UIActionSheet。

識別網(wǎng)頁內(nèi)容

1.5.JS

UIWebView調(diào)用jsstringByEvaluatingJavaScriptFromString是同步返回的,并沒有提供狀態(tài)信息。WebKit是異步block回調(diào)的,并帶有狀態(tài)信息。使用時要注意在頁面銷毀時恰好進行了回調(diào)在iOS8上會crash。

- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

2.一些坑

2.1.cookie問題

這應(yīng)該是WebKit最大的坑,網(wǎng)上有好多文章介紹了原因和解決方法,我就不畫蛇添足了,貼上個鏈接:cookie問題,我這里記錄下解決方法。
問題所在:WKWebView加載網(wǎng)頁得到的Cookie會同步到NSHTTPCookieStorage中,但是WKWebView加載請求時,不會同步NSHTTPCookieStorage中已有的Cookie,所以導(dǎo)致Cookie丟失,web無法識別客戶端身份。
解決方法:將NSHTTPCookieStorage存儲的Cookie設(shè)置為請求的allHTTPHeaderFields,通過注入js的方式將Cookie寫入web中。

2.2

[5504:1981977] webViewWebContentProcessDidTerminate
[5504:1982134] #WK: Unable to acquire assertion for process 0
[5504:1981977] Could not signal service com.apple.WebKit.WebContent: 113: Could not find specified service

模擬慢速網(wǎng)絡(luò)時經(jīng)常出現(xiàn)這種錯誤,進度加載一部分后退回到0。調(diào)試時發(fā)現(xiàn)是在創(chuàng)建NSMutableURLRequest是設(shè)置的超時時間過短導(dǎo)致的。解決方法是加大超時時間或者干脆不設(shè)置超時時間。

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:20];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.url]];

2.3

在iOS9 iPod上進行測試時,點進一個web頁面沒問題,但是返回的時候crash。錯誤信息如下

2017-08-18 19:29:52.734 BluedInternational[11600:1646954] dealloc
objc[11600]: Cannot form weak reference to instance (0x5225200) of class GJWebViewController. It is possible that this object was over-released, or is in the process of deallocation.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=EXC_ARM_BREAKPOINT, subcode=0xdefe)
  * frame #0: 0x20bf2a44 libobjc.A.dylib`_objc_trap()
    frame #1: 0x20bf2aa8 libobjc.A.dylib`_objc_fatal(char const*, ...) + 72
    frame #2: 0x20c0c412 libobjc.A.dylib`weak_register_no_lock + 210
    frame #3: 0x20c0c7b8 libobjc.A.dylib`objc_storeWeak + 208
    frame #4: 0x25a8489a UIKit`-[UIScrollView setDelegate:] + 306
    frame #5: 0x283c6f30 WebKit`-[WKScrollView _updateDelegate] + 228
    frame #6: 0x283d09fe WebKit`-[WKWebView dealloc] + 266
    frame #7: 0x20c0d3a8 libobjc.A.dylib`(anonymous namespace)::AutoreleasePoolPage::pop(void*) + 388
    frame #8: 0x21366f88 CoreFoundation`_CFAutoreleasePoolPop + 16
    frame #9: 0x2141806e CoreFoundation`__CFRunLoopRun + 1582
    frame #10: 0x21367228 CoreFoundation`CFRunLoopRunSpecific + 520
    frame #11: 0x21367014 CoreFoundation`CFRunLoopRunInMode + 108
    frame #12: 0x22957ac8 GraphicsServices`GSEventRunModal + 160
    frame #13: 0x25a3b188 UIKit`UIApplicationMain + 144
    frame #14: 0x0007cf62 BluedInternational`main(argc=1, argv=0x0361bab8) at main.m:13
    frame #15: 0x2100f872 libdyld.dylib`start + 2
(lldb) 

分析發(fā)現(xiàn),在WKWebView釋放之后竟然還進行了scrollView代理的設(shè)置,而這個時候的self,也就是當(dāng)前的控制器處于銷毀當(dāng)中,也就解釋了上面log提到的or is in the process of deallocation.。所以加入你的WKWebView是懶加載的,不要在懶加載中設(shè)置代理,其次在dealloc中將代理置為nil。

- (void)dealloc{
    NSLog(@"%@",NSStringFromSelector(_cmd));
    [self.wkWebView removeObserver:self forKeyPath:@"estimatedProgress" context:nil];
    [self.wkWebView removeObserver:self forKeyPath:@"title" context:nil];
    self.wkWebView.navigationDelegate = nil;
    self.wkWebView.UIDelegate = nil;
    self.wkWebView.scrollView.delegate = nil;
}

2.4

在iOS10調(diào)用js時crash,以下是錯誤信息。這個并沒有發(fā)現(xiàn)錯在哪里,APP刪除重新安裝后就沒復(fù)現(xiàn)過。如果有遇到同樣問題的,請不吝賜教。

//2017-08-18 11:17:00.576568 [8132:2457446] Could not signal service com.apple.WebKit.Networking: 113: Could not find specified service
//(8132,0x1a8799c40) malloc: *** error for object 0x1700bf260: pointer being freed was not allocated
//*** set a breakpoint in malloc_error_break to debug

2.5

上線一段時間后用戶反饋有個URL加載失?。?a target="_blank" rel="nofollow">https://changba.com/s/9nAISuzODZd125S0d2HhOQ。錯誤信息如下:

webView:didFailNavigation:withError:
<WKNavigation: 0x10a4c1e80>,error:Error Domain=NSURLErrorDomain Code=-999 "(null)" UserInfo={NSErrorFailingURLStringKey=http://changba.com/s/9nAISuzODZd125S0d2HhOQ, NSErrorFailingURLKey=http://changba.com/s/9nAISuzODZd125S0d2HhOQ, _WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x1c4a34900>}

webView:didFailProvisionalNavigation:withError:
<WKNavigation: 0x109e5f880>,error:Error Domain=NSURLErrorDomain Code=-1002 "unsupported URL" UserInfo={_WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x1c463a4e0>, NSErrorFailingURLStringKey=changba://?ac=playuserwork&workid=976250206, NSErrorFailingURLKey=changba://?ac=playuserwork&workid=976250206, NSLocalizedDescription=unsupported URL, NSUnderlyingError=0x1c4841320 {Error Domain=kCFErrorDomainCFNetwork Code=-1002 "(null)"}}

使用Safari打開雖然提示無效,但是可以正常顯示內(nèi)容:
Safari打開

那么看來是兼容無效URL的問題了。下面是每次重定向的URL信息:

webView:decidePolicyForNavigationAction:decisionHandler:
<WKNavigationAction: 0x11408e660; navigationType = -1; syntheticClickType = 0; position x = 0.00 y = 0.00 request = <NSMutableURLRequest: 0x1c4002650> { URL: https://changba.com/s/9nAISuzODZd125S0d2HhOQ }; sourceFrame = (null); targetFrame = <WKFrameInfo: 0x105ccad80; webView = 0x106181000; isMainFrame = YES; request = (null)>>
webView:decidePolicyForNavigationAction:decisionHandler:
<WKNavigationAction: 0x105d762f0; navigationType = -1; syntheticClickType = 0; position x = 0.00 y = 0.00 request = <NSMutableURLRequest: 0x1c0011230> { URL: http://changba.com/s/9nAISuzODZd125S0d2HhOQ }; sourceFrame = <WKFrameInfo: 0x105de47d0; webView = 0x106181000; isMainFrame = YES; request = (null)>; targetFrame = <WKFrameInfo: 0x105de47d0; webView = 0x106181000; isMainFrame = YES; request = (null)>>
webView:decidePolicyForNavigationAction:decisionHandler:
<WKNavigationAction: 0x105d762f0; navigationType = -1; syntheticClickType = 0; position x = 0.00 y = 0.00 request = <NSMutableURLRequest: 0x1c0009640> { URL: changba://?ac=playuserwork&workid=976250206 }; sourceFrame = <WKFrameInfo: 0x105d4e590; webView = 0x106181000; isMainFrame = YES; request = <NSMutableURLRequest: 0x1c40096d0> { URL: http://changba.com/s/9nAISuzODZd125S0d2HhOQ }>; targetFrame = <WKFrameInfo: 0x105d4e590; webView = 0x106181000; isMainFrame = YES; request = <NSMutableURLRequest: 0x1c40096d0> { URL: http://changba.com/s/9nAISuzODZd125S0d2HhOQ }>>

發(fā)現(xiàn)最后一次的URL為:changba://?ac=playuserwork&workid=976250206。host和scheme丟失了!導(dǎo)致最后加載失敗。解決方法是加個判斷條件,如果滿足那么就取消加載,顯示已經(jīng)加載出的頁面即可:

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler
    NSLog(@"%@\n%@",NSStringFromSelector(_cmd),navigationAction);
    if (navigationAction.request.URL.host == nil) {
        NSArray *schemeArr = @[@"mailto",@"tel"];
        if (![schemeArr containsObject:navigationAction.request.URL.scheme]) {
            decisionHandler(WKNavigationActionPolicyCancel);
            return;
        }
    }

擴展

1.cookie

Cookie是網(wǎng)站為了識別終端身份,保存在終端本地的用戶憑證信息。Cookie中的字段與意義由服務(wù)端進行定義。例如,當(dāng)用戶在某個網(wǎng)站進行了登錄操作后,服務(wù)端會將Cookie信息返回給終端,終端會將這些信息進行保存,在下一次再次訪問這個網(wǎng)站時,終端會將保存的Cookie信息一并發(fā)送到服務(wù)端,服務(wù)端根據(jù)Cookie信息是否有效來判斷此用戶是否可以自動登錄

1.1.NSHTTPCookie

一個NSHTTPCookie實例代表一個單獨的http cookie,以指定的字典來初始化。

- (nullable instancetype)initWithProperties:(NSDictionary<NSHTTPCookiePropertyKey, id> *)properties;
+ (nullable NSHTTPCookie *)cookieWithProperties:(NSDictionary<NSHTTPCookiePropertyKey, id> *)properties;

1.2.NSHTTPCookieStorage

NSHTTPCookieStorage實現(xiàn)了一個單例對象來管理共享的cookie存儲,客戶端可以通過這個對象來增加,刪除,獲取當(dāng)前的cookie,也可以解析和生成cookie相關(guān)的http頭字段。

- (void)setCookie:(NSHTTPCookie *)cookie;
- (void)deleteCookie:(NSHTTPCookie *)cookie;
- (nullable NSArray<NSHTTPCookie *> *)cookiesForURL:(NSURL *)URL;
@property NSHTTPCookieAcceptPolicy cookieAcceptPolicy;

2.NSURLAuthenticationChallenge

這個類代表一個鑒權(quán)查詢消息。
NSURLCredential代表一個鑒權(quán)憑證。

3.MIME

MIME (Multipurpose Internet Mail Extensions) 是描述消息內(nèi)容類型的因特網(wǎng)標(biāo)準(zhǔn)。
MIME 消息能包含文本、圖像、音頻、視頻以及其他應(yīng)用程序?qū)S玫臄?shù)據(jù)。
具體可以參考:MIME 參考手冊



提升代碼質(zhì)量最神圣的三部曲:模塊設(shè)計(謀定而后動) -->無錯編碼(知止而有得) -->開發(fā)自測(防患于未然)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容