美文网首页iOS 开发技术之经验总结篇
iOS 插件篇(原生与h5交互的实现)

iOS 插件篇(原生与h5交互的实现)

作者: 何小博 | 来源:发表于2018-09-26 11:05 被阅读0次

    简介

    iOS 插件篇主要内容分为三部分:原生与h5交互的实现,插件获取app生命周期能力,插件的封装三部分,插件获取app生命周期能力这个会单独介绍,现在很多功能如支付宝,第三方分享都需要插件能获取生命周期的能力,任何一部分都能在开发过程中单独使用.

    原生与h5交互的实现原理

    最原始链接拦截和wkwebviewaddScriptMessageHandler就不介绍了,这里使用的是利用<JavaScriptCore/JavaScriptCore.h>库中的JSContextruntime实现的,网上已经有了JSContext交互实现的方法介绍,但是都没有结合runtime来实现插件的即插即用的功能,利用runtime可以让插件单独编译成framework,在打包之前勾选特定的插件就能实现即插即用的功能

    demo地址

    demo地址

    获取JSContext

    • - (void)webViewDidFinishLoad:(UIWebView *)webView 中获取JSContext 对象,如下所示:
    - (void)webViewDidFinishLoad:(UIWebView *)webView {
        [self initializeJSCHandler];
    }
    
    - (void)initializeJSCHandler{
        if(_JSContext){
            _JSContext = nil;
        }
        JSContext *context = self.JSContext;
        if(!context){
            return;
        }
        [self initializeWithJSContext:context];
    }
    
    
    • 使用懒加载或者JSContext对象并保存,如下所示:
    - (JSContext *)JSContext{
        if (!_JSContext) {
            JSContext *context = nil;
            @try {
                context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
            }@catch (...) {}
            _JSContext = context;
            // 捕捉网页加载失败的异常信息
            [self.webView stringByEvaluatingJavaScriptFromString:@"window.onerror = function(error, url, line) {console.error('ERROR: '+error+' URL:'+url+' L:'+line);};"];
            //打印异常
            _JSContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue){
                context.exception = exceptionValue;
                NSLog(@"%@", exceptionValue);
            };
        }
        return _JSContext;
    }
    
    • JSContext中注入统一的交互对象_JSCHandler_,如下所示:
    NSString *const HandlerInjectField = @"_JSCHandler_";
    
    - (void)initializeWithJSContext:(JSContext *)context{
        context[HandlerInjectField] = self;
        NSString *baseJS = [self generateBaseJS];
        [context evaluateScript:baseJS];
        [context setExceptionHandler:^(JSContext *ctx, JSValue *exception) {
            ctx.exception = exception;
        }];
        self.ctx = context;
    }
    
    • 上面实现的方法中,也同步将插件的对象和方法注入到context中NSString *baseJS = [self generateBaseJS]; generateBaseJS方法中可以将我们制作好的插件类名称和方法名通过拼接成特定的字符串注入到context中.如下所示:
    - (NSString *)javaScriptForMethod:(NSString *)method plugin:(NSString *)plugin {
        return [NSString stringWithFormat:@"%@.%@=function(){var argCount = arguments.length;var args = [];for(var i = 0; i < argCount; i++){args[i] = arguments[i];};return _JSCHandler_.execute('%@','%@',args,argCount);};",plugin,method,plugin,method];
    }
    
    • 可以看下运行时字符串的效果:
    PluginDemo1={};PluginDemo1.demo1alert=function(){var argCount = arguments.length;var args = [];for(var i = 0; i < argCount; i++){args[i] = arguments[i];};return _JSCHandler_.execute('PluginDemo1','demo1alert',args,argCount);};
    

    可以看到在js中首选声明了PluginDemo1这个对象,还有它的demo1alert这个方法,最后rutern的实现方式却是通过交互对象_JSCHandler_execute方法实现的,在这里我们通过JSExport 协议将execute转化为原生实现的一个方法

    @protocol JSCHandler <JSExport>
    
    JSExportAs(execute,-(id)executeWithPlugin:(NSString *)pluginName method:(NSString *)methodName arguments:(JSValue *)arguments argCount:(NSInteger)argCount);
    
    @end
    
    
    • 在实现的方法的地方,我们通过NSClassFromString找到PluginDemo1的对象,并且通过CorInvoker这个类利用runtime来实现方法,并且带上参数
    - (id)executeWithPlugin:(NSString *)pluginName method:(NSString *)methodName arguments:(JSValue *)arguments argCount:(NSInteger)argCount {
        
        [PluginManager loadDynamicPlugins:pluginName];
        
        Class pluginClass = NSClassFromString(pluginName);
        id pluginInstance = [[pluginClass alloc] init];
        if (!pluginInstance) {
            return nil;
        }
    
        NSString *selector = [methodName stringByAppendingString:@":"];
        SEL sel = NSSelectorFromString(selector);
        if(![pluginInstance respondsToSelector:sel]){
            return nil;
        }
        
        id args = nil;
        args = [PluginManager arrayFromArguments:arguments count:argCount];
        
        BOOL isAsync = [self selector:sel isAsynchronousMethodInClass:[pluginInstance class]];
        
        
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        if (isAsync) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [pluginInstance ac_invoke:selector arguments:CorArgsPack(args)];
            });
            return nil;
        }else{
            return [pluginInstance ac_invoke:selector arguments:CorArgsPack(args)];
        }
    #pragma clang diagnostic pop
        
        return nil;
    }
    
    
    • runtime实现的方式这就不多说了,具体可以看demo

    • 可以看下PluginDemo1这个类的实现:

    @implementation PluginDemo1
    
    - (void)demo1alert:(NSMutableArray *)inArguments {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"PluginDemo1" message:inArguments.firstObject delegate:self cancelButtonTitle:@"取消" otherButtonTitles:nil, nil];
        _cb = inArguments.lastObject;
        [alert show];
        
        [_cb executeWithArguments:CorArgsPack(@"456")]; 
    }
    
    • 这里可以看到CorJSFunctionRef *cb 这个对象是专门针对js回调所创建的,js使用回调的方式如下:
    PluginDemo1.demo1alert('123',function(answer){
                            alert(answer);
                         });
    

    其中的第二个参数是一个回调function(answer){alert(answer);},这里使用CorJSFunctionRef *cb来接收这个回调,并且可以在想要的地方通过executeWithArguments实现这个回调

    总结

    交互的原理这里就讲完了,因为使用的是runtime的方式找到类并实现方法,所以可以将PluginDemo这个类封装成库,这样就可以在任何的项目中使用这个库了

    相关文章

      网友评论

        本文标题:iOS 插件篇(原生与h5交互的实现)

        本文链接:https://www.haomeiwen.com/subject/znvkoftx.html