IT_Programming/Objective-C · Swift · iOS

[펌] iPhone에서 하이브리드 앱 개발을 위해 Javascript와 Objective-C의 상호 호출하는 방법

JJun ™ 2013. 2. 1. 04:40



 출처

 http://blog.saltfactory.net/86

 : http://kka7.tistory.com/60





HTML5 인기 때문에 요즘 하이브리드 앱의 인기는 계속적으로 상승하는 분위기이다

HTML5에서 추가된 속성으로 마치 네이티브와 같은 개발을 개발 방법으로 할수 있게 되었다

또한 실제 네이티브 코드로 작성하면 복잡하고 어려운 부분도 javascript css3 편리하게 

구현할 수도 있다


 하지만 내장 브라우저(webkit)에서만 모든 프로세스를 처리하기에는 브라우저 자체도 

하나의 app이기 때문에 메모리와 cpu 사용의 한계가 있다. HTML5 만들어서 사용하는 앱이 

과연 성능이 네이티브 앱과 같은 성능을 내어 주는지 궁금하기도 해서 여러 자문을 구하는데 

몇몇 기능을 제외하고 순수 javascript로만 구현하면 네이티브보다 성능이 좋게 나오지 않는다고 

한다는 이야기를 들었다


javascript 인터프리터로 모든것을 처리하기에는 한계가 있기 때문에 핵심부분 데이터 처리나 

device api 호출하기 위해서 javascript에서 objective-c 메소드를 호출해야하는 경우가 

생긴다


iOS 이러한 webkit 한계를 극복하기 위해서 objective-c 메소드를 호출하는 방법을 

제공하고 있다물론 objective-c에서 javascript 호출하는 방법도 제공한다


iOS 이러한 기능을 만들어 놓았기 때문에 개발자가 보다 나은 방법으로 

네이티비브 코드의 한계와 코드의 한계를 이해하고 나은 방법으로 

하이브리드 앱을 개발할 있다.



1. Objective-C에서 Javascript 호출하는 방법


Objective-C에서 Javascript 호출하는 방법은 문자열을 evaluation 시키는 방법이다

AppViewController 새로 생성하고 Outlet으로 UIWebView appWebView @property 

@synthesize 만든다. 그리고 UIBarButtonItem 누르면 onCallJavascriptButton:  

호출될 수 있게 선언한다


그리고 구현클래스에서 IBAction 연결된 onCallJavascriptButton: 메소드를 구현하는데 

이때 stringByEvaluationJavascriptFromString: 메소드를 이용해서 Javascript 메소드를 

문자열로 입력한다


실제 Javascript app.html 파일 안에 <script>callJavascriptFromObjectiveC()</script> 

구현이 된다, stringByEvaluationJavascriptFromString: 메소드를 이용해 Javascript 이름을 

이용하여 Javascript 메소드를 호출하는 것이다.


AppViewController.h

#import <UIKit/UIKit.h>


@interface AppViewController : UIViewController<UIWebViewDelegate>

@property (retainnonatomicIBOutlet UIWebView *appWebView;


- (IBAction)onCallJavascriptButton:(id)sender;

@end


#import "AppViewController.h"


@interface AppViewController ()


@end


AppViewController.m

@implementation AppViewController

@synthesize appWebView;


- (IBAction)onCallJavascriptButton:(id)sender {

    [appWebView stringByEvaluatingJavaScriptFromString:@"callJavascriptFromObjectiveC();"];

}


- (void)viewDidLoad

{

    [super viewDidLoad];

    

    appWebView.delegate = self;

    

    NSString *path = [[NSBundle mainBundlepathForResource:@"app" ofType:@"html" inDirectory:@"www"];

    NSURL *url = [NSURL fileURLWithPath:path];

    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    [appWebView loadRequest:request];

   

}


app.html

<!DOCTYPE html>

<html>

    <head>

        <meta charset="utf-8"/>

        <meta name = "viewport" content = "width = device-width"/>

        <title>Hybrid App</title>

        <script type="text/javascript">

            

            function callJavascriptFromObjectiveC() {

                alert('called javascript function by objective-c');

            }

        </script>

     </head>

    <body>

        <div id="container">     

            Hello Hybrid App

        </div>

    </body>

</html>



2. Javascript에서 Objective-C 호출하는 방법


Javascript에서 Objective-C 호출하는 방법은 커스텀 Scheme 이용하는 방법이다

안드로이드에서도 URI 리소스에 접근할수 있는데 이렇게 외부에서 URI scheme 이용해서 

특정 메소드에서 접근하는 방법을 사용하면 웹에서 javascript hyper text 어플리케이션의 

특정 메소드를 호출할 있는 브릿지를 구현할 있다. app.html button 하나 만들고 

버턴이 눌러지면 javascript 구현한 callObjectiveCFromJavascript() 호출한다


메소드 안에는 window.location 이용해서 커스텀 scheme (ex. jscall://callObjectiveCFromJavascript) 이동하는 코드를 넣어둔다


그러면 webView delegate 메소드의 webView: souldStartLoadWithRequest:navigationType 

메소드를 호출하는데 이 때 특정 메소드가 실행되게 구현하면 된다


예제 코드에서는 objective-c 메소드 속에 단순하게 Log 출력하는 것만 넣어 두었다.


app.html

<!DOCTYPE html>

<html>

    <head>

        <meta charset="utf-8"/>

        <meta name = "viewport" content = "width = device-width"/>

        <title>Hybrid App</title>

        <script type="text/javascript">

            

            function callJavascriptFromObjectiveC() {

                alert('called javascript function by objective-c');

            }

            

            function callObjectiveCFromJavascript(){

                window.location="jscall://callObjectiveCFromJavascript";

            }

            

        </script>

     </head>

    <body>

        <div id="container">     

            <h3>Hello Hybrid App </h3>

            

            <button onclick="callObjectiveCFromJavascript();">Call Objective-C</button>

            

        </div>

    </body>

</html>


AppViewController.m


- (void)callObjectiveCFromJavascript {

    NSLog(@"called objective-c from javascript");

}



- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request 

                                        navigationType:(UIWebViewNavigationType)navigationType

{

    if ([[[request URL] absoluteString] hasPrefix:@"jscall:"]) {

        

        NSString *requestString = [[request URL] absoluteString];

        NSArray *components = [requestString componentsSeparatedByString:@"://"];

        NSString *functionName = [components objectAtIndex:1];

        

        [self performSelector:NSSelectorFromString(functionName)];


        return NO;

    }

    

    return YES;

}







JavaScript to Native



안드로이드에서 Bridge를 사용해서 Web에서 JavaScript 호출하면 Native를 호출 할수가 있었다.

안드로이드 예제

mWebView.getSettings().setJavaScriptEnabled(true);
mWebView.addJavascriptInterface(new AndroidBridge(),"MyScript");
public class AndroidBridge {
    public void webViewClose() {
        Log.d(TAG, "웹뷰 닫기~")
    }
}

iOS7 이후 부터는 안드로이드 처리하는게 가능하다.
(그 전에는 shouldStartLoadWithRequest에서 scheme 파싱해서 처리 했었다.)

자세한 내용은 다음 WWDC를 참조 (WWDC 615 - Integrating JavaScript into Native Apps)

사용방법은 다음과 같다.

  1. 호출할 프로토콜을 JSExport상속받아 생성
  2. 자바스크립트 처리할 클래스 만들고 프로토콜 처리

    @objc protocol ScriptProtocol : JSExport  {
        func webViewClose()
    }
    @objc class MyScript: NSObject {
        var webView: UIWebView
        init(webView: UIWebView) {
            self.webView = webView
        }
    }
    extension MyScript: ScriptProtocol {
        func webViewClose() {
            print("웹뷰 닫기~")
        }
    }
  3. WebView 로딩후 자바스크립트로 클래스와 함수 등록

    웹뷰 에서 delegate 설정하고 webViewDidFinishLoad에서 JavaScript를 실행시켜주면 된다.

    webView.delegate = self
    ...
    extension ViewController: UIWebViewDelegate {
        func webViewDidFinishLoad(_ webView: UIWebView) {
            let key = "documentView.webView.mainFrame.javaScriptContext"
            if let context = webView.value(forKeyPath: key) as? JSContext {
                context.setObject(MyScript(self.webView), forKeyedSubscript: "MyScript" as (NSCopying & NSObjectProtocol)!)
                context.evaluateScript("function webViewClose() {webViewClose();}")
            }
        }
    }

웹(JavaScript)에서는 다음과 같이 설정 되어 있으면 된다.

function webClose() {
    window.MyScript.webViewClose();
}
<button onclick="webClose();">종료</button>