來自何方
根據(jù)類的前綴可以得知,UIWebView是來自于UIKit框架茫蛹,WKWebView來自于WebKit操刀。所以,UIWebVieW和WKWebView的性能差距可想而知婴洼,后來我們會一一道來骨坑。
UIWebView 常用 API分析
-(void)loadRequest:(NSURLRequest *)request;
// 以NSURLRequest的方式加載(一般是NSURL的方式初始化request)。
-(void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;
// 把HTML轉換成string文本方式加載柬采。
-(void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;
// 以二進制流data的方式加載欢唾。
-(void)reload; //重新加載當前的界面
-(void)stopLoading; // 停止加載
-(void)goBack; // 導航條回退
-(void)goForward; // 導航條前進
// goBack 和 goForward 類似于UINavagationController 管理的控制器類型(UIViewController)的棧結構執(zhí)行的Push和Pop操作览芳。
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
//這個方法經常會用到嗤详,script是js腳本,通過script來調起js的方法碗殷。
以下是webView常用的屬性:
- @property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack; //是否能回退
- @property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward; // 是否可以前進
- @property (nonatomic, readonly, getter=isLoading) BOOL loading; // 是否在加載中
- @property (nonatomic) BOOL scalesPageToFit; // 是否界面是自適應的
- @property (nonatomic) UIDataDetectorTypes dataDetectorTypes NS_AVAILABLE_IOS(3_0); // 設置界面點擊后的類型檢測肩刃。
UIWebViewDelegate 代理
@protocol UIWebViewDelegate
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
//是否接受這個request的加載請求祟霍,返回布爾值押搪。在此代理中可以通過攔截URL請求,截取request的url的相關屬性值來調起native方法浅碾,native和js協(xié)定好字段的相關邏輯,如果需要調起native方法续语,則需要返回NO垂谢。
-(void)webViewDidStartLoad:(UIWebView *)webView;
//webView開始加載
-(void)webViewDidFinishLoad:(UIWebView *)webView;
//webView加載完畢
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;
//webView加載失敗
UIWebView native 和 js 的交互策略
- 最基本的交互方式
native 調起 js (有返回值):
-(nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
js調用native:
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
在這個代理方法中進行處理相關邏輯。
- iOS7 蘋果對外開放的JavaScriptCore提供的交互方式
JavaScriptCore是webkit的一個重要組成部分疮茄,極大的方便了我們對js的操作滥朱。iOS7以前我們對JS的操作只有webview里面一個函數(shù)stringByEvaluatingJavaScriptFromString,JS對OC的回調都是基于URL的攔截進行的操作力试。
我們要熟悉一下幾個概念:
JSContext
JS執(zhí)行的上下文環(huán)境徙邻,通過JSVirtualMachine管理著所有對象的生命周期,每個JSValue都和JSContext相關聯(lián)并且強引用context畸裳。
JSValue
每個JSValue都是強引用一個context缰犁,OC和JS對象之間的轉換也是通過它,相應的類型轉換如下:
<pre>
@textblock
Objective-C type | JavaScript type
--------------------+---------------------
nil | undefined
NSNull | null
NSString | string
NSNumber | number, boolean
NSDictionary | Object object
NSArray | Array object
NSDate | Date object
NSBlock (1) | Function object (1)
id (2) | Wrapper object (2)
Class (3) | Constructor object (3)
@/textblock
</pre>
JSManagedValue
由于JS內存管理是垃圾回收怖糊,并且JS中的對象都是強引用帅容,而OC是通過引用計數(shù),如果相互強引用的話伍伤,會造成內存泄漏問題并徘,所以用JSManagedValue保存JSValue來避免這種問題的發(fā)生。
JSVirtualMachine
JS運行的虛擬機扰魂,有獨立的堆空間和垃圾回收機制麦乞。
JSExport
一個協(xié)議,如果JS對象想調起native方法劝评,那么native需要自定義一個類來實現(xiàn)這個JSExport協(xié)議就可以了姐直。
介紹過相關基礎類后,來看看調用的例子蒋畜。
@protocol WebViewNativeBridgeDelegete <JSExport>
JSExportAs(add, -(int)add:(int)a other:(int)b);
@end
自定義一個 helper class 實現(xiàn)這個WebViewNativeBridgeDelegete 協(xié)議就可以了简肴。
native 實現(xiàn)這個代理方法
- (int) add: (int)a other: (int)b{
return a + b;
}
// js端在調起這個native方法的同時,可以同步拿到返回值百侧。
Note: 在webView加載完畢的時候砰识,要拿到js的上下文。
-(void)webViewDidFinishLoad:(UIWebView *)webView{
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
self.context[@"native"] = helper;
}
JSContext 通過evaluateScript 方法實現(xiàn)動態(tài)注入
self.context[@"add"] = ^(NSInteger a, NSInteger b) {
NSLog(@"sum = %@", @(a + b));
};
[self.context evaluateScript:@"add(2,3);"];
OC call JS
JSValue *value = [self.context[@"add"] callWithArguments:@[@1, @2]];
NSLog(@"value = %@", @([value toInt32]));
UIWebView內存暴增問題的探究
我簡單的寫了一個小界面佣渴,UIWebView加載百度的首頁辫狼,點擊幾個界面后,內存的增長圖如下:
如圖可以看出內存增長的很急辛润,曲線很陡峭膨处,最大達到117.4MB,并且當頁面切換后,內存居高不下真椿,內存無法釋放鹃答。特別如果在設備性能比較差的設備,比如iPhone4突硝,iPhone4s等测摔,很容易觸發(fā)看門狗機制。
為了防止一個應用占用過多的系統(tǒng)資源解恰,開發(fā)iOS的蘋果工程師門設計了一個“看門狗”的機制锋八。在不同的場景下,“看門狗”會監(jiān)測應用的性能护盈。如果超出了該場景所規(guī)定的運行時間挟纱,“看門狗”就會強制終結這個應用的進程。
初學者可能會想著在dealloc中腐宋,設置webView = nil, 可惜根本沒用紊服。
我也google了一下,很多的結果是清除cache胸竞,嘗試過围苫,有效果不過不是讓人滿意的答案,能清除部分的內存撤师,可以參考 這篇文章 剂府。
工欲善其事必先利其器,我們就用Instrument的allocations來檢測一下內存暴增的原因剃盾,很明顯這是系統(tǒng)庫的問題腺占,以圖為證。
下圖截取時間跟上圖不一致痒谴,并且在模擬器下操作的衰伯,僅作參考
很明顯50%以上的內存是開辟了一個WebThread(pthread),在這個線程中bitmap的位圖渲染积蔚。由于UIWebView基于UIKit機制意鲸,文本圖片等資源都是在QuartzCore下的context棧中管理,渲染尽爆,繪制怎顾,以及runloop的保持的常駐線程。
UIWebView的內存問題我也無能為力漱贱,可以優(yōu)化的是保持一個UIWebViewController槐雾,持有一個UIWebView屬性,不要在不同的控制器中頻繁的創(chuàng)建幅狮。同時在適當?shù)臅r候進行cache的清空募强,也可以實現(xiàn)NSURLProtocol株灸,進行URL的攔截,使用緩存的圖片資源擎值,避免重復請求慌烧。
不過,我個人推薦使用iOS8引入的WKWebView鸠儿,不過你要想兼容iOS7的話屹蚊,可以做一個兼容性版本,F(xiàn)acebook也引入了捆交,基于WebKit內核的WKWebView,性能會有很大的提升腐巢。UIWebView的內容就寫到這里品追,后續(xù)我會寫下WKWebView。
如果大家對我的理解有什么異議或者有更深的看法肉瓦,歡迎一起交流,謝謝大家胃惜!