本文設(shè)計(jì)到的源碼是基于Cordova 4.2.1版本,Cordova官網(wǎng)吟吝。
CDVViewController
CDVViewController
是Cordova最主要的類(lèi)指蚁,它把所有模塊整合在一起菩佑,直接初始化一個(gè)它的實(shí)例就可以使用。例如下面的代碼:
CDVViewController *vc = [[CDVViewController alloc] init];
vc.startPage = @"www.baidu.com";
[self presentViewController:vc animated:YES completion:nil];
CDVViewController
主要實(shí)現(xiàn)的功能:
- 注冊(cè)凝化、初始化插件
- 讀取稍坯、應(yīng)用配置文件
- 初始化并配置WebView,設(shè)置其代理
- 管理js與原生的方法調(diào)用
- 管理應(yīng)用與網(wǎng)頁(yè)的生命周期
搓劫。瞧哟。。
它主要的屬性有:
- CDVWebViewEngineProtocol:webview相關(guān)的回調(diào)
- CDVCommandDelegate:js與原生插件交互方法糟把,插件初始化
- CDVCommandQueue:命令執(zhí)行隊(duì)列
CDVCommandDelegate
和CDVCommandQueue
會(huì)在js調(diào)用原生插件與插件初始化提到绢涡,這里先不細(xì)說(shuō)。
CDVWebViewEngineProtocol
定義了WebView
引擎的抽象類(lèi)遣疯,具體實(shí)現(xiàn)由插件提供雄可,例如CDVUIWebViewEngine
實(shí)現(xiàn)UIWebView
的引擎。
CDVWebViewEngineProtocol協(xié)議定義
CDVWebViewEngineProtocol
協(xié)議其實(shí)是對(duì)于WebView的一層封裝缠犀,屏蔽了不同WebView
接口的差異数苫,現(xiàn)在iOS有UIWebView
與WKWebView
。
@protocol CDVWebViewEngineProtocol <NSObject>
@property (nonatomic, strong, readonly) UIView* engineWebView;
- (id)loadRequest:(NSURLRequest*)request;
- (id)loadHTMLString:(NSString*)string baseURL:(NSURL*)baseURL;
- (void)evaluateJavaScript:(NSString*)javaScriptString completionHandler:(void (^)(id, NSError*))completionHandler;
- (NSURL*)URL;
- (BOOL)canLoadRequest:(NSURLRequest*)request;
- (instancetype)initWithFrame:(CGRect)frame;
- (void)updateWithInfo:(NSDictionary*)info;
@end
engineWebView
屬性對(duì)外直接暴露了內(nèi)部封裝的WebView
辨液,其它方法都是對(duì)WebView
方法的一層簡(jiǎn)單封裝虐急。
UIWebView引擎CDVUIWebViewEngine
我們以UIWebView
的實(shí)現(xiàn)CDVUIWebViewEngine
為例說(shuō)明,它是以插件的形式實(shí)現(xiàn)的滔迈,主要作用是初始化UIWebView的配置止吁,對(duì)UIWebView的方法和代理進(jìn)行了一層封裝。它實(shí)現(xiàn)了協(xié)議CDVWebViewEngineProtocol
燎悍,主要有以下幾個(gè)屬性敬惦。
// CDVUIWebViewEngine
// UIWebview
@property (nonatomic, strong, readwrite) UIView* engineWebView;
// UIWebView的代理
@property (nonatomic, strong, readwrite) id <UIWebViewDelegate> uiWebViewDelegate;
@property (nonatomic, strong, readwrite) CDVUIWebViewNavigationDelegate* navWebViewDelegate;
初始化從initWithFrame:
方法開(kāi)始,它創(chuàng)建了一個(gè)UIWebView
谈山,并賦值給了engineWebView
俄删,然后在插件初始化方法pluginInitialize
中初始化UIWebView
的代理和配置。
- (void)pluginInitialize
{
UIWebView* uiWebView = (UIWebView*)_engineWebView;
// 判斷當(dāng)前controller是否實(shí)現(xiàn)了UIWebViewDelegate
// 如果實(shí)現(xiàn)了就把當(dāng)前controller設(shè)置為CDVUIWebViewDelegate的代理實(shí)現(xiàn)
if ([self.viewController conformsToProtocol:@protocol(UIWebViewDelegate)]) {
self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:(id <UIWebViewDelegate>)self.viewController];
uiWebView.delegate = self.uiWebViewDelegate;
}
// 如果沒(méi)有實(shí)現(xiàn),創(chuàng)建一個(gè)CDVUIWebViewNavigationDelegate畴椰,作為CDVUIWebViewDelegate的代理實(shí)現(xiàn)
else {
self.navWebViewDelegate = [[CDVUIWebViewNavigationDelegate alloc] initWithEnginePlugin:self];
self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:self.navWebViewDelegate];
uiWebView.delegate = self.uiWebViewDelegate;
}
// 初始化配置信息
// self.commandDelegate.settings是CDVViewController的配置信息臊诊,定義在config.xml
[self updateSettings:self.commandDelegate.settings];
}
js調(diào)用原生插件解析
插件調(diào)用流程:
- js發(fā)起請(qǐng)求
gap://
- 實(shí)現(xiàn)
WebView
的代理webView:shouldStartLoadWithRequest:navigationType:
,攔截scheme
為gap
的請(qǐng)求 - 執(zhí)行js方法
cordova.require('cordova/exec').nativeFetchMessages()
獲取需要執(zhí)行的原生插件的信息(插件名斜脂,插件方法抓艳,回調(diào)ID,參數(shù)) - 將需要執(zhí)行的原生插件信息放入命令隊(duì)列等待執(zhí)行
- 執(zhí)行原生插件帚戳,并把結(jié)果回調(diào)給js
插件調(diào)用堆棧如圖所示:
js請(qǐng)求攔截
CDVUIWebViewDelegate
實(shí)現(xiàn)了UIWebView的webView:shouldStartLoadWithRequest:navigationType:
代理壶硅,在頁(yè)面加載前做一些處理。
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
BOOL shouldLoad = YES;
// 1. 判斷如果有代理销斟,先調(diào)用代理方法
// 這里的_delegate是CDVUIWebViewNavigationDelegate
if ([_delegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {
shouldLoad = [_delegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
}
if (shouldLoad) {
// 是否是調(diào)試工具refresh
BOOL isDevToolsRefresh = (request == webView.request);
// 是否是頂層頁(yè)面
BOOL isTopLevelNavigation = isDevToolsRefresh || [request.URL isEqual:[request mainDocumentURL]];
if (isTopLevelNavigation) {
if ([self request:request isEqualToRequestAfterStrippingFragments:webView.request]) {
NSString* prevURL = [self evalForCurrentURL:webView];
if ([prevURL isEqualToString:[request.URL absoluteString]]) {
VerboseLog(@"Page reload detected.");
} else {
VerboseLog(@"Detected hash change shouldLoad");
return shouldLoad;
}
}
switch (_state) {
case STATE_WAITING_FOR_LOAD_FINISH:
// 重定向情況庐椒,判斷l(xiāng)oadCount是否是1
if (_loadCount != 1) {
NSLog(@"CDVWebViewDelegate: Detected redirect when loadCount=%ld", (long)_loadCount);
}
break;
case STATE_IDLE:
case STATE_IOS5_POLLING_FOR_LOAD_START:
case STATE_CANCELLED:
// 頁(yè)面導(dǎo)航開(kāi)始
_loadCount = 0;
_state = STATE_WAITING_FOR_LOAD_START;
break;
default:
{
// 其它情況,回調(diào)webView:didFailLoadWithError:
NSString* description = [NSString stringWithFormat:@"CDVWebViewDelegate: Navigation started when state=%ld", (long)_state];
NSLog(@"%@", description);
_loadCount = 0;
_state = STATE_WAITING_FOR_LOAD_START;
if ([_delegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) {
NSDictionary* errorDictionary = @{NSLocalizedDescriptionKey : description};
NSError* error = [[NSError alloc] initWithDomain:@"CDVUIWebViewDelegate" code:1 userInfo:errorDictionary];
[_delegate webView:webView didFailLoadWithError:error];
}
}
}
} else {
// 屏蔽一些無(wú)效網(wǎng)站的訪問(wèn)
shouldLoad = shouldLoad && [self shouldLoadRequest:request];
}
}
return shouldLoad;
}
攔截js調(diào)用原生插件請(qǐng)求的關(guān)鍵代碼在CDVUIWebViewNavigationDelegate
蚂踊,它實(shí)現(xiàn)了CDVUIWebViewDelegate
的代理约谈,在CDVUIWebViewDelegate
會(huì)把請(qǐng)求轉(zhuǎn)發(fā)給它。
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL* url = [request URL];
CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
// H5調(diào)用原生插件犁钟,后面分析
if ([[url scheme] isEqualToString:@"gap"]) {
[vc.commandQueue fetchCommandsFromJs];
[vc.commandQueue executePending];
return NO;
}
// 給插件預(yù)留了一個(gè)處理URL的方法棱诱,調(diào)用插件的方法`shouldOverrideLoadWithRequest:navigationType:`,獲取返回值
// 應(yīng)用:系統(tǒng)默認(rèn)插件CDVIntentAndNavigationFilter中涝动,實(shí)現(xiàn)了Intent與Navigation的白名單機(jī)制迈勋。
BOOL anyPluginsResponded = NO;
BOOL shouldAllowRequest = NO;
for (NSString* pluginName in vc.pluginObjects) {
CDVPlugin* plugin = [vc.pluginObjects objectForKey:pluginName];
SEL selector = NSSelectorFromString(@"shouldOverrideLoadWithRequest:navigationType:");
if ([plugin respondsToSelector:selector]) {
anyPluginsResponded = YES;
shouldAllowRequest = (((BOOL (*)(id, SEL, id, int))objc_msgSend)(plugin, selector, request, navigationType));
if (!shouldAllowRequest) {
break;
}
}
}
if (anyPluginsResponded) {
return shouldAllowRequest;
}
// 處理其它類(lèi)型的url,file:類(lèi)型直接返回YES
BOOL shouldAllowNavigation = [self defaultResourcePolicyForURL:url];
if (shouldAllowNavigation) {
return YES;
} else {
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
}
return NO;
}
H5調(diào)用原生插件
Cordova調(diào)用原生插件的方式是通過(guò)攔截gap://
的URL,然后執(zhí)行js代碼cordova.require('cordova/exec').nativeFetchMessages()
獲取參數(shù)醋粟,來(lái)實(shí)現(xiàn)調(diào)用靡菇。
我們來(lái)看關(guān)鍵代碼:
// CDVUIWebViewNavigationDelegate.m
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
if ([[url scheme] isEqualToString:@"gap"]) {
[vc.commandQueue fetchCommandsFromJs];
[vc.commandQueue executePending];
return NO;
}
}
- (void)fetchCommandsFromJs
{
__weak CDVCommandQueue* weakSelf = self;
NSString* js = @"cordova.require('cordova/exec').nativeFetchMessages()";
[_viewController.webViewEngine evaluateJavaScript:js
completionHandler:^(id obj, NSError* error) {
if ((error == nil) && [obj isKindOfClass:[NSString class]]) {
NSString* queuedCommandsJSON = (NSString*)obj;
// 調(diào)用的插件信息加入到queue中
[weakSelf enqueueCommandBatch:queuedCommandsJSON];
// 調(diào)用執(zhí)行方法
[self executePending];
}
}];
}
調(diào)用js方法cordova.require('cordova/exec').nativeFetchMessages()
,獲取調(diào)用的插件信息米愿。
// 插件信息示例
[["DevicePlugin1678563772","DevicePlugin","getDeviceInfo",[]]]
命令隊(duì)列CDVCommandQueue
js的每次調(diào)用信息會(huì)封裝被封裝為一個(gè)命令CDVInvokedUrlCommand
厦凤,CDVInvokedUrlCommand
繼承自NSObject
,主要存儲(chǔ)了下面的信息:
// CDVInvokedUrlCommand
// 參數(shù)
@property (nonatomic, readonly) NSArray* arguments;
// 回調(diào)ID
@property (nonatomic, readonly) NSString* callbackId;
// 類(lèi)名
@property (nonatomic, readonly) NSString* className;
// 方法名
@property (nonatomic, readonly) NSString* methodName;
CDVCommandQueue
管理著所有的命令育苟,實(shí)現(xiàn)了一個(gè)命令的隊(duì)列较鼓。在js調(diào)用原生插件時(shí),會(huì)調(diào)用CDVCommandQueue
的enqueueCommandBatch:
方法违柏,將插件調(diào)用信息加到commandBatchHolder
數(shù)組中博烂,最后commandBatchHolder
數(shù)組添加到CDVCommandQueue
的queue
。
- (void)enqueueCommandBatch:(NSString*)batchJSON
{
if ([batchJSON length] > 0) {
NSMutableArray* commandBatchHolder = [[NSMutableArray alloc] init];
[_queue addObject:commandBatchHolder];
if ([batchJSON length] < JSON_SIZE_FOR_MAIN_THREAD) {
[commandBatchHolder addObject:[batchJSON cdv_JSONObject]];
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
NSMutableArray* result = [batchJSON cdv_JSONObject];
@synchronized(commandBatchHolder) {
[commandBatchHolder addObject:result];
}
[self performSelectorOnMainThread:@selector(executePending) withObject:nil waitUntilDone:NO];
});
}
}
}
插件的執(zhí)行由CDVCommandQueue
管理漱竖,每個(gè)CDVViewController
有自己的隊(duì)列禽篱,有兩個(gè)重要的成員變量。
/* 二維數(shù)組闲孤,存儲(chǔ)著所有插件調(diào)用的json */
NSMutableArray* _queue;
/* 記錄開(kāi)始調(diào)用的時(shí)間 */
NSTimeInterval _startExecutionTime;
executePending
負(fù)責(zé)執(zhí)行命令隊(duì)列中待執(zhí)行的插件谆级,具體實(shí)現(xiàn)就是遍歷執(zhí)行二維數(shù)組queue
。
- (void)executePending
{
// 如果已經(jīng)開(kāi)始執(zhí)行了讼积,返回
if (_startExecutionTime > 0) {
return;
}
@try {
// 記錄開(kāi)始執(zhí)行的時(shí)間
_startExecutionTime = [NSDate timeIntervalSinceReferenceDate];
// 遍歷_queue
while ([_queue count] > 0) {
NSMutableArray* commandBatchHolder = _queue[0];
NSMutableArray* commandBatch = nil;
@synchronized(commandBatchHolder) {
if ([commandBatchHolder count] == 0) {
break;
}
commandBatch = commandBatchHolder[0];
}
// 遍歷commandBatch
while ([commandBatch count] > 0) {
@autoreleasepool {
// 取出commandBatch的第一條數(shù)據(jù)肥照,并移除
NSArray* jsonEntry = [commandBatch cdv_dequeue];
if ([commandBatch count] == 0) {
[_queue removeObjectAtIndex:0];
}
// 用插件調(diào)用json信息,創(chuàng)建CDVInvokedUrlCommand
CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
// 調(diào)用插件
[self execute:command]);
}
// 對(duì)于性能的一個(gè)優(yōu)化勤众,后面會(huì)詳細(xì)說(shuō)
if (([_queue count] > 0) && ([NSDate timeIntervalSinceReferenceDate] - _startExecutionTime > MAX_EXECUTION_TIME)) {
[self performSelector:@selector(executePending) withObject:nil afterDelay:0];
return;
}
}
}
} @finally
{
_startExecutionTime = 0;
}
}
用Runloop優(yōu)化性能
Cordova對(duì)于插件的執(zhí)行進(jìn)行了優(yōu)化舆绎,保證頁(yè)面的流程度,運(yùn)用了RunLoop们颜,巧妙的將代碼分割為多塊分次執(zhí)行吕朵,避免由于插件執(zhí)行導(dǎo)致主線程阻塞,影響頁(yè)面繪制窥突,導(dǎo)致掉幀努溃。具體代碼如下:
// CDVCommandQueue.m
// MAX_EXECUTION_TIME ≈ 1s / 60 / 2
// 計(jì)算出繪制一幀時(shí)間的一半
static const double MAX_EXECUTION_TIME = .008;
// 判斷本次執(zhí)行時(shí)間,如果大于MAX_EXECUTION_TIME阻问,調(diào)用performSelector:withObject:afterDelay梧税,結(jié)束本次調(diào)用
if (([_queue count] > 0) && ([NSDate timeIntervalSinceReferenceDate] - _startExecutionTime > MAX_EXECUTION_TIME)) {
[self performSelector:@selector(executePending) withObject:nil afterDelay:0];
return;
}
優(yōu)化策略分析:
- 將隊(duì)列中的插件分割為很多小塊來(lái)執(zhí)行
- 開(kāi)始執(zhí)行
executePending
方法時(shí),記錄開(kāi)始時(shí)間称近,每次執(zhí)行完一個(gè)插件方法后第队,判斷本次執(zhí)行時(shí)間是否超過(guò)MAX_EXECUTION_TIME
,如果沒(méi)有超過(guò)刨秆,繼續(xù)執(zhí)行凳谦,如果超過(guò)了MAX_EXECUTION_TIME
,調(diào)用performSelector:withObject:afterDelay
衡未,結(jié)束本次調(diào)用 - 如果要保證UI流暢尸执,需要滿(mǎn)足條件
CPU時(shí)間 + GPU時(shí)間 <= 1s/60
, 為了給GPU留下足夠的時(shí)間渲染缓醋,要盡量讓CPU占用時(shí)間小于1s/60/2
- Runloop執(zhí)行的流程如下圖所示剔交,系統(tǒng)在收到
kCFRunLoopBeforeWaiting
(線程即將休眠)通知時(shí),會(huì)觸發(fā)一次界面的渲染改衩,也就是在完成source0
的處理后 -
source0
在這里就是插件的執(zhí)行代碼岖常,在kCFRunLoopBeforeWaiting
通知之前,如果source0
執(zhí)行時(shí)間過(guò)長(zhǎng)就會(huì)導(dǎo)致界面沒(méi)有得到及時(shí)的刷新葫督。 - 函數(shù)
performSelector:withObject:afterDelay
竭鞍,會(huì)將方法注冊(cè)到Timer
,結(jié)束source0
調(diào)用橄镜,開(kāi)始渲染界面偎快。界面渲染完成后,Runloop
開(kāi)始sleep
洽胶,然后被timer
喚醒又開(kāi)始繼續(xù)處理source0
晒夹。
插件方法執(zhí)行
方法最終的執(zhí)行在方法execute:
中,從command中取出要執(zhí)行的插件類(lèi)、方法丐怯、參數(shù)喷好,然后執(zhí)行方法。
- (BOOL)execute:(CDVInvokedUrlCommand*)command
{
// 獲取插件實(shí)例
CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className];
BOOL retVal = YES;
double started = [[NSDate date] timeIntervalSince1970] * 1000.0;
NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName];
SEL normalSelector = NSSelectorFromString(methodName);
if ([obj respondsToSelector:normalSelector]) {
((void (*)(id, SEL, id))objc_msgSend)(obj, normalSelector, command);
} else {
// There's no method to call, so throw an error.
NSLog(@"ERROR: Method '%@' not defined in Plugin '%@'", methodName, command.className);
retVal = NO;
}
double elapsed = [[NSDate date] timeIntervalSince1970] * 1000.0 - started;
// 監(jiān)控插件方法執(zhí)行時(shí)間读跷,打印出大于10ms的方法
if (elapsed > 10) {
NSLog(@"THREAD WARNING: ['%@'] took '%f' ms. Plugin should use a background thread.", command.className, elapsed);
}
return retVal;
}
原生回調(diào)js
原生方法執(zhí)行完成后梗搅,會(huì)把結(jié)果返回給js,調(diào)用方法sendPluginResult:callbackId:
效览,用CDVPluginResult
來(lái)傳遞回調(diào)參數(shù)无切,用callbackId
來(lái)區(qū)分是哪次調(diào)用(callbackId由js產(chǎn)生)。
// CDVCommandDelegateImpl.m
- (void)sendPluginResult:(CDVPluginResult*)result callbackId:(NSString*)callbackId
{
// 判斷callbackId長(zhǎng)度是否小于100
// 用正則表達(dá)式"[^A-Za-z0-9._-]"來(lái)驗(yàn)證callbackId
if (![self isValidCallbackId:callbackId]) {
return;
}
// 狀態(tài)碼
int status = [result.status intValue];
// 是否持續(xù)回調(diào)
BOOL keepCallback = [result.keepCallback boolValue];
// 會(huì)帶哦參數(shù)
NSString* argumentsAsJSON = [result argumentsAsJSON];
// 執(zhí)行js方法丐枉,回調(diào)
NSString* js = [NSString stringWithFormat:@"cordova.require('cordova/exec').nativeCallback('%@',%d,%@,%d, %d)", callbackId, status, argumentsAsJSON, keepCallback, debug];
[self evalJsHelper:js];
}
CDVPlugin注冊(cè)與初始化
我們先看看配置文件中插件的定義:
<!-- 定義插件名為HandleOpenUrl的插件 -->
<feature name="HandleOpenUrl">
<!-- 對(duì)應(yīng)的iOS類(lèi)名是CDVHandleOpenURL -->
<param name="ios-package" value="CDVHandleOpenURL" />
<!-- 需要默認(rèn)加載的插件 -->
<param name="onload" value="true" />
</feature>
加載默認(rèn)插件
在CDVViewController
的viewDidLoad
時(shí)哆键,從Cordova的配置文件config.xml
中,讀取出需要默認(rèn)加載的插件瘦锹,遍歷初始化洼哎。
CDVViewController
中初始化默認(rèn)插件代碼。
- (void)viewDidLoad {
// Load settings
[self loadSettings];
if ([self.startupPluginNames count] > 0) {
[CDVTimer start:@"TotalPluginStartup"];
for (NSString* pluginName in self.startupPluginNames) {
[CDVTimer start:pluginName];
// 初始化插件
[self getCommandInstance:pluginName];
[CDVTimer stop:pluginName];
}
[CDVTimer stop:@"TotalPluginStartup"];
}
}
插件初始化
插件初始化的過(guò)程:
- 加載配置文件
config.xml
- 根據(jù)插件名獲取對(duì)應(yīng)類(lèi)名
- 根據(jù)類(lèi)名從緩存中查找沼本,如果命中直接返回
- 沒(méi)有緩存重新創(chuàng)建一個(gè)實(shí)例噩峦,并寫(xiě)入緩存
插件初始化的入口是getCommandInstance
,傳入?yún)?shù)是插件名稱(chēng)抽兆,返回一個(gè)插件的實(shí)例對(duì)象识补。
- (id)getCommandInstance:(NSString*)pluginName
{
// 在pluginsMap中用插件名稱(chēng)獲取類(lèi)名(插件名不區(qū)分大小寫(xiě))
NSString* className = [self.pluginsMap objectForKey:[pluginName lowercaseString]];
// 沒(méi)有配置插件,初始化失敗
if (className == nil) {
return nil;
}
// 從緩存中獲取辫红,如果命中直接返回緩存
id obj = [self.pluginObjects objectForKey:className];
if (!obj) {
// 沒(méi)有緩存凭涂,創(chuàng)建一個(gè)新的實(shí)例
obj = [[NSClassFromString(className)alloc] initWithWebViewEngine:_webViewEngine];
if (obj != nil) {
// 實(shí)例創(chuàng)建成功,注冊(cè)插件
[self registerPlugin:obj withClassName:className];
} else {
NSLog(@"CDVPlugin class %@ (pluginName: %@) does not exist.", className, pluginName);
}
}
return obj;
}
注冊(cè)插件的關(guān)鍵方法registerPlugin:withClassName:
- (void)registerPlugin:(CDVPlugin*)plugin withClassName:(NSString*)className
{
if ([plugin respondsToSelector:@selector(setViewController:)]) {
[plugin setViewController:self];
}
if ([plugin respondsToSelector:@selector(setCommandDelegate:)]) {
[plugin setCommandDelegate:_commandDelegate];
}
// 寫(xiě)入緩存
[self.pluginObjects setObject:plugin forKey:className];
[plugin pluginInitialize];
}
Cordova還提供了插件名注冊(cè)插件的方式贴妻,使用函數(shù)registerPlugin:withPluginName:
切油,實(shí)現(xiàn)方式差不多,就不贅述了名惩。
注冊(cè)插件步驟
- 設(shè)置插件的
viewController
和delegate
- 將插件以
className
為key放入pluginObjects
中澎胡,pluginObjects
是一個(gè)插件的緩存 - 調(diào)用插件的
pluginInitialize
插件銷(xiāo)毀
插件銷(xiāo)毀的時(shí)機(jī)是創(chuàng)建插件的CDVViewController
釋放的時(shí)候,因?yàn)椴寮?shí)例被創(chuàng)建后被緩存map引用娩鹉,對(duì)應(yīng)的銷(xiāo)毀代碼攻谁。
// CDVViewController.m
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[CDVUserAgentUtil releaseLock:&_userAgentLockToken];
[_commandQueue dispose];
[[self.pluginObjects allValues] makeObjectsPerformSelector:@selector(dispose)];
}
// CDVPlugin.m
- (void)dispose
{
viewController = nil;
commandDelegate = nil;
}
小結(jié)
- 在
config.xml
文件中配置插件,聲明插件與類(lèi)的映射關(guān)系弯予,以及加載策略 - 插件的初始化時(shí)懶加載戚宦,除了
onload
配置為YES
的插件會(huì)默認(rèn)加載,其它插件都是使用時(shí)加載 - 插件使用了兩個(gè)Map來(lái)緩存锈嫩,
pluginObjects
建立了類(lèi)名與插件實(shí)例對(duì)象的映射受楼,pluginsMap
建立了插件名與類(lèi)名的映射垦搬。 - 在一個(gè)
CDVViewController
中,同一個(gè)插件同時(shí)只會(huì)存在一個(gè)實(shí)例
歡迎關(guān)注我的博客