CC
為了減輕客戶端同學的開發(fā)壓力,大家討論一致決定,把一些界面性質(zhì)的東西扔給web來做,客戶端提供窗口容器(猴開心)。然后用WKWebView,遇到好多坑,這里拿出最大的一個坑來講。
WKWebView web調(diào)用native
初始方案
客戶端在下面這個回調(diào)方法中,截獲web以特定的schema開頭的url。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
WKWebView中的web執(zhí)行以下方法來觸發(fā):
document.location = "xxx://{methodName:sendRequest}"
然后發(fā)現(xiàn)有坑,坑就是同時調(diào)用兩個后面那個可能會覆蓋前面那個。
其次方案
客戶端在下面這個回調(diào)方法中,截獲web以特定的字符串開頭的alert字符串。
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler;
WKWebView中的web執(zhí)行以下方法來觸發(fā):
alert("xxx://{methodName:sendRequest}");
然后發(fā)現(xiàn)有坑,坑就是iOS 8的WKWebView不支持加載本地的web網(wǎng)頁,iOS 9才開始支持,而且必須用下面方法才行:
- (nullable WKNavigation *)loadFileURL:(NSURL *)URL allowingReadAccessToURL:(NSURL *)readAccessURL API_AVAILABLE(macosx(10.11), ios(9.0));
所以iOS 8的本地加載必須用UIWebView,而UIWebView必須寫個Catagory來觸發(fā)私有方法。怕被拒,不敢用。
其實如果不是iOS 8本地加載業(yè)務(wù)場景的限制,這個方案應(yīng)該是可行的,但是web在alert的時候,整體是阻塞狀態(tài),沒有驗證是否會有其他的坑。
最終方案
感謝唐巧大神老早12年的博客,業(yè)界已有成熟方案,結(jié)果自己又踩了一遍坑(我天,17年了好么!)
http://blog.devtang.com/2012/03/24/talk-about-uiwebview-and-phonegap/
最終還是在下面這個方法里做的url截獲:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler;
WKWebView native調(diào)用web
官方提供的方法:
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
因為這個方法是異步的,有時候你想從web那邊同步拿個數(shù)據(jù),都很蛋疼。所以自己實現(xiàn)了個同步方法:
- (NSString *)syncExecScript:(NSString *)script
{
? ? __block BOOL finished = NO;
? ? __block NSString *content = nil;
? ? [self.webview evaluateJavaScript:script completionHandler:^(id _Nullable result, NSError * _Nullable error) {
? ? ? ? content = result;
? ? ? ? finished = YES;
? ? }];
? ? while (!finished)
? ? {
? ? ? ? [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
? ? }
? ? return content;
}
然后就撞到坑了
坑是這樣的,web調(diào)了客戶端一個分享的方法,然后在這個方法里呢,客戶端要調(diào)用web的方法取得要分享的數(shù)據(jù)。于是就實現(xiàn)了類似以下的調(diào)用:
- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler
{
? ? decisionHandler(WKNavigationActionPolicyCancel);
? ? NSString *url = [self syncExecScript];
? ?****
? ? return;
}
然后就死掉了!我天,卡死。
然后,跟寫web的達叔一起查了下這個問題,得到答案:
我以為,執(zhí)行了decisionHandler(WKNavigationActionPolicyCancel); web的事情就完事了。
然鵝,達叔說JS是單線程的。
所以,JS的單線程出發(fā)了WKWebView的截獲事件,一直等著WKWebView來告訴他要不要跳轉(zhuǎn)。然后iOS的這個線程,又調(diào)用了JS的一個方法,拿了數(shù)據(jù)我這個線程才會去做其他事情。于是你等著我,我等著你,就死鎖了。
這是達叔給出的解釋,perfect,沒瑕疵。這也就說明了,decisionHandler(WKNavigationActionPolicyCancel);在return之后才生效的。
解決了這個坑,完美。睡覺去了。