JS&iOS原生交互
2016-04-28 10:27
501 查看
关于原生和hybid之争,这里不做探讨.主要讲讲JS和OC交互
开讲前附上一个牛逼的第三方
JavascriptBridge
这个方法是UIWebView里面的方法,也是最为简单的和JS交互的方式
用法比较简单,一般在代理方法
这个是iOS7以后引进的,使用起来可简单,也可以比较复杂.
熟悉一下里面常见得几个对象及协议
对象简介
简单方式:直接调用JS代码
快速调用Block,可传参数
各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。虽然JavaScritpCore没有自带(毕竟不是在网页上运行的,自然不会有window、document、console这些类了),仍然可以定义一个Block方法来调用NSLog来模拟:
通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,JSContext提供了类方法来获取参数列表
,输出的内容是GlobalObject,这也是JSContext对象方法
所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是window,在JavaScriptCore是什么就不得而知了。Block可以传入JSContext作方法,但是JSValue没有toBlock方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是JSValue提供了
方法可以反过来将参数传进去来调用方法。
JSValue
还提供
让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在JSContext的globalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的JSValue
对象调用。
异常处理Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext
对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext
对象设置exceptionHandler,它接受的是^(JSContext context, JSValue exceptionValue)
形式的Block。其默认值就是将传入的exceptionValue赋给传入的context的exception属性:
我们也可以给exceptionHandler
赋予新的Block以便在JavaScript运行发生异常的时候我们可以立即知道:
使用Block的注意事项
从之前的例子和介绍应该有体会到Block在JavaScriptCore中起到的强大作用,它在JavaScript和Objective-C之间的转换 建立起更多的桥梁,让互通更方便。但是要注意的是无论是把Block传给JSContext对象让其变成JavaScript方法,还是把它赋给exceptionHandler属性,在Block内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+ (JSContext
)currentContext;来获得。否则会造成循环引用使得内存无法被正确释放。比如上边自定义异常处理方法,就是赋给传入JSContext对象con,而不是其外创建的context对象,虽然它们其实是同一个对象。这是因为Block会对内部使用的在外部定义创建的对象做强引用,而JSContext也会对被赋予的Block做强引用,这样它们之间就形成了循环引用使得内存无法正常释放。对于JSValue也不能直接从外部引用到Block中,因为每个JSValue上都有JSContext
的引用 (@property(readonly, retain) JSContext context;),JSContext再引用Block同样也会形成引用循环。
前面十分的简单方便而且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。
为了方便起见,以下所有代码中的JSContext对象都会添加如下的log
方法和eventHandler
:
复杂但强大的方式:通过协议,模型实现(事先和前端协商格式)
首先,我们需要先定义一个协议,而且这个协议必须要遵守JSExport协议。
注意js中调用oc方法时参数的写法
为了在调用原生方法之后调用对应的JS方法,写两个JS方法:
接下来,我们还需要定义一个模型,实现了上面定义协议里面的方法:
实现这个模型:
接下来,我们在controller中在webview加载完成的代理中,给JS注入模型。
注意,获取webview的jsContext的方法
通过KVC取得context,其路径为documentView.webView.mainFrame.javaScriptContext。这样就可以获取到JS的context,然后为这个context注入我们的模型对象。
这里我们定义了两个JS方法,一个是jsFunc,不带参数。
另一个是jsParamFunc,带一个参数。
接下来,我们在html中的body中添加以下代码:
现在就可以测试代码了。
当我们点击第一个按钮:Call ObjC system camera时,
通过OCModel.callSystemCamera(),就可以在HTML中通过JS调用OC的方法。
在OC代码中,我们的callSystemCamera方法体中,添加了以下两行代码,就是获取HTML中所定义的JS就去jsFunc,然后调用它。
这样就可以在JS调用OC方法时,也让OC反馈给JS。
看看下面传字典参数:
获取我们在HTML中定义的jsParamFunc方法,然后调用它并传了一个字典作为参数。
步骤有点多,但是理顺了确实很好用.
JavaScriptCore使用注意
JavaStript调用本地方法是在子线程中执行的,这里要根据实际情况考虑线程之间的切换,而在回调JavaScript方法的时候最好是在刚开始调用此方法的线程中去执行那段JavaStript方法的代码
如果需要从url接受参数,我们可以把参数拼接到url上,从而传递到原生中.但是这种方式局限比较大
文/纸简书生(简书作者)
原文链接:http://www.jianshu.com/p/fad8c7844d3e
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
开讲前附上一个牛逼的第三方
JavascriptBridge
OC执行JS代码
1.stringByEvaluatingJavaScriptFromString这个方法是UIWebView里面的方法,也是最为简单的和JS交互的方式
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;
用法比较简单,一般在代理方法
- (void)webViewDidFinishLoad:(UIWebView*)webView中使用
// 获取当前页面的title NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"]; // 获取当前页面的url NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];
OC执行JS&JS执行OC
1.JavaScriptCore这个是iOS7以后引进的,使用起来可简单,也可以比较复杂.
熟悉一下里面常见得几个对象及协议
JSContext:给JavaScript提供运行的上下文环境,通过-evaluateScript:方法就可以执行一JS代码 JSValue:JavaScript和Objective-C数据和方法的桥梁,封装了JS与ObjC中的对应的类型,以及调用JS的API等 JSManagedValue:管理数据和方法的类 JSVirtualMachine:处理线程相关,使用较少 JSExport:这是一个协议,如果采用协议的方法交互,自己定义的协议必须遵守此协议
对象简介
简单方式:直接调用JS代码
// 一个JSContext对象,就类似于Js中的window, // 只需要创建一次即可。 self.jsContext = [[JSContext alloc] init]; // jscontext可以直接执行JS代码。 [self.jsContext evaluateScript:@"var num = 10"]; [self.jsContext evaluateScript:@"var squareFunc = function(value) { return value * 2 }"]; // 计算正方形的面积 JSValue *square = [self.jsContext evaluateScript:@"squareFunc(num)"]; // 也可以通过下标的方式获取到方法 JSValue *squareFunc = self.jsContext[@"squareFunc"]; JSValue *value = [squareFunc callWithArguments:@[@"20"]]; NSLog(@"%@", square.toNumber); NSLog(@"%@", value.toNumber);
快速调用Block,可传参数
各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。虽然JavaScritpCore没有自带(毕竟不是在网页上运行的,自然不会有window、document、console这些类了),仍然可以定义一个Block方法来调用NSLog来模拟:
JSContext *context = [[JSContext alloc] init]; context[@"log"] = ^() { NSLog(@"+++++++Begin Log+++++++"); NSArray *args = [JSContext currentArguments]; for (JSValue *jsVal in args) { NSLog(@"%@", jsVal); } JSValue *this = [JSContext currentThis]; NSLog(@"this: %@",this); NSLog(@"-------End Log-------"); }; [context evaluateScript:@"log('ider', [7, 21], { hello:'world', js:100 });"]; // // Output: // +++++++Begin Log+++++++ // ider // 7,21 // [object Object] // this: [object GlobalObject] // -------End Log-------
通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,JSContext提供了类方法来获取参数列表
(+ (JSContext *) currentArguments;)和当前调用该方法的对象
(+ (JSValue *)currentThis)。对于"this"
,输出的内容是GlobalObject,这也是JSContext对象方法
- (JSValue *)globalObject;
所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是window,在JavaScriptCore是什么就不得而知了。Block可以传入JSContext作方法,但是JSValue没有toBlock方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是JSValue提供了
- (JSValue *)callWithArguments:(NSArray *)arguments;
方法可以反过来将参数传进去来调用方法。
JSContext *context = [[JSContext alloc] init]; [context evaluateScript:@"function add(a, b) { return a + b; }"]; JSValue *add = context[@"add"]; NSLog(@"Func: %@", add); JSValue *sum = [add callWithArguments:@[@(7), @(21)]]; NSLog(@"Sum: %d",[sum toInt32]); // OutPut: // Func: function add(a, b) { return a + b; } // Sum: 28
JSValue
还提供
- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在JSContext的globalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的JSValue
对象调用。
异常处理Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext
对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext
对象设置exceptionHandler,它接受的是^(JSContext context, JSValue exceptionValue)
形式的Block。其默认值就是将传入的exceptionValue赋给传入的context的exception属性:
^(JSContext *context, JSValue *exceptionValue) { context.exception = exceptionValue; };
我们也可以给exceptionHandler
赋予新的Block以便在JavaScript运行发生异常的时候我们可以立即知道:
JSContext *context = [[JSContext alloc] init]; context.exceptionHandler = ^(JSContext *con, JSValue *exception) { NSLog(@"%@", exception); con.exception = exception; }; [context evaluateScript:@"ider.zheng = 21"]; //Output: // ReferenceError: Can't find variable: ider
使用Block的注意事项
从之前的例子和介绍应该有体会到Block在JavaScriptCore中起到的强大作用,它在JavaScript和Objective-C之间的转换 建立起更多的桥梁,让互通更方便。但是要注意的是无论是把Block传给JSContext对象让其变成JavaScript方法,还是把它赋给exceptionHandler属性,在Block内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+ (JSContext
)currentContext;来获得。否则会造成循环引用使得内存无法被正确释放。比如上边自定义异常处理方法,就是赋给传入JSContext对象con,而不是其外创建的context对象,虽然它们其实是同一个对象。这是因为Block会对内部使用的在外部定义创建的对象做强引用,而JSContext也会对被赋予的Block做强引用,这样它们之间就形成了循环引用使得内存无法正常释放。对于JSValue也不能直接从外部引用到Block中,因为每个JSValue上都有JSContext
的引用 (@property(readonly, retain) JSContext context;),JSContext再引用Block同样也会形成引用循环。
前面十分的简单方便而且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。
为了方便起见,以下所有代码中的JSContext对象都会添加如下的log
方法和eventHandler
:
JSContext *context = [[JSContext alloc] init]; context.exceptionHandler = ^(JSContext *con, JSValue *exception) { NSLog(@"%@", exception); con.exception = exception; }; context[@"log"] = ^() { NSArray *args = [JSContext currentArguments]; for (id obj in args) { NSLog(@"%@",obj); } };
复杂但强大的方式:通过协议,模型实现(事先和前端协商格式)
首先,我们需要先定义一个协议,而且这个协议必须要遵守JSExport协议。
注意js中调用oc方法时参数的写法
@protocol JavaScriptObjectiveCDelegate <JSExport> // JS调用此方法来调用OC的系统相册方法 - (void)callSystemCamera; // 在JS中调用时,函数名应该为showAlertMsg(arg1, arg2) // 这里是只两个参数的。 - (void)showAlert:(NSString *)title msg:(NSString *)msg; // 通过JSON传过来 - (void)callWithDict:(NSDictionary *)params; // JS调用Oc,然后在OC中通过调用JS方法来传值给JS。 - (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params; @end
为了在调用原生方法之后调用对应的JS方法,写两个JS方法:
var jsFunc = function() { alert('Objective-C call js to show alert'); } var jsParamFunc = function(argument) { document.getElementById('jsParamFuncSpan').innerHTML = argument['name']; }
接下来,我们还需要定义一个模型,实现了上面定义协议里面的方法:
// 此模型用于注入JS的模型,这样就可以通过模型来调用方法。 @interface ObjCModel : NSObject <JavaScriptObjectiveCDelegate> @property (nonatomic, weak) JSContext *jsContext; @property (nonatomic, weak) UIWebView *webView; @end
实现这个模型:
@implementation ObjCModel - (void)callWithDict:(NSDictionary *)params { NSLog(@"Js调用了OC的方法,参数为:%@", params); } // Js调用了callSystemCamera - (void)callSystemCamera { NSLog(@"JS调用了OC的方法,调起系统相册"); // JS调用后OC后,又通过OC调用JS,但是这个是没有传参数的 JSValue *jsFunc = self.jsContext[@"jsFunc"]; [jsFunc callWithArguments:nil]; } - (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params { NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params); // 调用JS的方法 JSValue *jsParamFunc = self.jsContext[@"jsParamFunc"]; [jsParamFunc callWithArguments:@[@{@"age": @10, @"name": @"lili", @"height": @158}]]; } - (void)showAlert:(NSString *)title msg:(NSString *)msg { dispatch_async(dispatch_get_main_queue(), ^{ UIAlertView *a = [[UIAlertView alloc] initWithTitle:title message:msg delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil]; [a show]; }); } @end
接下来,我们在controller中在webview加载完成的代理中,给JS注入模型。
注意,获取webview的jsContext的方法
self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
#pragma mark - UIWebViewDelegate - (void)webViewDidFinishLoad:(UIWebView *)webView { self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"]; // 通过模型调用方法,这种方式更好些。 ObjCModel *model = [[ObjCModel alloc] init]; self.jsContext[@"OCModel"] = model; model.jsContext = self.jsContext; model.webView = self.webView; self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) { context.exception = exceptionValue; NSLog(@"异常信息:%@", exceptionValue); }; }
通过KVC取得context,其路径为documentView.webView.mainFrame.javaScriptContext。这样就可以获取到JS的context,然后为这个context注入我们的模型对象。
这里我们定义了两个JS方法,一个是jsFunc,不带参数。
另一个是jsParamFunc,带一个参数。
接下来,我们在html中的body中添加以下代码:
<div style="margin-top: 100px"> <h1>Test how to use objective-c call js</h1> <input type="button" value="Call ObjC system camera" onclick="OCModel.callSystemCamera()"> <input type="button" value="Call ObjC system alert" onclick="OCModel.showAlertMsg('js title', 'js message')"> </div> <div> <input type="button" value="Call ObjC func with JSON " onclick="OCModel.callWithDict({'name': 'testname', 'age': 10, 'height': 170})"> <input type="button" value="Call ObjC func with JSON and ObjC call js func to pass args." onclick="OCModel.jsCallObjcAndObjcCallJsWithDict({'name': 'testname', 'age': 10, 'height': 170})"> </div> <div> <span id="jsParamFuncSpan" style="color: red; font-size: 50px;"></span> </div>
现在就可以测试代码了。
当我们点击第一个按钮:Call ObjC system camera时,
通过OCModel.callSystemCamera(),就可以在HTML中通过JS调用OC的方法。
在OC代码中,我们的callSystemCamera方法体中,添加了以下两行代码,就是获取HTML中所定义的JS就去jsFunc,然后调用它。
JSValue *jsFunc = self.jsContext[@"jsFunc"]; [jsFunc callWithArguments:nil];
这样就可以在JS调用OC方法时,也让OC反馈给JS。
看看下面传字典参数:
- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params { NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params); // 调用JS的方法 JSValue *jsParamFunc = self.jsContext[@"jsParamFunc"]; [jsParamFunc callWithArguments:@[@{@"age": @10, @"name": @"lili", @"height": @158}]]; }
获取我们在HTML中定义的jsParamFunc方法,然后调用它并传了一个字典作为参数。
步骤有点多,但是理顺了确实很好用.
JavaScriptCore使用注意
JavaStript调用本地方法是在子线程中执行的,这里要根据实际情况考虑线程之间的切换,而在回调JavaScript方法的时候最好是在刚开始调用此方法的线程中去执行那段JavaStript方法的代码
根据url前缀特殊处理(协议拦截)-简单传递参数
在UIWebView的代理方法:- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType判断url前缀,然后,根据协议类型进行特殊处理.
如果需要从url接受参数,我们可以把参数拼接到url上,从而传递到原生中.但是这种方式局限比较大
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { NSString *urlStr = request.URL.absoluteString; if ([urlStr rangeOfString:@"test://"].location != NSNotFound) { // url的协议头是test的特殊处理 NSLog(@"test"); NSURL *url = [NSURL URLWithString:urlStr]; NSString *scheme = url.scheme; NSString *host = url.host; NSString *qurey = url.query; NSString *parameter = url.parameterString; // 根据参数做进一步处理 // TODO return NO; } return YES; }
文/纸简书生(简书作者)
原文链接:http://www.jianshu.com/p/fad8c7844d3e
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
相关文章推荐
- iOS 利用阿里云上传及下载图片
- ios-NSString总结
- IOS动画总结-Core Animation
- miss @end
- 浅谈iOS中的蓝牙技术(一) --GameKit.framework
- Ios中常见的问题
- iOS 调用地图导航
- iOS开发 ☞ 应用间跳转
- sdwebimage 图片大小 处理
- iOS开发——NSString小结
- IOS常用方法封装(1)
- iOS开发——瀑布流
- iOS开发——蓝牙小结
- 手把手教你如何搭建iOS项目基本框架
- iOS开发——观察者模式
- iOS开发——打电话
- iOS开发——工厂模式
- iOS开发——单例模式
- iOS 导航栏Title不居中
- iOS中使用脚本打包项目