簡介
iOS 插件篇主要內(nèi)容分為三部分:原生與h5交互的實現(xiàn),插件獲取app生命周期能力,插件的封裝三部分,插件獲取app生命周期能力這個會單獨介紹,現(xiàn)在很多功能如支付寶,第三方分享都需要插件能獲取生命周期的能力,任何一部分都能在開發(fā)過程中單獨使用.
原生與h5交互的實現(xiàn)原理
最原始鏈接攔截和wkwebview
的addScriptMessageHandler
就不介紹了,這里使用的是利用<JavaScriptCore/JavaScriptCore.h>
庫中的JSContext
和runtime
實現(xiàn)的,網(wǎng)上已經(jīng)有了JSContext
交互實現(xiàn)的方法介紹,但是都沒有結(jié)合runtime
來實現(xiàn)插件的即插即用的功能,利用runtime
可以讓插件單獨編譯成framework,在打包之前勾選特定的插件就能實現(xiàn)即插即用的功能
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;
// 捕捉網(wǎng)頁加載失敗的異常信息
[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
中注入統(tǒng)一的交互對象_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;
}
- 上面實現(xiàn)的方法中,也同步將插件的對象和方法注入到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
的實現(xiàn)方式卻是通過交互對象_JSCHandler_
的execute
方法實現(xiàn)的,在這里我們通過JSExport
協(xié)議將execute
轉(zhuǎn)化為原生實現(xiàn)的一個方法
@protocol JSCHandler <JSExport>
JSExportAs(execute,-(id)executeWithPlugin:(NSString *)pluginName method:(NSString *)methodName arguments:(JSValue *)arguments argCount:(NSInteger)argCount);
@end
- 在實現(xiàn)的方法的地方,我們通過
NSClassFromString
找到PluginDemo1的對象,并且通過CorInvoker
這個類利用runtime
來實現(xiàn)方法,并且?guī)蠀?shù)
- (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實現(xiàn)的方式這就不多說了,具體可以看demo
可以看下PluginDemo1這個類的實現(xiàn):
@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回調(diào)所創(chuàng)建的,js使用回調(diào)的方式如下:
PluginDemo1.demo1alert('123',function(answer){
alert(answer);
});
其中的第二個參數(shù)是一個回調(diào)function(answer){alert(answer);}
,這里使用CorJSFunctionRef *cb
來接收這個回調(diào),并且可以在想要的地方通過executeWithArguments
實現(xiàn)這個回調(diào)
總結(jié)
交互的原理這里就講完了,因為使用的是runtime
的方式找到類并實現(xiàn)方法,所以可以將PluginDemo這個類封裝成庫,這樣就可以在任何的項目中使用這個庫了