前端直接调用OC的native方法
来源:互联网 发布:一事无成 知乎 编辑:程序博客网 时间:2024/05/14 14:23
在ANDROID中,WebView控件有setJavaScriptEnable接口,这里大概的意思就是让客户端能够响应来自WebView的回调,还有一个接口是addJavaScriptInterface(obj, "external"),这个接口的大概意思是给obj开一个叫"external"的口子,这样前端通过window.external.func(param1,param2...)这样的方式就可以直接调用obj中名叫"func"的方法了。
在IOS中,要想实现这样的WebView需要经过一段周章,下面开始简要说明一下前端能够调用到客户端的代码的基本原理:客户端不管是根据本地的html加载网页还是url动态加载网页,实际上都已经接管了网页上的源码,然而这个源码是用JavaScript写的,这种源码是不能直接对IOS的OC代码进行调用的,我们要做的就是这样的一个转换,让JS通过一个bridge间接调用OC。
;(function() { var messagingIframe, bridge = 'external', CUSTOM_PROTOCOL_SCHEME = 'jscall'; if (window[bridge]) { return }function _createQueueReadyIframe(doc) { messagingIframe = doc.createElement('iframe');messagingIframe.style.display = 'none';doc.documentElement.appendChild(messagingIframe);}window[bridge] = {}; var methods = [%@]; for (var i=0;i<methods.length;i++){ var method = methods[i]; var code = "(window[bridge])[method] = function " + method + "() {messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ':' + arguments.callee.name + ':' + encodeURIComponent(JSON.stringify(arguments));}"; eval(code); } //创建iframe,必须在创建external之后,否则会出现死循环 _createQueueReadyIframe(document); //通知js开始初始化 //initReady();})();
我们通常使用IOS的WebView控件都是通过实现shouldStartLoadWithRequest等相关代理来截获网页url变化这个通知,在url中通常就隐含了我们需要的参数,然而这种方式并不够人性化,前端要是能够直接通过函数调用的方法来call OC的native是比较合理的方式。
shouldStartLoadWithRequest什么时候会被调用?是否一定要url变化才会调用?
shouldStartLoadWithRequest不仅在url变化的时候调用,而且只要网页内容变化的时候也能调用
上面的JS代码
messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + ':' + arguments.callee.name + ':' + encodeURIComponent(JSON.stringify(arguments));
就是对网页内容进行改变,通过在webview中植入这样的代码,就可以调到shouldStartLoadWithRequest,shouldStartLoadWithRequest是OC的代码,这样就实现了从JS到OC的调用。和Java的反射有点类似。
接下来解决如何在webview中植入这样的代码
- (void)webViewDidFinishLoad:(UIWebView *)webView { if (webView != _webView) { return; } //is js insert if (![[webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:@"typeof window.%@ == 'object'", kBridgeName]] isEqualToString:@"true"]) { //get class method dynamically unsigned int methodCount = 0; Method *methods = class_copyMethodList([self class], &methodCount); NSMutableString *methodList = [NSMutableString string]; for (int i=0; i<methodCount; i++) { NSString *methodName = [NSString stringWithCString:sel_getName(method_getName(methods[i])) encoding:NSUTF8StringEncoding]; //防止隐藏的系统方法名包含“.”导致js报错 if ([methodName rangeOfString:@"."].location!=NSNotFound) { continue; } [methodList appendString:@"\""]; [methodList appendString:[methodName stringByReplacingOccurrencesOfString:@":" withString:@""]]; [methodList appendString:@"\","]; } if (methodList.length>0) { [methodList deleteCharactersInRange:NSMakeRange(methodList.length-1, 1)]; } free(methods); NSBundle *bundle = _resourceBundle ? _resourceBundle : [NSBundle mainBundle]; NSString *filePath = [bundle pathForResource:@"WebViewJsBridge" ofType:@"js"]; NSString *js = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil]; [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:js, methodList]]; }}
webViewDidFinishLoad这个代理在webview加载完成后调用。
stringByEvaluatingJavaScriptFromString
相当于在webview的尾部追加一段代码,这里不仅追加进去了js代码,还有本地的函数列表,也就是OC暴露给前端可以调用的函数列表,当我们点击webview中的某个按钮触发前端执行了window.external.func(param1, param2)这样的代码,而这个代码因为我们注入了上面那段JS代码,不仅触发了shouldStartLoadWithRequest的执行,还把前端调用的函数名和参数传了回来,接下来就是在shouldStartLoadWithRequest中对这些参数进行整合,变成OC可以识别的代码,就能够正确调用到OC的native方法了
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { if (webView != _webView) { return YES; } NSURL *url = [request URL]; NSString *requestString = [[request URL] absoluteString]; if ([requestString hasPrefix:kCustomProtocolScheme]) { NSArray *components = [[url absoluteString] componentsSeparatedByString:@":"]; NSString *function = (NSString*)[components objectAtIndex:1]; NSString *argsAsString = [(NSString*)[components objectAtIndex:2] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSData *argsData = [argsAsString dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *argsDic = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:argsData options:kNilOptions error:NULL]; //convert js array to objc array NSMutableArray *args = [NSMutableArray array]; for (int i=0; i<[argsDic count]; i++) { [args addObject:[argsDic objectForKey:[NSString stringWithFormat:@"%d", i]]]; } //ignore warning#pragma clang diagnostic ignored "-Warc-performSelector-leaks" SEL selector = NSSelectorFromString([args count]>0?[function stringByAppendingString:@":"]:function); if ([self respondsToSelector:selector]) { [self performSelector:selector withObject:args]; } return NO; }else { return YES; }
这里的request和真实的url改变带回来的参数组成不太一样,这个值是在JS代码中拼接的,所以这里解析也要按照那个规则逆向解析,后面用到了selector,将函数名function转换成selector,在run-time时就会调到了那个OC中的同名函数了
- (void)writeTopic:(NSArray *)params{ NSLog(@"writeTopic called");}
这里整合成一个参数,params数组,可以通过objectAtIndex来取出每个参数,进行后面的相关操作。
总结:
1 通过注入JS代码到webview
2 注入的JS代码在能改变webview的内容,实现网页的跳转(这里用的是一个空白的什么都没有的不可见的网页)
3 根据注入的JS中的规则在shouldStartLoadWithRequest中反向解析,并通过SEL动态调用。
前端直接调用OC的native方法
- 前端直接调用OC的native方法
- 在iOS平台上使用js直接调用OC方法
- OC 类方法的调用
- 类方法的直接调用
- JavaScript直接调用OC代码
- 在Android Native层直接调用MediaCodec接口的实现
- React Native 原生与JS之间事件绑定注册 作用在于原生可以直接调用JS的方法
- wp7中调用native代码的方法
- 自己实现一个Native方法的调用
- 自己实现一个Native方法的调用
- 自己实现一个Native方法的调用
- react-native调用Android的原生方法
- swift调用oc类文件的方法
- oc中如何调用c++的方法
- OC的方法调用-和Java对比
- javaScript 与OC方法的调用
- swift 调用第三方的oc 方法
- Java调用Native方法
- 码农小汪-spring框架学习之9-基于 Java 的配置元数据 @ImportResource
- iOS开发系列--视图切换
- 欢迎大家来到霉老板的博客!
- 如何造出39元的智能插座?——小米智能插座拆解
- yum使用详解
- 前端直接调用OC的native方法
- android studio运行程序找不到class,java.lang.ClassNotFoundException: Didn't find class,完美解决!
- Masonry的使用
- Git使用教程
- 从今天起,开始记录一些关于Android的问题或者文章
- JavaScript闭包
- Cocos2d-JS 场景与层
- SVN linux下安装(服务端版本安装)
- jQueryx相关