李洪强iOS经典面试题147-WebView与JS交互

 

WebView与JS交互

iOS中调用HTML

 1. 加载网页
NSURL *url = [[NSBundle mainBundle] URLForResource:@"index" withExtension:@"html"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:request]; 2. 删除
NSString *str1 = @"var word = document.getElementById('word');";
NSString *str2 = @"word.remove();"; [webView stringByEvaluatingJavaScriptFromString:str1];
[webView stringByEvaluatingJavaScriptFromString:str2]; 3. 更改
NSString *str3 = @"var change = document.getElementsByClassName('change')[0];"
"change.innerHTML = '好你的哦!';";
[webView stringByEvaluatingJavaScriptFromString:str3]; 4. 插入
NSString *str4 =@"var img = document.createElement('img');"
"img.src = 'img_01.jpg';"
"img.width = '160';"
"img.height = '80';"
"document.body.appendChild(img);";
[webView stringByEvaluatingJavaScriptFromString:str4]; 5. 改变标题
NSString *str1 = @"var h1 = document.getElementsByTagName('h1')[0];"
"h1.innerHTML='简书啊啊啊啊';";
[webView stringByEvaluatingJavaScriptFromString:str1]; 6. 删除尾部
NSString *str2 =@"document.getElementById('footer').remove();";
[webView stringByEvaluatingJavaScriptFromString:str2]; 7. 拿出所有的网页内容
NSString *str3 = @"document.body.outerHTML";
NSString *html = [webView stringByEvaluatingJavaScriptFromString:str3];
NSLog(@"%@", html);

在HTML中调用OC

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
NSString *str = request.URL.absoluteString;
NSRange range = [str rangeOfString:@"ZJY://"];
if (range.location != NSNotFound) {
NSString *method = [str substringFromIndex:range.location + range.length];
SEL sel = NSSelectorFromString(method);
[self performSelector:sel];
}
return YES;
} // 访问相册
- (void)getImage{
UIImagePickerController *pickerImg = [[UIImagePickerController alloc]init];
pickerImg.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; [self presentViewController:pickerImg animated:YES completion:nil];
}

JavaScriptCore 使用

  • JavaScriptCore是webkit的一个重要组成部分,主要是对JS进行解析和提供执行环境。iOS7后苹果在iPhone平台推出,极大的方便了我们对js的操作。我们可以脱离webview直接运行我们的js。iOS7以前我们对JS的操作只有webview里面一个函数stringByEvaluatingJavaScriptFromString,JS对OC的回调都是基于URL的拦截进行的操作。大家用得比较多的是WebViewJavascriptBridge和EasyJSWebView这两个开源库,很多混合都采用的这种方式。
#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
  • JSContext:JS执行的环境,同时也通过JSVirtualMachine管理着所有对象的生命周期,每个JSValue都和JSContext相关联并且强引用context。

  • JSValue:JS对象在JSVirtualMachine中的一个强引用,其实就是Hybird对象。我们对JS的操作都是通过它。并且每个JSValue都是强引用一个context。同时,OC和JS对象之间的转换也是通过它,相应的类型转换如下:

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)
  • JSManagedValue:JS和OC对象的内存管理辅助对象。由于JS内存管理是垃圾回收,并且JS中的对象都是强引用,而OC是引用计数。如果双方相互引用,势必会造成循环引用,而导致内存泄露。我们可以用JSManagedValue保存JSValue来避免。

  • JSVirtualMachine:JS运行的虚拟机,有独立的堆空间和垃圾回收机制。

  • JSExport:一个协议,如果JS对象想直接调用OC对象里面的方法和属性,那么这个OC对象只要实现这个JSExport协议就可以了。

  • Objective-C -> JavaScript

    self.context = [[JSContext alloc] init];

    NSString *js = @"function add(a,b) {return a+b}";

    [self.context evaluateScript:js];

    JSValue *n = [self.context[@"add"] callWithArguments:@[@2, @3]];

    NSLog(@"---%@", @([n toInt32]));//---5
  • JavaScript -> Objective-C.JS调用OC有两个方法:block和JSExport protocol。
  • block(JS function):
    self.context = [[JSContext alloc] init];

    self.context[@"add"] = ^(NSInteger a, NSInteger b) {
NSLog(@"---%@", @(a + b));
}; [self.context evaluateScript:@"add(2,3)"];
我们定义一个block,然后保存到context里面,其实就是转换成了JS的function。然后我们直接执行这个function,调用的就是我们的block里面的内容了。
  • JSExport protocol:
//定义一个JSExport protocol
@protocol JSExportTest <JSExport> - (NSInteger)add:(NSInteger)a b:(NSInteger)b; @property (nonatomic, assign) NSInteger sum; @end //建一个对象去实现这个协议: @interface JSProtocolObj : NSObject<JSExportTest>
@end @implementation JSProtocolObj
@synthesize sum = _sum;
//实现协议方法
- (NSInteger)add:(NSInteger)a b:(NSInteger)b
{
return a+b;
}
//重写setter方法方便打印信息,
- (void)setSum:(NSInteger)sum
{
NSLog(@"--%@", @(sum));
_sum = sum;
} @end //在VC中进行测试
@interface ViewController () <JSExportTest> @property (nonatomic, strong) JSProtocolObj *obj;
@property (nonatomic, strong) JSContext *context; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
//创建context
self.context = [[JSContext alloc] init];
//设置异常处理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
//将obj添加到context中
self.context[@"OCObj"] = self.obj;
//JS里面调用Obj方法,并将结果赋值给Obj的sum属性
[self.context evaluateScript:@"OCObj.sum = OCObj.addB(2,3)"]; } demo很简单,还是定义了一个两个数相加的方法,还有一个保存结果的变量。在JS中进行调用这个对象的方法,并将结果赋值sum。唯一要注意的是OC的函数命名和JS函数命名规则问题。协议中定义的是add: b:,但是JS里面方法名字是addB(a,b)。可以通过JSExportAs这个宏转换成JS的函数名字。
  • 内存管理:现在来说说内存管理的注意点,OC使用的ARC,JS使用的是垃圾回收机制,并且所有的引用是都强引用,不过JS的循环引用,垃圾回收会帮它们打破。JavaScriptCore里面提供的API,正常情况下,OC和JS对象之间内存管理都无需我们去关心。不过还是有几个注意点需要我们去留意下。
1、不要在block里面直接使用context,或者使用外部的JSValue对象。

//错误代码:
self.context[@"block"] = ^(){
JSValue *value = [JSValue valueWithObject:@"aaa" inContext:self.context];
};
这个代码,不用自己看了,编译器都会提示你的。这个block里面使用self,很容易就看出来了。 //一个比较隐蔽的
JSValue *value = [JSValue valueWithObject:@"ssss" inContext:self.context]; self.context[@"log"] = ^(){
NSLog(@"%@",value);
};
这个是block里面使用了外部的value,value对context和它管理的JS对象都是强引用。这个value被block所捕获,这边同样也会内存泄露,context是销毁不掉的。 //正确的做法,str对象是JS那边传递过来。
self.context[@"log"] = ^(NSString *str){
NSLog(@"%@",str);
};
2、OC对象不要用属性直接保存JSValue对象,因为这样太容易循环引用了。 看个demo,把上面的示例改下: //定义一个JSExport protocol
@protocol JSExportTest <JSExport>
//用来保存JS的对象
@property (nonatomic, strong) JSvalue *jsValue; @end //建一个对象去实现这个协议: @interface JSProtocolObj : NSObject<JSExportTest>
@end @implementation JSProtocolObj @synthesize jsValue = _jsValue; @end //在VC中进行测试
@interface ViewController () <JSExportTest> @property (nonatomic, strong) JSProtocolObj *obj;
@property (nonatomic, strong) JSContext *context; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
//创建context
self.context = [[JSContext alloc] init];
//设置异常处理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
//加载JS代码到context中
[self.context evaluateScript:
@"function callback (){}; function setObj(obj) {
this.obj = obj;
obj.jsValue=callback;
}"];
//调用JS方法
[self.context[@"setObj"] callWithArguments:@[self.obj]];
} 上面的例子很简单,调用JS方法,进行赋值,JS对象保留了传进来的obj,最后,JS将自己的回调callback赋值给了obj,方便obj下次回调给JS;由于JS那边保存了obj,而且obj这边也保留了JS的回调。这样就形成了循环引用。 怎么解决这个问题?我们只需要打破obj对JSValue对象的引用即可。当然,不是我们OC中的weak。而是之前说的内存管理辅助对象JSManagedValue。 JSManagedValue 本身就是我们需要的弱引用。用官方的话来说叫garbage collection weak reference。但是它帮助我们持有JSValue对象必须同时满足一下两个条件(不翻译了,翻译了怪怪的!): The JSManagedValue's JavaScript value is reachable from JavaScript The owner of the managed reference is reachable in Objective-C. Manually adding or removing the managed reference in the JSVirtualMachine determines reachability. 意思很简单,JSManagedValue 帮助我们保存JSValue,那里面保存的JS对象必须在JS中存在,同时 JSManagedValue 的owner在OC中也存在。我们可以通过它提供的两个方法``` + (JSManagedValue )managedValueWithValue:(JSValue )value; (JSManagedValue )managedValueWithValue:(JSValue )value andOwner:(id)owner创建JSManagedValue对象。通过JSVirtualMachine的方法- (void)addManagedReference:(id)object withOwner:(id)owner来建立这个弱引用关系。通过- (void)removeManagedReference:(id)object withOwner:(id)owner``` 来手动移除他们之间的联系。 把刚刚的代码改下: //定义一个JSExport protocol
@protocol JSExportTest <JSExport>
//用来保存JS的对象
@property (nonatomic, strong) JSValue *jsValue; @end //建一个对象去实现这个协议: @interface JSProtocolObj : NSObject<JSExportTest>
//添加一个JSManagedValue用来保存JSValue
@property (nonatomic, strong) JSManagedValue *managedValue; @end @implementation JSProtocolObj @synthesize jsValue = _jsValue;
//重写setter方法
- (void)setJsValue:(JSValue *)jsValue
{
_managedValue = [JSManagedValue managedValueWithValue:jsValue]; [[[JSContext currentContext] virtualMachine] addManagedReference:_managedValue
withOwner:self];
} @end //在VC中进行测试
@interface ViewController () <JSExportTest> @property (nonatomic, strong) JSProtocolObj *obj;
@property (nonatomic, strong) JSContext *context; @end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
//创建context
self.context = [[JSContext alloc] init];
//设置异常处理
self.context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
[JSContext currentContext].exception = exception;
NSLog(@"exception:%@",exception);
};
//加载JS代码到context中
[self.context evaluateScript:
@"function callback (){}; function setObj(obj) {
this.obj = obj;
obj.jsValue=callback;
}"];
//调用JS方法
[self.context[@"setObj"] callWithArguments:@[self.obj]];
} 注:以上代码只是为了突出用 JSManagedValue来保存 JSValue,所以重写了 setter 方法。实际不会写这么搓的姿势。。。应该根据回调方法传进来参数,进行保存 JSValue。 3、不要在不同的 JSVirtualMachine 之间进行传递JS对象。 一个 JSVirtualMachine可以运行多个context,由于都是在同一个堆内存和同一个垃圾回收下,所以相互之间传值是没问题的。但是如果在不同的 JSVirtualMachine传值,垃圾回收就不知道他们之间的关系了,可能会引起异常。
  • 线程:JavaScriptCore 线程是安全的,每个context运行的时候通过lock关联的JSVirtualMachine。如果要进行并发操作,可以创建多个JSVirtualMachine实例进行操作。

  • 与UIWebView的操作

通过上面的demo,应该差不多了解OC如何和JS进行通信。下面我们看看如何对 UIWebView 进行操作,我们不再通过URL拦截,我们直接取 UIWebView 的 context,然后进行对JS操作。

在UIWebView的finish的回调中进行获取

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
self.context = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; } 上面用了私有属性,可能会被苹果给拒了。这边要注意的是每个页面加载完都是一个新的context,但是都是同一个JSVirtualMachine。如果JS调用OC方法进行操作UI的时候,请注意线程是不是主线程。

文章如有问题,请留言,我将及时更正。

李洪强iOS经典面试题147-WebView与JS交互的更多相关文章

  1. 李洪强iOS经典面试题156 - Runtime详解(面试必备)

    李洪强iOS经典面试题156 - Runtime详解(面试必备)   一.runtime简介 RunTime简称运行时.OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的是消息机制. 对于C ...

  2. 李洪强iOS经典面试题155 - const,static,extern详解(面试必备)

    李洪强iOS经典面试题155 - const,static,extern详解(面试必备) 一.const与宏的区别(面试题): const简介:之前常用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽 ...

  3. 李洪强iOS经典面试题154- 通知与推送

    李洪强iOS经典面试题154- 通知与推送   通知与推送 本地通知和远程推送通知对基本概念和用法? image 本地通知和远程推送通知都可以向不在前台运行的应用发送消息,这种消息既可能是即将发生的事 ...

  4. 李洪强iOS经典面试题153- 补充

    李洪强iOS经典面试题153- 补充   补充 有空就来解决几个问题,已经懒癌晚期没救了... UML 统一建模语言(UML,UnifiedModelingLanguage)是面向对象软件的标准化建模 ...

  5. 李洪强iOS经典面试题144-数据存储

    李洪强iOS经典面试题144-数据存储   数据存储 sqlite中插入特殊字符的方法和接收到处理方法. 除'其他的都是在特殊字符前面加"/",而 ' -> '' .方法:k ...

  6. 李洪强iOS经典面试题143-绘图与动画

    李洪强iOS经典面试题143-绘图与动画   绘图与动画 CAAnimation的层级结构 CAPropertyAnimation是CAAnimation的子类,也是个抽象类,要想创建动画对象,应该使 ...

  7. 李洪强iOS经典面试题142-第三方框架及其管理

    李洪强iOS经典面试题142-第三方框架及其管理   第三方框架及其管理 使用过CocoaPods吗?它是什么?CocoaPods的原理? CocoaPod是一个第三方库的管理工具,用来管理项目中的第 ...

  8. 李洪强iOS经典面试题141-报错警告调试

    李洪强iOS经典面试题141-报错警告调试   报错警告调试 你在实际开发中,有哪些手机架构与性能调试经验 刚接手公司的旧项目时,模块特别多,而且几乎所有的代码都写在控制器里面,比如UI控件代码.网络 ...

  9. 李洪强iOS经典面试题140-UI

    李洪强iOS经典面试题140-UI   UI viewcontroller的一些方法的说明viewDidLoad,viewWillDisappear, viewWillAppear方法的 顺序和作用? ...

随机推荐

  1. 关于hg的命令

    整理的创建分支合并一个分支的代码步骤:1.创建一个目录用于作为本地仓库mkdir Center2.将远端代码克隆到本地仓库(这时我的位置在刚创建的Center目录下)hg clone HTTP DIR ...

  2. 大熊君{{bb}}------春节期间你跳槽了吗?

    时间过的很快,转眼间又快过春节了,推荐你在春节期间跳槽,是基于以下几个原因: 1,命中率高 通常情况下,所有公司都会在年底进行一定幅度的裁员,而惟独这家公司在招工,这等于明摆着告诉公众他们现在面临严重 ...

  3. 精选9个值得学习的 HTML5 效果【附源码】

    这里精选了一组很酷的 HTML5 效果.HTML5 是现 Web 开发领域的热点, 拥有很多让人期待已久的新特性,特别是在移动端,Web 开发人员可以借助 HTML5 强大功能轻松制作各种交互性强.效 ...

  4. LNMP环境搭建笔记

    说明:前面尝试的在ubuntu12.04上搭建的LAMP环境由于开发的需要需要对php的版本进行升级,然而通过apt-get库安装的php的版本是5.3.10,不能满足开发需要.此笔记安装的php的 ...

  5. iOS开发——高级篇——iOS中常见的设计模式(MVC/单例/委托/观察者)

    关于设计模式这个问题,在网上也找过一些资料,下面是我自己总结的,分享给大家 如果你刚接触设计模式,我们有好消息告诉你!首先,多亏了Cocoa的构建方式,你已经使用了许多的设计模式以及被鼓励的最佳实践. ...

  6. javascript基础02

    javascript基础02 1.数据类型 数据类型的描述在上篇的扩展中有写到链接 由于ECMAScript数据类型具有动态性,因此的确没有再定义其他数据类型的必要.这句话很重要. 如果以后再数据类型 ...

  7. 数组去重及数组的prototype原型

    Array.prototype.check= function(){ for(var i=0;i<this.length;i++){ for(var j=i+1;j<this.length ...

  8. 开源实时日志分析ELK平台部署

    参考帖子: (1)自动化测试Web服务器性能autobench+httperf

  9. C# Mvc中文件下载

    public ActionResult DownloadFile(string id) { var fileinfo = CommonAnnexService.Get(id); if (fileinf ...

  10. linux编程问题记录

    1.程序中需要用到字符串的时候,尽可能选择string类型,这种类型的字符串有很多比较容易的功能,如字符串之间可以直接拷贝赋值 string a; string b="123"; ...