UIWebView的那些事

來(lái)自何方

根據(jù)類的前綴可以得知,UIWebView是來(lái)自于UIKit框架,WKWebView來(lái)自于WebKit。所以,UIWebVieW和WKWebView的性能差距可想而知,后來(lái)我們會(huì)一一道來(lái)。

UIWebView 常用 API分析

-(void)loadRequest:(NSURLRequest *)request;
// 以NSURLRequest的方式加載(一般是NSURL的方式初始化request)。

-(void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;

// 把HTML轉(zhuǎn)換成string文本方式加載。

-(void)loadData:(NSData *)data MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName baseURL:(NSURL *)baseURL;

// 以二進(jìn)制流data的方式加載。

-(void)reload;             //重新加載當(dāng)前的界面

-(void)stopLoading;       // 停止加載

-(void)goBack;           // 導(dǎo)航條回退

-(void)goForward;        // 導(dǎo)航條前進(jìn)

// goBack 和 goForward 類似于UINavagationController 管理的控制器類型(UIViewController)的棧結(jié)構(gòu)執(zhí)行的Push和Pop操作。

- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

//這個(gè)方法經(jīng)常會(huì)用到,script是js腳本,通過(guò)script來(lái)調(diào)起js的方法。

以下是webView常用的屬性:

- @property (nonatomic, readonly, getter=canGoBack) BOOL canGoBack;   //是否能回退
- @property (nonatomic, readonly, getter=canGoForward) BOOL canGoForward;  // 是否可以前進(jìn)
- @property (nonatomic, readonly, getter=isLoading) BOOL loading;  // 是否在加載中
- @property (nonatomic) BOOL scalesPageToFit;   // 是否界面是自適應(yīng)的
- @property (nonatomic) UIDataDetectorTypes dataDetectorTypes NS_AVAILABLE_IOS(3_0); // 設(shè)置界面點(diǎn)擊后的類型檢測(cè)。

UIWebViewDelegate 代理

@protocol UIWebViewDelegate

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

//是否接受這個(gè)request的加載請(qǐng)求,返回布爾值。在此代理中可以通過(guò)攔截URL請(qǐng)求,截取request的url的相關(guān)屬性值來(lái)調(diào)起native方法,native和js協(xié)定好字段的相關(guān)邏輯,如果需要調(diào)起native方法,則需要返回NO。

-(void)webViewDidStartLoad:(UIWebView *)webView;

//webView開(kāi)始加載

-(void)webViewDidFinishLoad:(UIWebView *)webView;

//webView加載完畢

-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error;

//webView加載失敗

UIWebView native 和 js 的交互策略

  • 最基本的交互方式

native 調(diào)起 js (有返回值):

-(nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

js調(diào)用native:

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

在這個(gè)代理方法中進(jìn)行處理相關(guān)邏輯。

  • iOS7 蘋果對(duì)外開(kāi)放的JavaScriptCore提供的交互方式

JavaScriptCore是webkit的一個(gè)重要組成部分,極大的方便了我們對(duì)js的操作。iOS7以前我們對(duì)JS的操作只有webview里面一個(gè)函數(shù)stringByEvaluatingJavaScriptFromString,JS對(duì)OC的回調(diào)都是基于URL的攔截進(jìn)行的操作。

我們要熟悉一下幾個(gè)概念:

JSContext

JS執(zhí)行的上下文環(huán)境,通過(guò)JSVirtualMachine管理著所有對(duì)象的生命周期,每個(gè)JSValue都和JSContext相關(guān)聯(lián)并且強(qiáng)引用context。

JSValue

每個(gè)JSValue都是強(qiáng)引用一個(gè)context,OC和JS對(duì)象之間的轉(zhuǎn)換也是通過(guò)它,相應(yīng)的類型轉(zhuǎn)換如下:
<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內(nèi)存管理是垃圾回收,并且JS中的對(duì)象都是強(qiáng)引用,而OC是通過(guò)引用計(jì)數(shù),如果相互強(qiáng)引用的話,會(huì)造成內(nèi)存泄漏問(wèn)題,所以用JSManagedValue保存JSValue來(lái)避免這種問(wèn)題的發(fā)生。

JSVirtualMachine

JS運(yùn)行的虛擬機(jī),有獨(dú)立的堆空間和垃圾回收機(jī)制。

JSExport

一個(gè)協(xié)議,如果JS對(duì)象想調(diào)起native方法,那么native需要自定義一個(gè)類來(lái)實(shí)現(xiàn)這個(gè)JSExport協(xié)議就可以了。

介紹過(guò)相關(guān)基礎(chǔ)類后,來(lái)看看調(diào)用的例子。

@protocol WebViewNativeBridgeDelegete <JSExport>

JSExportAs(add, -(int)add:(int)a other:(int)b);

@end

自定義一個(gè) helper class 實(shí)現(xiàn)這個(gè)WebViewNativeBridgeDelegete 協(xié)議就可以了。

native 實(shí)現(xiàn)這個(gè)代理方法

- (int) add: (int)a other: (int)b{
       return a + b;
}  
// js端在調(diào)起這個(gè)native方法的同時(shí),可以同步拿到返回值。

Note: 在webView加載完畢的時(shí)候,要拿到j(luò)s的上下文。

-(void)webViewDidFinishLoad:(UIWebView *)webView{
   self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
   self.context[@"native"] = helper;
}

JSContext 通過(guò)evaluateScript 方法實(shí)現(xiàn)動(dòng)態(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內(nèi)存暴增問(wèn)題的探究

我簡(jiǎn)單的寫了一個(gè)小界面,UIWebView加載百度的首頁(yè),點(diǎn)擊幾個(gè)界面后,內(nèi)存的增長(zhǎng)圖如下:

memoryGraph.png

如圖可以看出內(nèi)存增長(zhǎng)的很急,曲線很陡峭,最大達(dá)到117.4MB,并且當(dāng)頁(yè)面切換后,內(nèi)存居高不下,內(nèi)存無(wú)法釋放。特別如果在設(shè)備性能比較差的設(shè)備,比如iPhone4,iPhone4s等,很容易觸發(fā)看門狗機(jī)制。

為了防止一個(gè)應(yīng)用占用過(guò)多的系統(tǒng)資源,開(kāi)發(fā)iOS的蘋果工程師門設(shè)計(jì)了一個(gè)“看門狗”的機(jī)制。在不同的場(chǎng)景下,“看門狗”會(huì)監(jiān)測(cè)應(yīng)用的性能。如果超出了該場(chǎng)景所規(guī)定的運(yùn)行時(shí)間,“看門狗”就會(huì)強(qiáng)制終結(jié)這個(gè)應(yīng)用的進(jìn)程。

初學(xué)者可能會(huì)想著在dealloc中,設(shè)置webView = nil, 可惜根本沒(méi)用。
我也google了一下,很多的結(jié)果是清除cache,嘗試過(guò),有效果不過(guò)不是讓人滿意的答案,能清除部分的內(nèi)存,可以參考 這篇文章

工欲善其事必先利其器,我們就用Instrument的allocations來(lái)檢測(cè)一下內(nèi)存暴增的原因,很明顯這是系統(tǒng)庫(kù)的問(wèn)題,以圖為證。

allocations.png
majorMemory.png

下圖截取時(shí)間跟上圖不一致,并且在模擬器下操作的,僅作參考

很明顯50%以上的內(nèi)存是開(kāi)辟了一個(gè)WebThread(pthread),在這個(gè)線程中bitmap的位圖渲染。由于UIWebView基于UIKit機(jī)制,文本圖片等資源都是在QuartzCore下的context棧中管理,渲染,繪制,以及runloop的保持的常駐線程。

UIWebView的內(nèi)存問(wèn)題我也無(wú)能為力,可以優(yōu)化的是保持一個(gè)UIWebViewController,持有一個(gè)UIWebView屬性,不要在不同的控制器中頻繁的創(chuàng)建。同時(shí)在適當(dāng)?shù)臅r(shí)候進(jìn)行cache的清空,也可以實(shí)現(xiàn)NSURLProtocol,進(jìn)行URL的攔截,使用緩存的圖片資源,避免重復(fù)請(qǐng)求。

不過(guò),我個(gè)人推薦使用iOS8引入的WKWebView,不過(guò)你要想兼容iOS7的話,可以做一個(gè)兼容性版本,F(xiàn)acebook也引入了,基于WebKit內(nèi)核的WKWebView,性能會(huì)有很大的提升。UIWebView的內(nèi)容就寫到這里,后續(xù)我會(huì)寫下WKWebView。

如果大家對(duì)我的理解有什么異議或者有更深的看法,歡迎一起交流,謝謝大家!

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

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

  • 前言 Web 頁(yè)面中的 JS 與 iOS Native 如何交互是每個(gè) iOS 猿必須掌握的技能。而說(shuō)到 Nati...
    幽城88閱讀 2,322評(píng)論 1 8
  • 隨著H5技術(shù)的興起,在iOS開(kāi)發(fā)過(guò)程中,難免會(huì)遇到原生應(yīng)用需要和H5頁(yè)面交互的問(wèn)題。其中會(huì)涉及方法調(diào)用及參數(shù)傳值等...
    Chris_js閱讀 3,234評(píng)論 1 8
  • 本博客主要分以下幾個(gè)方面來(lái)介紹iOS中的JavaScriptCore JavaScriptCore簡(jiǎn)介 JavaS...
    dullgrass閱讀 4,408評(píng)論 1 38
  • JavaScriptCore框架主要是用來(lái)實(shí)現(xiàn)iOS與H5的交互。由于現(xiàn)在混合編程越來(lái)越多,H5的相對(duì)講多,所以研...
    水靈芳蕥閱讀 1,491評(píng)論 1 8
  • 本文由我們團(tuán)隊(duì)的 糾結(jié)倫 童鞋撰寫。 寫在前面 本篇文章是對(duì)我一次組內(nèi)分享的整理,大部分圖片都是直接從keynot...
    知識(shí)小集閱讀 15,382評(píng)論 11 172

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