Cordova - iOS 프레임 소스 분석

36366 단어 uijson디자인 모드
최근 2 년 동안 Cordova 프로젝트 를 해 왔 습 니 다. 현재 저 희 는 Cordova 의 jsBridge 를 바탕 으로 양쪽 의 상호작용 을 하고 있 습 니 다. 로 컬 JS 를 불 러 와 렌 더 링 시간 과 화이트 스크린 문 제 를 최적화 시 켰 습 니 다. Cordova 는 우리 에 게 상호작용 의 플러그 인 화, 설정 가능 등 장점 을 가 져 왔 습 니 다. Cordova 의 실현 을 정리 하고 다음은 주요 부분의 소스 코드 를 바탕 으로 분석 과 학습 을 하 겠 습 니 다.
1. 뷰 디 드 로드 에서 무엇 을 했 는 지 살 펴 보 자.
- (void)viewDidLoad
{
    [super viewDidLoad];

    1.     config.xml      ,          。
    // Load settings
    [self loadSettings];

    2.       cordova     
    NSString* backupWebStorageType = @"cloud"; // default value

    id backupWebStorage = [self.settings cordovaSettingForKey:@"BackupWebStorage"];
    if ([backupWebStorage isKindOfClass:[NSString class]]) {
        backupWebStorageType = backupWebStorage;
    }
    [self.settings setCordovaSetting:backupWebStorageType forKey:@"BackupWebStorage"];
    
    [CDVLocalStorage __fixupDatabaseLocationsWithBackupType:backupWebStorageType];

    // // Instantiate the WebView ///

    3.  Cordova Webview,           
    if (!self.webView) {
        [self createGapView];
    }

    // /

    /*
     * Fire up CDVLocalStorage to work-around WebKit storage limitations: on all iOS 5.1+ versions for local-only backups, but only needed on iOS 5.1 for cloud backup.
        With minimum iOS 7/8 supported, only first clause applies.
     */
    if ([backupWebStorageType isEqualToString:@"local"]) {
        NSString* localStorageFeatureName = @"localstorage";
        if ([self.pluginsMap objectForKey:localStorageFeatureName]) { // plugin specified in config
            [self.startupPluginNames addObject:localStorageFeatureName];
        }
    }
    4. config.xml   ,   onload true       
    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"];
    }

    // /
    5.  url
    NSURL* appURL = [self appUrl];

    6.  webView userAgent  ,  url
    [CDVUserAgentUtil acquireLock:^(NSInteger lockToken) {
        _userAgentLockToken = lockToken;
        [CDVUserAgentUtil setUserAgent:self.userAgent lockToken:lockToken];
        if (appURL) {
            NSURLRequest* appReq = [NSURLRequest requestWithURL:appURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
            [self.webViewEngine loadRequest:appReq];
        } else {
            NSString* loadErr = [NSString stringWithFormat:@"ERROR: Start Page at '%@/%@' was not found.", self.wwwFolderName, self.startPage];
            NSLog(@"%@", loadErr);

            NSURL* errorUrl = [self errorURL];
            if (errorUrl) {
                errorUrl = [NSURL URLWithString:[NSString stringWithFormat:@"?error=%@", [loadErr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]] relativeToURL:errorUrl];
                NSLog(@"%@", [errorUrl absoluteString]);
                [self.webViewEngine loadRequest:[NSURLRequest requestWithURL:errorUrl]];
            } else {
                NSString* html = [NSString stringWithFormat:@" %@ ", loadErr];
                [self.webViewEngine loadHTMLString:html baseURL:nil];
            }
        }
    }];
}

다음 단계 에서 구체 적 으로 어떻게 실현 되 었 는 지 분석한다.
2. 먼저 프로필 을 불 러 올 지, 코드 를 볼 지:
- (void)loadSettings
{
    1.config.xml           
    CDVConfigParser* delegate = [[CDVConfigParser alloc] init];

    [self parseSettingsWithParser:delegate];
    2.        self,   CDVViewController,  pluginsMap        xml        ,key      feature,value     。startupPluginNames          onload true   ,        ,settings      xml  web     ,      。
    // Get the plugin dictionary, whitelist and settings from the delegate.
    self.pluginsMap = delegate.pluginsDict;
    self.startupPluginNames = delegate.startupPluginNames;
    self.settings = delegate.settings;
    3.  wwwFolderName www,wwwFolderName        。
    // And the start folder/page.
    if(self.wwwFolderName == nil){
        self.wwwFolderName = @"www";
    }
    4.startPage       ,        xml   ,             index.html。
    if(delegate.startPage && self.startPage == nil){
        self.startPage = delegate.startPage;
    }
    if (self.startPage == nil) {
        self.startPage = @"index.html";
    }

    // Initialize the plugin objects dict.
    self.pluginObjects = [[NSMutableDictionary alloc] initWithCapacity:20];
}

 3. Cordova 의 webview 를 설정 하 는 것 은 분석 에 중심 을 두 는 것 이 중요 합 니 다.
- (UIView*)newCordovaViewWithFrame:(CGRect)bounds
{
      
1. webView , CDVViewController webView , 。 , , UIWebView , WKWebView, 。 NSString
* defaultWebViewEngineClass = @"CDVUIWebViewEngine"; NSString* webViewEngineClass = [self.settings cordovaSettingForKey:@"CordovaWebViewEngine"]; if (!webViewEngineClass) { webViewEngineClass = defaultWebViewEngineClass; } 2. webView if (NSClassFromString(webViewEngineClass)) { self.webViewEngine = [[NSClassFromString(webViewEngineClass) alloc] initWithFrame:bounds];
3. webEngine nil, protocol, url, , 。
// if a webView engine returns nil (not supported by the current iOS version) or doesn't conform to the protocol, or can't load the request, we use UIWebView if (!self.webViewEngine || ![self.webViewEngine conformsToProtocol:@protocol(CDVWebViewEngineProtocol)] || ![self.webViewEngine canLoadRequest:[NSURLRequest requestWithURL:self.appUrl]]) { self.webViewEngine = [[NSClassFromString(defaultWebViewEngineClass) alloc] initWithFrame:bounds]; } } else { self.webViewEngine = [[NSClassFromString(defaultWebViewEngineClass) alloc] initWithFrame:bounds]; } 4. webView if ([self.webViewEngine isKindOfClass:[CDVPlugin class]]) { [self registerPlugin:(CDVPlugin*)self.webViewEngine withClassName:webViewEngineClass]; } 5. webView return self.webViewEngine.engineWebView; }

이 부분 은 약간 추상 적 입 니 다. 사실은 프로 토 콜 을 대상 으로 하 는 프로 그래 밍 사상 을 바탕 으로 인터페이스 와 시 도 를 분리 시 켰 습 니 다. id webViewEngine 은 실제 적 으로 id 유형 을 가리 키 고 CDVWebViewEngineProtocol 프로 토 콜 의 대상 을 따 랐 습 니 다. 즉, CDVWebViewEngineProtocol 이 누 출 된 인 터 페 이 스 를 실현 할 수 있 습 니 다.이렇게 하면 우 리 는 추상 류 가 이 협 의 를 따 르 게 하면 협의 에서 정 의 된 방법 과 속성 을 실현 하여 인터페이스 분 리 를 실현 할 수 있다.같은 이치 로 webView Engine 추상 류 는 겉으로 볼 때 webview 로 실제 적 으로 webView 를 추출 하여 분리 시 키 고 결합 을 실현 합 니 다.
4. 웹 뷰 엔진 대상 이 무엇 을 했 는 지 자세히 말씀 드 리 겠 습 니 다.코드 를 보 든 가.
- (instancetype)initWithFrame:(CGRect)frame
{
    1.     
    self = [super init];
    if (self) {
        2.             WebView,                   webView,   DLPanableWebView        webView,         web     DLPanableWebView    ,          。
        Class WebClass = NSClassFromString(@"DLPanableWebView");
        if ([[WebClass class] isSubclassOfClass:[UIWebView class]]) {
            self.engineWebView = [[WebClass alloc] initWithFrame:frame];
        } else {
            self.engineWebView = [[UIWebView alloc] initWithFrame:frame];
        }
        NSLog(@"Using UIWebView");
    }

    return self;
}
- (void)pluginInitialize
{
    // viewController would be available now. we attempt to set all possible delegates to it, by default
    1.           web。
    UIWebView* uiWebView = (UIWebView*)_engineWebView;
    
    2.            Controller       UIWebView   ,     ,      , web             controller            。
    if ([self.viewController conformsToProtocol:@protocol(UIWebViewDelegate)]) {
        self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:(id )self.viewController];
        uiWebView.delegate = self.uiWebViewDelegate;
    } else {
        3.    controller    ,          。              HWebViewDelegate,    web                   ,          。
        self.navWebViewDelegate = [[CDVUIWebViewNavigationDelegate alloc] initWithEnginePlugin:self];

        Class TheClass = NSClassFromString(@"HWebViewDelegate");
        if ([TheClass isSubclassOfClass:[CDVUIWebViewDelegate class]]) {
            self.uiWebViewDelegate = [[TheClass alloc] initWithDelegate:self.navWebViewDelegate];
        } else {
            self.uiWebViewDelegate = [[CDVUIWebViewDelegate alloc] initWithDelegate:self.navWebViewDelegate];
        }
        // end
        uiWebView.delegate = self.uiWebViewDelegate;
    }

    [self updateSettings:self.commandDelegate.settings];
}

5. 지금까지 플러그 인 설정 과 로 딩 이 완료 되 었 습 니 다. webView 의 구체 적 인 실현 과 프 록 시 설정 도 완성 되 었 습 니 다. 그러면 네 이 티 브 와 js 의 구체 적 인 상호작용 을 말씀 드 리 겠 습 니 다. 주로 네 이 티 브 엔 드 가 무엇 을 했 는 지 말씀 드 리 겠 습 니 다.이것 은 CDVUIWebView Navigation Delegate 클래스 에서 웹 프 록 시 를 실현 하 는 것 이 며, 위 에서 웹 뷰 를 설정 할 때 프 록 시 로 설정 하 는 것 입 니 다.여기 서 의 실현 은 바로 상호작용 의 중요 한 것 이다. 그러면 우 리 는 상세 하 게 말 하 자.
- (BOOL)webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
    1.  url
    NSURL* url = [request URL];
    2.        
    CDVViewController* vc = (CDVViewController*)self.enginePlugin.viewController;
    
    3. url scheme   gap
    if ([[url scheme] isEqualToString:@"gap"]) {
    4.        ,           。
        [vc.commandQueue fetchCommandsFromJs];
        [vc.commandQueue executePending];
        return NO;
    }
    
    /*
     * Give plugins the chance to handle the url
     */
    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;
    }

    /*
     * Handle all other types of urls (tel:, sms:), and requests to load a url in the main webview.
     */
    BOOL shouldAllowNavigation = [self defaultResourcePolicyForURL:url];
    if (shouldAllowNavigation) {
        return YES;
    } else {
        [[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
    }
    
    return NO;
}

여기 서 두 가지 방법 을 중점적으로 분석한다. [vc. com mand Queue fetch Commands FromJs];그리고 [vc. comandQueue execute Pending];우리 가 막 은 구체 적 인 실현 이기 도 하 다.코드 를 보 든 가.
- (void)fetchCommandsFromJs
{
    __weak CDVCommandQueue* weakSelf = self;
    NSString* js = @"cordova.require('cordova/exec').nativeFetchMessages()";
    1.  jsBridge  js  ,js               
    [_viewController.webViewEngine evaluateJavaScript:js
                                    completionHandler:^(id obj, NSError* error) {
        if ((error == nil) && [obj isKindOfClass:[NSString class]]) {
            NSString* queuedCommandsJSON = (NSString*)obj;
            CDV_EXEC_LOG(@"Exec: Flushed JS->native queue (hadCommands=%d).", [queuedCommandsJSON length] > 0);
            2.     。
            [weakSelf enqueueCommandBatch:queuedCommandsJSON];
            // this has to be called here now, because fetchCommandsFromJs is now async (previously: synchronous)
           3.    
            [self executePending];
        }
    }];
}

- (void)enqueueCommandBatch:(NSString*)batchJSON
{
    1.    。
    if ([batchJSON length] > 0) {
        NSMutableArray* commandBatchHolder = [[NSMutableArray alloc] init];
        2.   queue 。
        [_queue addObject:commandBatchHolder];
        3.  json   4M    ,               。
        if ([batchJSON length] < JSON_SIZE_FOR_MAIN_THREAD) {
            4.     commandBatchHolder   。
            [commandBatchHolder addObject:[batchJSON cdv_JSONObject]];
        } else {
            dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^() {
                NSMutableArray* result = [batchJSON cdv_JSONObject];
                 5.                              。
                @synchronized(commandBatchHolder) {
                    [commandBatchHolder addObject:result];
                }
                6.        executePending
                [self performSelectorOnMainThread:@selector(executePending) withObject:nil waitUntilDone:NO];
            });
        }
    }
}

6. 지금까지 우 리 는 설 정 된 플러그 인, webView, js 단 에서 전 달 된 인 자 를 받 았 습 니 다. 마지막 단계 가 남 았 습 니 다. 인 자 는 플러그 인 에 어떻게 호출 되 었 습 니까?코드
- (void)executePending
{
    1.  executePending          ,      。
    if (_startExecutionTime > 0) {
        return;
    }
    @try {
        _startExecutionTime = [NSDate timeIntervalSinceReferenceDate];
      2.  queue        ,             。
        while ([_queue count] > 0) {
            NSMutableArray* commandBatchHolder = _queue[0];
            NSMutableArray* commandBatch = nil;
            @synchronized(commandBatchHolder) {
                // If the next-up command is still being decoded, wait for it.
                if ([commandBatchHolder count] == 0) {
                    break;
                }
                commandBatch = commandBatchHolder[0];
            }
            3.  queue       。
            while ([commandBatch count] > 0) {
                4.    。
                @autoreleasepool {
                    5.         ,         。
                    NSArray* jsonEntry = [commandBatch cdv_dequeue];
                    if ([commandBatch count] == 0) {
                        6.         。
                        [_queue removeObjectAtIndex:0];
                    }
                    7.      CDVInvokedUrlCommand        ,                     CDVInvokedUrlCommand    。
                    CDVInvokedUrlCommand* command = [CDVInvokedUrlCommand commandFromJson:jsonEntry];
                    CDV_EXEC_LOG(@"Exec(%@): Calling %@.%@", command.callbackId, command.className, command.methodName);
                    8.        。
                    if (![self execute:command]) {
#ifdef DEBUG
                            NSString* commandJson = [jsonEntry cdv_JSONString];
                            static NSUInteger maxLogLength = 1024;
                            NSString* commandString = ([commandJson length] > maxLogLength) ?
                                [NSString stringWithFormat : @"%@[...]", [commandJson substringToIndex:maxLogLength]] :
                                commandJson;

                            DLog(@"FAILED pluginJSON = %@", commandString);
#endif
                    }
                }
                9.  runloop    ,        runloop   ,       UI       。
                // Yield if we're taking too long.
                if (([_queue count] > 0) && ([NSDate timeIntervalSinceReferenceDate] - _startExecutionTime > MAX_EXECUTION_TIME)) {
                    [self performSelector:@selector(executePending) withObject:nil afterDelay:0];
                    return;
                }
            }
        }
    } @finally
    {
        _startExecutionTime = 0;
    }
}


- (BOOL)execute:(CDVInvokedUrlCommand*)command
{
    if ((command.className == nil) || (command.methodName == nil)) {
        NSLog(@"ERROR: Classname and/or methodName not found for command.");
        return NO;
    }

    1.  native          。
    CDVPlugin* obj = [_viewController.commandDelegate getCommandInstance:command.className];
    2.     CDVPlugin。
    if (!([obj isKindOfClass:[CDVPlugin class]])) {
        NSLog(@"ERROR: Plugin '%@' not found, or is not a CDVPlugin. Check your plugin mapping in config.xml.", command.className);
        return NO;
    }
    BOOL retVal = YES;
    double started = [[NSDate date] timeIntervalSince1970] * 1000.0;
    // Find the proper selector to call.
    NSString* methodName = [NSString stringWithFormat:@"%@:", command.methodName];
    3.        。
    SEL normalSelector = NSSelectorFromString(methodName);
    4.     。
    if ([obj respondsToSelector:normalSelector]) {
        // [obj performSelector:normalSelector withObject:command];
        ((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;
    if (elapsed > 10) {
        NSLog(@"THREAD WARNING: ['%@'] took '%f' ms. Plugin should use a background thread.", command.className, elapsed);
    }
    return retVal;
}

여기까지 전체 플러그 인의 호출 과정 이 끝 났 습 니 다. plugin 을 생 성 합 니 다. 프레임 워 크 는 공장 의 디자인 모델 을 바탕 으로 서로 다른 유형의 이름 을 통 해 CDVPlugin 의 서로 다른 대상 을 계승 한 다음 에 해당 하 는 plugin 대상 에서 해당 하 는 방법 을 수행 합 니 다.
 
다음으로 전송:https://www.cnblogs.com/byxixiblogs/p/9980533.html

좋은 웹페이지 즐겨찾기