来自何方
根据类的前缀可以得知,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)的栈结构执行的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协定好字段的相关逻辑,如果需要调起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里面一个函数stringByEvaluatingJavaScriptFromString,JS对OC的回调都是基于URL的拦截进行的操作。
我们要熟悉一下几个概念:
JSContext
JS执行的上下文环境,通过JSVirtualMachine管理着所有对象的生命周期,每个JSValue都和JSContext相关联并且强引用context。
JSValue
每个JSValue都是强引用一个context,OC和JS对象之间的转换也是通过它,相应的类型转换如下:
@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
JSManagedValue
由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是通过引用计数,如果相互强引用的话,会造成内存泄漏问题,所以用JSManagedValue保存JSValue来避免这种问题的发生。
JSVirtualMachine
JS运行的虚拟机,有独立的堆空间和垃圾回收机制。
JSExport
一个协议,如果JS对象想调起native方法,那么native需要自定义一个类来实现这个JSExport协议就可以了。
介绍过相关基础类后,来看看调用的例子。
@protocol WebViewNativeBridgeDelegete
JSExportAs(add, -(int)add:(int)a other:(int)b);
@end
自定义一个 helper class 实现这个WebViewNativeBridgeDelegete 协议就可以了。
native 实现这个代理方法
- (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 方法实现动态注入
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等,很容易触发看门狗机制。
为了防止一个应用占用过多的系统资源,开发iOS的苹果工程师门设计了一个“看门狗”的机制。在不同的场景下,“看门狗”会监测应用的性能。如果超出了该场景所规定的运行时间,“看门狗”就会强制终结这个应用的进程。
初学者可能会想着在dealloc中,设置webView = nil, 可惜根本没用。
我也google了一下,很多的结果是清除cache,尝试过,有效果不过不是让人满意的答案,能清除部分的内存,可以参考 这篇文章 。
工欲善其事必先利其器,我们就用Instrument的allocations来检测一下内存暴增的原因,很明显这是系统库的问题,以图为证。
下图截取时间跟上图不一致,并且在模拟器下操作的,仅作参考
很明显50%以上的内存是开辟了一个WebThread(pthread),在这个线程中bitmap的位图渲染。由于UIWebView基于UIKit机制,文本图片等资源都是在QuartzCore下的context栈中管理,渲染,绘制,以及runloop的保持的常驻线程。
UIWebView的内存问题我也无能为力,可以优化的是保持一个UIWebViewController,持有一个UIWebView属性,不要在不同的控制器中频繁的创建。同时在适当的时候进行cache的清空,也可以实现NSURLProtocol,进行URL的拦截,使用缓存的图片资源,避免重复请求。
不过,我个人推荐使用iOS8引入的WKWebView,不过你要想兼容iOS7的话,可以做一个兼容性版本,Facebook也引入了,基于WebKit内核的WKWebView,性能会有很大的提升。UIWebView的内容就写到这里,后续我会写下WKWebView。
如果大家对我的理解有什么异议或者有更深的看法,欢迎一起交流,谢谢大家!