본문 바로가기

프로그래밍 세상/폰갭 이야기

cordova-1.9.0.js 파악하기 (2) - iOS


이번엔 define require 가 사용된 exec 함수에 대해서 알아보자.

define, require 를 간단하게 설명하자면,

define 은 private 속성인 modules 객체에 id 와 function 을 등록하는 과정이며, 

require 는 define 된 function 으로 객체를 생성하여 리턴해준다.

define 은 Class 를 작성하는 것이라면, require 는 객체(인스턴스)를 생성하는 것으로 볼 수 있다.

이 exec 를 define 하는 것만으로 99줄이므로 간단하게 살펴보도록 하겠다.

 

▶ Cordova for iOS - define        

// file: lib/ios/exec.js
define("cordova/exec", function(require, exports, module) {
    /**
     * Creates a gap bridge iframe used to notify the native code about queued
     * commands.
     *
     * @private
     */
var cordova = require('cordova'),
    utils = require('cordova/utils'),
    gapBridge,
    createGapBridge = function() {
        gapBridge = document.createElement("iframe");
        gapBridge.setAttribute("style", "display:none;");
        gapBridge.setAttribute("height","0px");
        gapBridge.setAttribute("width","0px");
        gapBridge.setAttribute("frameborder","0");
        document.documentElement.appendChild(gapBridge);
    },
    channel = require('cordova/channel');
     
module.exports = function() {
    if (!channel.onCordovaInfoReady.fired) {
        utils.alert("ERROR: Attempting to call cordova.exec()" +
              " before 'deviceready'. Ignoring.");
        return;
    }
     
    var successCallback, failCallback, service, action, actionArgs, splitCommand;
    var callbackId = null;
    if (typeof arguments[0] !== "string") {
        // FORMAT ONE
        successCallback = arguments[0];
        failCallback = arguments[1];
        service = arguments[2];
        action = arguments[3];
        actionArgs = arguments[4];
     
        // Since we need to maintain backwards compatibility, we have to pass
        // an invalid callbackId even if no callback was provided since plugins
        // will be expecting it. The Cordova.exec() implementation allocates
        // an invalid callbackId and passes it even if no callbacks were given.
        callbackId = 'INVALID';
    } else {
        // FORMAT TWO
        splitCommand = arguments[0].split(".");
        action = splitCommand.pop();
        service = splitCommand.join(".");
        actionArgs = Array.prototype.splice.call(arguments, 1);
    }
     
    // Start building the command object.
    var command = {
        className: service,
        methodName: action,
        "arguments": []
    };
     
    // Register the callbacks and add the callbackId to the positional
    // arguments if given.
    if (successCallback || failCallback) {
        callbackId = service + cordova.callbackId++;
        cordova.callbacks[callbackId] =
            {success:successCallback, fail:failCallback};
    }
    if (callbackId !== null) {
        command["arguments"].push(callbackId);
    }
     
    for (var i = 0; i < actionArgs.length; ++i) {
        var arg = actionArgs[i];
        // nulls are pushed to the args now (becomes NSNull)
        if (arg === undefined || arg === null) {
            command["arguments"].push(arg);
        } else if (typeof(arg) == 'object' && !(utils.isArray(arg))) {
            command.options = arg;
        } else {
            command["arguments"].push(arg);
        }
    }
     
    // Stringify and queue the command. We stringify to command now to
    // effectively clone the command arguments in case they are mutated before
    // the command is executed.
    cordova.commandQueue.push(JSON.stringify(command));
     
    // If the queue length is 1, then that means it was empty before we queued
    // the given command, so let the native side know that we have some
    // commands to execute, unless the queue is currently being flushed, in
    // which case the command will be picked up without notification.
    if (cordova.commandQueue.length == 1 && !cordova.commandQueueFlushing) {
        if (!gapBridge) {
            createGapBridge();
        }
        gapBridge.src = "gap://ready";
    }
};
     
});

Line 1 : 주석을 보면 알 수 있듯이 여러 파일로 쪼개어 작업했던 것을 한 파일로 Merge 했음을 알 수 있다. 

Line 2 : define 함수를 사용하여 Class 를 만드는 것으로 첫번째 인수에는 id, 

             두번째 인수는 function 원형이 들어감을 알 수 있다.

             소스를 검색하면 require("cordova/exec") 를 사용하여 인스턴스를 생성할 것이다.

Line 8 : require 로 cordova 인스턴스를 생성한다.

Line 9 : require 로 utils 인스턴스를 생성한다.

Line 12 : iOS Phonegap의 경우 iframe 을 이용하여, Native App 과 WebView 간에 정보 공유를 한다. 

              이러한 iframe 을 생성하는 함수

Line 19 : require 를 이용하여 channel 인스턴스를 생성한다.

Line 22 : require 로 인스턴스를 생성할 때 리턴되는 module.exports 를 작성한다.

Line 23 : 폰갭이 사용할 준비가 되어 있지 않은 상태에서 접근 할 경우 return

Line 31 : phonegap의 경우 Javascript 에서 successCallback, failCallback, service, action, actionArgs 

           를 넘기는데, 이러한 인수들을 변수에 저장하는 과정

            successCallback : 사용자에 의해 작성된 함수로 exec 성공시 호출된다. ( Script 실행 )

            failCallback : 사용자에 의해 작성된 함수로 exec 실패시 호출된다. ( Script 실행 )

            service : 폰갭 플러그인 이름 ( Script ↔ Native App )

                       폰갭 플러그인 중 실행하고자 하는 이름을 입력한다.

            action : 폰갭 플러그인 동작을 위한 이름 ( Native App 실행 )

                       사용자 플러그인의 경우 Native App 에서 action 명으로 어떤 동작을 할지 결정할 수 있다.

            actionArgs : service 와 action 이 넘어 올 때 파라메터가 넘어온다. ( Native App ↔ Script)

Line 53 : command 객체 생성. 추후 String 으로 만들기 위한 JSON 객체

              service, action 저장

Line 61 : successCallback, failCallback 등록

Line 66 : Callback Id 등록

Line 70 : 넘어온 actionArgs 를 등록

Line 85 : JSON.stringify 를 이용해 command 객체를 String 으로 생성

Line 91 : cordova.commandQueue 이 하나인 경우 gapBridge.src 를 "gap://ready" 기본 String 형으로 저장

           

위와 같이 define, require 를 사용하는 exec 를 간단하게 살펴보았다.

exec 함수는 사용자 플러그인으로 생성할 경우 Javascript 에서 작성하여, 실행되도록 하는 것으로

WebView의 Javascript 와 Native App 을 연결시켜주는 역할을 담당한다.

           

define 과 require 를 이용하여, 여러 객체들이 cordova-1.9.0.js 에 포함되어 있는데,

define, require, exec 의 개념을 어느 정도 알고 있다면, 

다른 객체들을 해석하는 데는 그리 어렵지 않을 것이다.

           

지금 이 글을 포스팅 하고 있는 나 조차도 Javascript의 여러 기술들과 Phonegap의 js 에 대해서

자세히 알지는 못한다.

           

이러한 구조로 구성되어 있고, 개략적인 개념만 파악하고 있는 것으로 만족해야겠다.