모바일 웹 디버깅 툴 - Weinre

국내 모바일 브라우저 시장 점유율은 2011년 12월 현재 Webkit 계열이 99% 이상의 점유율을 가지고 있다(Statcounter 기준, Android, iPhone, iPod Touch, Dolfin 을 합한 수치).

하지만 Webkit 계열의 브라우저로 천하통일되었더라도 모바일 사이트를 구축하는 것은 여전히 쉽지 않은 일이다.

단말기가 다양할 뿐 아니라 단말기마다 사용된 웹킷 브라우저 버전이 각각 다르고, 단말기에 적용된 OS 버전에 따라서도 다르다. 심지어는 제조사의 커스터마이징이 이루어진 경우도 심심치 않게 볼 수 있다. 같은 회사에서 나온 라인업(갤럭시 S, 갤럭시 K 등) 에서도 다르게 동작하는 경우마저 있다. 

일반적인 모바일 사이트를 개발은 사파리, 크롬과 같은 PC용 웹킷 브라우저에서 이루어지고, 이후 모바일 브라우저에서 확인과정을 거치는 경우가 많다.

하지만 모바일 브라우저에서 명백히 잘못된 작동을 하는 경우에도 Firebug나 Web Inspector 등 디버깅 툴을 내장하고 있지 않으므로 정상 동작하는 코드로 되돌려내는 것은 결국 삽질을 통할 수 밖에 없다. Developer Toolbar가 지원되지 않는 IE6 대응 사이트를 만드는 것과 별반 다르지 않다.

Weinre 는 내장 디버깅 툴이 없는 모바일 브라우저를 위한 원격 디버깅 툴이다. Webkit 계열의 개발자 도구인 Web Inspector 를 사용할 수 있도록 해준다.

Weinre Elements Panel

모바일에서도 위와 같은 디버깅이 가능해진다!

 

Weinre 개요

처음보면 어떻게 발음해야할지 갸우뚱한 이 프로그램은 포도주 양조장이라는 '와이너리'라 부른다. Patrick Mueller http://muellerware.org/ 가 만든 이 프로젝트는 웹킷의 원격 디버깅 프로토콜 http://www.webkit.org/blog/1620/webkit-remote-debugging/ 을 이용해 개발되었고 하이브리드 앱 개발 도구인 Apache Callback(Phonegap) 의 서브 프로젝트이다.

Weinre를 통한 디버깅은 세가지 파트로 나뉜다.

  • 서버(Server)
    • java기반(Jetty)의 http 서버이다.
  • 대상(Target)
    • 디버깅할 모바일 디바이스 브라우저. 웹킷 계열만 지원한다.
  • 클라이언트(Client)
    • Safari, Chrome 등에 내장된 Web Inspector와 비슷한 외관의 클라이언트 페이지. -webkit- prefix 를 통한 CSS3만 지원하므로 웹킷 브라우저가 필수.

 

설치 방법

서버

서버는 프로젝트 홈페이지에서 jar 파일을 다운로드 받아 실행한다.

$ java -jar weinre.jar

옵션설명

--help (or -? or -h)

이 도움말 화면을 보여줌.

 

--httpPort [포트번호]

HTTP 서버가 동작할 포트 번호.

기본값 : 8080

 

--boundHost [hostname | ip address | -all-]

서버에 바인딩할 아이피 주소

기본값 : localhost

 

기본값인 localhost 로는 다른 기기에서 접속할 수 없다. 따라서 다른 기기에서 접속하기 위해서는 다른 호스트네임이나 IP 주소를 바인딩해야한다. -all- 을 사용하면 이 기기로 연결가능한 모든 인터페이스를 사용할 수 있다.

 

맥이나 라눅스에서는 ifconfig 을 사용하고, 윈도우에서는 ipconfig 를 사용해 ip 주소를 얻어올 수 있다.

 

--verbose [true | false]

stdout 을 통해 소소한 것까지 출력함. 

기본값: false

 

--reuseAddr [true | false]

Jetty 의 reuseAddr 옵션을 설정함. 

기본값: false

 서버를 주기적으로 재시작할 때 필요할 수 있다.

 

--readTimeout [seconds]

기본값: 5 

--deathTimeout [seconds]

기본값: 3 * readTimeout

 

대상

대상은 디버깅용 JS파일을 임포트 한다. 이 디버깅용 파일은 HTML 소스에 고정적으로 로드하거나, 북마클릿을 통해 동적으로 로드할 수 있다.

<script src="http://a.b.c:8080/target/target-script-min.js#anonymous"></script>

* 여기서 a.b.c:8080 은 설치된 서버의 아이피/도메인을 의미함.

 

클라이언트

이제 사용할 준비가 완료 되었다. 브라우저를 통해 http://a.b.c:8080/client 에 접속하면 아래와 같은 Web Inspector를 볼 수 있다. 

Weinre Remote Debug Client

Weinre Elements Panel

 

활용

아이디를 이용한 여러 프로젝트 분기

스크립트 URL : http://a.b.c:8080/target/target-script-min.js#anonymous

클라이언트 접속 URL: http://a.b.c:8080/client/#anonymous

에서 마지막의 #anonymous 부분을 변경해 서버 하나에 프로젝트마다 또는 각 사용자마다 아이디를 줄 수 있다.

북마클릿의 활용

특히 긴급한 상황에서 북마클릿을 활용해 스크립트를 로딩하면 디버그가 가능하다. 


출처 - http://elegantcoder.com/weinre





하이브리드 앱 개발을 할때 UIWebView에서 Javascript 디버깅하는 방법과 Insepector 사용

2012/04/02 15:13



이전에는 Native 앱 (순수 Objective-C와 C 기반 라이브러리를 이용하여 만든 앱)을 주로 만들었다면, 최근 HTML5 앱 기술이 발달하고 자료가 많아지면서 Hybrid 앱 (웹 개발 기술과 클라이언트 개발 기술을 접목하여 개발하는 앱)이 큰 인기를 얻고 있다. 실제 앱을 개발하면서 느낀 것인데 각각의 장점을 충분히 살릴 수 있으면 어렵게 구현할수 밖에 없는 코드를 아주 효과적으로 개발할 수 있기도 한다. HTML은 Native 앱과 랄리 UIWebView 안에서 HTML, Javascript, CSS로 개발이 되어 렌더링되기 때문에 순수 Objective-C로 개발한 것 보다는 속도가 느리지만 유연하고 상대적으로 개발하기 편리한 언어로 C의 복잡함을 단순하게 구현할 수 있다. 또한 웹 언어로(HTML, Javascript, CSS)로 개발된 코드는 다른 디바이스에서 재사용성이 가능하기 때문에 이론적으로는 코드 재사용성의 장점도 가지고 있다. 왜 이론적이라고 이야기하냐면 사실 각각 디바이스마다 가지고 있는 내장 브라우저 앤진의 해석능력이 다르기 때문에 100% 호환된다고는 말할 수 없기 때문이다. 하지만 Objective-C를 Java로 구현하는 것 보다 상대적으로 매우 완벽하게 재사용성이 가능한것은 사실이다. 

웹언어는 표현력과 개발속도를 빠르게 할 수는 있을지 모르나 물리적인 제약이나 빠른 성능을 요구하는 연산에는 Native 코드보다 비교적 한계가 있기 때문에 그런 작업은 Native 코드에게 맡기기로 한다. 이러한 이유에서 순수 웹 기술로 개발하거나 클라이언트 개발 기술로 개발하는 것 보다 서로의 장점을 접목해서 개발하려는 시도가 많이 일어나고 있다. 이전에 소개한 아이폰 앱을 개발할 때  iPhone에서 하이브리드 앱 개발을 위해 Javascript와 Objective-C의 상호 호출하는 방법에 대한 글을 역시 그러한 방법중에 한가지로 사용이 된다.

그런데 실제 Hybrid 앱을 개발하려면 두가지 환경에서 개발을 해야한다. 하나는 웹기반이고 하나는 클라이언트 기반이다. 더 쉽게 말하면 하나는 웹브라우저에서 실행되는 결과를 확인해야하고 하나는 Xcode에서 결과를 확인해야한다는 이야기다. Xcode는 LLVM의 훌륭한 컴파일 때문에 에러라던지 디버깅을 매우 편리하게 할 수 있다. 하지만 UIWebView 안에 들어있는 코드들에 대한 디버깅은 전혀할 수 가 없다.

웹 프로그램을 개발할때 웹 개발자들은 웹 화면에 나타나는 코드를 분석하거나 브라우저 앤진에서 연산되는 코드들을 디버깅한다.  이 때 사용되는 대표적인 툴이 Firebug나 Webkit Inspector 가 있다. Firebug는 Firefox 브라우저에서 동작하는 웹 개발툴로 현재의 Firefox의 명성을 유지하는데 가장 큰 역활을한 플러그인이 아닌가 생각이 든다. 그리고 오픈소스 WebKit 기반의 브라우저는 Inspector라는 툴로 Firefox의 Firebug과 동일한 작업을 할 수 있다. 현재 구글의 Chrome과 애플의 Safari는 WebKit 기반의 브라우저로 개발되어있고 두 브라우저 모두 Inspector를 사용할 수 있다. 아래는 지금 글을 작성하는 이 화면을 Web Inspector의 화면으로 열어본 것이다. Eelements는 HTML의 DOM의 구조를 보여주고 Resources는 웹을 표현하는데 사용되는 리소스들을 보여준다. 그리고 Network는 리소스들이 사용자에게 표현되기 까지의 시간을 보여준다. Scripts는 javascript 코드를 보거나 디버깅(브레이크 포인트 등)을 할 수 있는 메뉴이다. 그리고 console은 Javascript가 동작하는 것을 확인하거나 변수의 내용을 출력시킬 수 있는 콘솔이다. (다른 메뉴들도 각각 기능이 있지만 이 포스트에서 소개는 하지 않겠다.)

이렇게 웹을 개발할때는 Web Inspector를 사용할 수 있으니가 UIWebView에 이것을 이용하면 될거라는 생각을 하고 찾아보기 시작했다. 하지만 UIWebView는 Safari 브라우저가 아니다. UIWebView는 iOS SDK의 UIKit에 포함된 웹뷰이기 때문에서 Web Inspector를 바로 실행시킬 수 없다. 더구나 Web Inspector는 Mac 에 포함된 기능이지 아이폰 자체에 포함된 기능은 아니기 때문이다. 그래서 조사하던 도중에 @kenu0000 @iolothebard 께서 조언을 해주신 weinre를 사용할 수 있게 되었다. (다시 한번 링크와 조언 감사드립니다) 아래는 slideshare에 좋은 자료가 있어서 첨부한다. 

weinre 의 사이트는 http://phonegap.github.com/weinre/ 이다. 그리고 다음 그림은 weinre 첫 화면에 나타내는 그림인데, 아이폰 시뮬레이터 속에 있는 웹페이지의 내용을 맥에 있는 Safari 브라우저에서 디버깅을 하는 화면이다. 다시 말하면 로컬 PC에 디버깅 서버를 동작시키고 그 서버에서 분석한 결과를 웹으로 확인하는 방법이다.

하지만 위에 슬라이드나 그림들은 모두 시뮬레이터의 브라우저에서 코드를 실행한 결과를 확인하는 것인다. 우리의 목적은 UIWebView 안에 Hybrid 앱을 만들기 위한 코드를 작성하면서 디버깅을 하고 싶은 것이다. 그래서 예제를 준비 했다. 

우선 weinre를 다운받는다. 맥 버전을 받아도 좋고 jar 버전을 받아도 괜찮다. 다음 링크로 가서 zip 파일을 다운 받아서 unzip을 한다.

https://github.com/callback/callback-weinre/archives/master 

맥 버전을 사용한다고 가정하고 weinre-mac을 다운받아서 실행한다. 실행하면 Safari에서 Web Inspector과 거의 동일한 메뉴들이 보일 것이다. 만약 jar 버전을 다운 받으면 아래 명령어로 실행하고 http://localhost:8080/client 로 브라우저를 열어서 확인할 수 있다. boundHosts 에는 자동으로 host가 탐색되는데 IP 정보가 나와서 잘라 내었다. 원래는 IP 주소 목록이 나온다.

java -jar weinre.jar --boundHost -all-




Single View Application을 하나 만들고 UIWebView를 추가하도록 하자. 그리고 Web 이라는 폴더 안에 index.html 파일을 만들고 프로젝트에 추가를 하는데 파을을 복사하지 않고 Xcode 프로젝트 내에서 참조만 할 수 있게 다음과 같이 설정한다.

index.html에는 다음과 같은 코드를 입력한다. weinre 서버가 디폴트로 8080 포트로 운영되기 때문에 localhost 의 8080 으로 접근해서 디버깅을 하기 위한 javascript를 받아온다. 그리고 우리가 테스트할 메소드를 하나 만들었다.

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8"/>

    <title>UIWebView Debugging Tutotial</title>

    <meta name="viewport" content="initial-scale=1.0,user-scalable=no">

    

    <script src="http://localhost:8080/target/target-script-min.js"></script>

    <script type="text/javascript">

        function callPrint() {

            console.log('first');

            console.log('second');

        }

    </script>

</head>

<body>

    <button onclick="callPrint();">call print</button>

</body>

</html>



viewController에는 물러적으로 만든 index.html 파일을 가져오도록 다음 코드를 추가한다. 위에 것은 .h 파일이고 밑에 것은 .m 파일이다.

//  SFViewController.h

//  SaltfactoryiOSTutorial

//

//  Created by SungKwang Song on 4/2/12.

//  Copyright (c) 2012 saltfactory@gmail.com. All rights reserved.

//


#import <UIKit/UIKit.h>


@interface SFViewController : UIViewController


@property (strongnonatomicIBOutlet UIWebView *appWebView;

@end


//

//  SFViewController.m

//  SaltfactoryiOSTutorial

//

//  Created by SungKwang Song on 4/2/12.

//  Copyright (c) 2012 saltfactory@gmail.com. All rights reserved.

//


#import "SFViewController.h"


@interface SFViewController ()


@end


@implementation SFViewController

@synthesize appWebView;


- (NSString *) getFilePath

{

    return [[NSBundle mainBundlepathForResource:@"index" ofType:@"html" inDirectory:@"Web"];

}


- (void)viewDidLoad

{

    [super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.

    NSURL *uri = [NSURL fileURLWithPath:[self getFilePath]];

    [appWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[[[uri absoluteStringstringByAppendingString@"?"]stringByAppendingString: [uri absoluteString]]]]];

}


- (void)viewDidUnload

{

    [self setAppWebView:nil];

    [super viewDidUnload];

    // Release any retained subviews of the main view.

}


- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

{

    return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);

}


@end

실행시키면 간단한 버턴만 눈에 보일 뿐 아무러 동작을 하지 않는다.


우리는 프로젝트를 만들고 UIWebView를 추가하기 전에 UIWebView를 디버깅하기 위해서 weinre를 실행하였었다. 그 화면으로 돌아가보면 Targets에 시뮬레이터가 감지되고 번들 밑에 Web 폴더 안의 index.html을 타게팅하는 정보를 얻게 된다. 

Inspector의 Elements를 열어서 특정 DOM을 선택하면 UIWebView 안에서 해당되는 엘리먼트가 선택되는 것을 확인할 수도 있다.


그리고 버턴을 눌렀을때 console에 메세지를 출력하게 함수를 만들었는데 버턴을 눌러보면 콘솔에 로그가 찍히는 것을 확인할 수 있다.

뿐만아니라 만약에 Javascript 에서 에러가 발생하면 콘솔에서 에러 메세지를 확인할 수 있다. 에러를 발생시키기 위해서 일부러 console.log를 consol.log로 만들어서 테스트 하였다. Can't find variable: consol 이라는 에러가 나타난다.

자바스크립트 코드만 확인할 수 있는 것이 아니라 Elements 창에서 실시간으로 HTML 코드를 변경할 수 있고 CSS를 변경하여 확인할수도 있다. 아래는 버턴의 글자 크기를 Inspector에서 변경하여 바로 확인한 결과 이다.

이제 하이브리드 앱을 개발할 때 UIWebView 안에 HTML, CSS, Javascript의 값을 확인하거나 변경할 수 있게 되었다. 다만 개발자들에게 좀 아쉬운 부분은 javascript의 변수들이 어떻게 변화되는지를 console.log로 계속 확인해야한다면 약간 부담스럽다. 그래서 개발자들은 breakpoint 기능이 있기를 원한다. 하지만 아쉽게도 weinre에서는 Web Inspector에서 제공하는 Scripts 메뉴가 없어서 javasctipt 디버깅을 하기에 약간 아쉬운 감이 있다. 이럴 경우에는 애플에서만 사용하는 private API를 이용하여 아쉬운 부분을 해결할 수 있다. 하지만 지금 소개하는 것은 private API 이기 때문에 이 코드를 남겨두고 앱스토어에 등록하면 reject를 당하게 된다. 앱스토어에 등록할때는 반드시 이 코드를 제외시켜야 함을 기억하자.

AppDelegate.m 파일에서 앱이 구동되면 호출되는 application:didFinishLaunchingWithOptions: 메소드에 다음 코드를 추가하는 것이다.

[NSClassFromString(@"WebView") performSelector:@selector(_enableRemoteInspector)];

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

{

    [NSClassFromString(@"WebView"performSelector:@selector(_enableRemoteInspector)];

    

    self.window = [[UIWindow allocinitWithFrame:[[UIScreen mainScreenbounds]];

    // Override point for customization after application launch.

    self.viewController = [[SFViewController allocinitWithNibName:@"SFViewController" bundle:nil];

    self.window.rootViewController = self.viewController;

    [self.window makeKeyAndVisible];

    return YES;

}

Javascript의 변수의 값이 변화되는 것을 디버깅하기 위해서 index.html의 코드를 다음과 같이 변경한다.

<!DOCTYPE html>

<html>

<head>

    <meta charset="utf-8"/>

    <title>UIWebView Debugging Tutotial</title>

    <meta name="viewport" content="initial-scale=1.0,user-scalable=no">

    

    <script src="http://localhost:8080/target/target-script-min.js"></script>

    <script type="text/javascript">

        function callPrint(message) {

            console.log(message);

            console.log('second');

        }

    </script>

</head>

<body>

    <button onclick="callPrint('saltfactory');">call print</button>

</body>

</html>

이제 다시 Build & Run을 하여 시뮬레이터를 실행시킨다. 그리고 http://localhost:9999 로 Safari 브라우저에서 접속한다. weinre를 사용할 때와 비슷한 것이 나오며 디버깅이 시작된다. 


링크를 클릭해서 들어가서  Scripts 메뉴에서 breakpoint를 linenumber가 나오는 패널에다 추가할 수 있고 실행하여 breakpoint에 걸렸을 때 변수에 어떤 값이 들어있는지 변수 값의 변화를 확인할 수 있다.

Hybrid 앱을 개발하기 위해서는 UIWebView라는 객체의 사용은 필수조건이 된다. 물론 html 파일을 로컬 에디터에서 작성하고 Safari  브라우저에서 작업하여 최종 결과물을 Xcode 프로젝트에 넣어서 활용하기도 하지만 Safari 브라우저와 UIWebView가 100% 똑같이 해석되지 않는다. 더구나 Hybrid 앱을 작성하면 Native 코드와 데이터를 주고 받게 되는데 이것을 동작시키면서 디버깅하는 방법이 필요한데 이때 반드시 UIWebView 상에서 디버깅이 이루어져야한다. 이 때 weinre와 WebView의 private API가 디버깅을 하는데 많은 도움이 될거라 예상된다. 


참고

1. http://phonegap.github.com/weinre/Installing.html

2. http://atnan.com/blog/2011/11/17/enabling-remote-debugging-via-private-apis-in-mobile-safari/

3. @kenu0000 @iolothebard  조언 감사합니다.


출처 - http://blog.saltfactory.net/113




Posted by linuxism
,