最近跟同事联调一个iOS与JS交互想接口,折腾了整整一天,被迫重新研究了一下iOS与JS交互的原理,才发现原来项目中用的方案已经是很老很老的方式了,
效率不高,学习成本倒不低,但总归之前用了很久都没出现过什么问题,所以还是简单的总结下吧,然后就准备把这套东西淘汰掉用新的方式了。
OC调用JS
OC调用JS的方法很简单,就是UIWebView的stringByEvaluatingJavaScriptFromString
方法,或者
WKWebView的- (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;
方法。
JS调用OC
其实现在项目中用的方案原理很简单,就是拦截请求,然后获取参数执行OC的方法;
不过为了跟安卓统一,并且方便复用,做了一层封装,给JS注入方法。
考虑到js有很多的全局函数,所以直接给js添加方法是不太合适的,可能会导致命名冲突,比较理想的做法是模块化,就是给js注入个对象,给这个对象再添加方法。
具体来说就是:
客户端这里有一个bridge类,里面有跟js端协商好的方法;
当网页加载结束后(webViewDidFinishLoading
),iOS这端会执行一个方法,取到bridge里面所有方法列表(用runtime方法获取),
然后拼装成对应的js方法,用evaluateJavaScript
方法注入给js,
js需要做的就是在需要的时候调用对应方法即可。
注意,js端除了判断方法存在不存在和调用方法,其他什么都没做,对象和所有的方法都是客户端注入的。
所以这个方案的关键是拼接注入给js的方法,
这里用了一个巧妙的方法,即利用js可以执行字符串中的JavaScript代码的eval()
方法。
iOS端将bridge里获取的方法列表拼接成字符串,跟js代码的字符串拼在一起,拼成一段JavaScript字符串,
然后用webView的evaluateJavaScript
方法调用就可以了
而注入给js的方法,就是让js去加载一个指定格式的自定义的URL,然后iOS端拦截这个请求,判断如果是自定义的URL,则去执行自己的方法,
这里一般是提前一定好一个scheme,然后根据后面的字段来区别不同的方法,
这个方案js传递参数给OC也是很简单,直接拼在URL后面即可。
理论上OC传递参数给js也是很简单的,只需js端定义一个带返回值的函数,然后iOS端要拼接这个函数的调用的字符串,然后直接用webView的evaluateJavaScript
方法调用它就行了;
但是这跟上面“所有方法都是让js去load一个自定义格式的URL”的设计是冲突的,
因为上面的设计是,所有方法都是iOS端注入的,注入的方法内容是让js去load一个自定义URL,而这个传参的js方法不是去load一个自定义的URL,而是去取一个OC执行的js的结果,然后再进行其他操作;
如何解决这个问题呢?
我自己想到的方法是,在注入方法的时候,将注入的方法分为两类,一类就是去load自定义URL的方法;另外一种是专门传递参数给js的方法,也就是直接执行拼接好的js函数的方法。
我感觉这个方法是可行的,如果是一些简单的传值的方法的话,所不定还挺高效,但貌似有一个问题,执行结果貌似是同步传回给js的,如果OC端执行的方法比较耗时,那可能会卡住js端的进程。
前辈们在项目中使用了个更高级的办法,就是调用js方法时,如果需要返回值的话,传一个回调函数到OC。回调函数固定以一个json字符串作为输入参数。
为什么说这个方法高级呢,一是统一了注入给js的方法格式,而是统一了js注入方法的返回值的格式,三是统一了用异步回调。
需要注意的是:OC最终调用的方法只有一个参数,这个参数是一个js传过来的参数数组,具体参数数组里面都是什么内容,要在定义注入函数的时候就跟js端商量好!
(这里还有一个小细节,如果没有参数,OC端的selector是没有“:”的,如果有参数,一定记得要拼接“:”,否则OC端会识别成不同的函数名)
回调函数还有一个需要注意的地方,因为OC回调的是js函数,所以js端要在某个地方有保存这个函数,直接在调用注入方法时实现这个函数貌似是不行的,必须要建立其调用js注入函数与callback函数之间的对应关系才行,否则oc调用js回调是不知道对应的回调函数是谁的。。。。。这个比较绕
我又看了下js端同学些的代码,js端是在传入callback的同时,将callback函数保存到一个数组里面,然后传数组名称和callback的index给OC,所以OC端实际调用的是,拿到数组里保存的函数然后传参数调用。
也就是说,如果是需要OC传递参数给JS的方法,实际上不完全是OC这边注入的,还是需要JS端同学配合下的。
这样看来,跟直接让JS端同学实现几个专门的传参数方法代价也差不多。。。
这个方案是UIWebView和WKWebView都可以使用的。
新的方式也就是iOS7之后的推出的JavaScriptCore,这个下一篇再写。