iOS 开发技术之经验总结篇

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

2018-09-26  本文已影响0人  何小博

简介

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

原生与h5交互的实现原理

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

demo地址

demo地址

获取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{
    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;
}
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;
}
- (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

- (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;
}

@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")]; 
}
PluginDemo1.demo1alert('123',function(answer){
                        alert(answer);
                     });

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

总结

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

上一篇下一篇

猜你喜欢

热点阅读