최근 작업하면서 생겼던 문제와 해결방법

푸시 알람 적용할때, 인증서 문제

인증서 파기하고 다시 생성하면 잘된다.

시뮬레이터 로그파일 열어보기

보통은 스튜디오의 콘솔을 이용해 로그를 확인해보지만, 어떤 이유에서인지 어느날부터 콘솔창에 로그가 찍히지 않아서 불편함을 겪고 있었다. 이럴때는 그냥 시뮬레이터에서 찍는 로그파일을 직접 열어서 확인해보는 방법을 이용하자. 시뮬레이터의 아래 위치로 가서 로그 파일을 열어보자!

/User/[사용자]/Library/Application Support/iPhone Simulator/[버전]/Applications/[앱해시폴더]/Documents/xxxxx.log

리스트뷰 아이템 삭제

공식 문서상에는 delete 이벤트가 없어서 삭제가 안되는 줄 알았는데 삭제 이벤트가 있더라. iOS 시뮬레이터에서는 스윕 삭제 제스처가 안되지만, 실제 디바이스에서는 잘 동작하더라. 그리고, LIST_ACCESSORY_TYPE_DETAL도 디바이스와 시뮬레이터의 모양이 달랐다!

데이터 베이스(SQLite)가 업데이트 되지 않는 문제

model.save()를 해도 데이터가 업데이트 되지 않는 문제가 있어서, 혹시나하고 데이터베이스를 열어보니 중복으로 저장되고 있다! 문제는 데이터 베이스 정의할때 PK를 지정하지 않아서 발생!!

서버에 데이터가 PUT이 되지 않고 POST 되는 문제

model.save()로 데이터를 업데이트하고 싶은데, 자꾸 POST 되는 문제는 Backbone의 문제였다. 컬렉션을 만들 때 idAttribute를 지정하면 PUT이 제대로 간다.

참고 문서1

8월 타이타늄 커뮤니티 티타임 후기

오랜만에 티타임을 가졌다. 하루 밖에 안지났는데,.. 벌서 기억나지 않는 것들이 너무 많다.. -_-;;.. 일단 생각나는 대로 정리!

Alloy 이슈들

Alloy 1.2 버전부터 ListView를 XML로도 정의할수 있게됐다. 물론 ListView는 아직 한창 개발중이기 때문에 drag 이벤트라든가 headerView나 Pulldown Refresh 기능들은 좀더 기다려야한다. 자세한 내용은 Alloy Jira 페이지를 확인해보자.

ListView에 데이터 바인딩시 삽질하기 쉬운 착각들

1. 먼저 dataCollection은 ListView가 아닌 ListSection에 정의해야한다.

이 문제 때문에 삽질한 시간은 정말 눈물 겹다. TableView의 경우 dataCollection을 TableView에 정의 하지만 ListView 조금 다르다. 다음 예제를 보자. 아래와 같이 ListView에 dataCollection을 정의하면 Alloy 컴파일 오류를 발생시킨다.

<ListView dataCollection='잘못된정의'>
    <Templates>
        <!-- 여기에 여러 템플릿을 정의 할수있다. -->
        <ItemTemplate name="tpl1" />
        <ItemTemplate name="tpl2" />
    </Templates>
    <ListSection></ListSection>
</ListView>

따라서 dataCollection은 반드시 아래와 같이 ListSection에 정의하도록 하자!! dataCollection으로 지정된 값은 models 폴더에 정의된 파일명과 동일한 콜랙션을 바인딩하게 된다. 아래 예제는 posts 콜랙션을 리스트뷰 템플릿에 바인딩하게 된다.

<ListView> 
    <Templates> 
        <ItemTemplate name="tpl1"></itemtemplate> 
        <ItemTemplate name="tpl2"></itemtemplate> 
    </Templates> 

    <!-- 데이터 바인딩시 dataCollection 속성은 필수 인자다. -->
    <ListSection dataCollection='posts'></ListSection>   
</ListView>

2. dataTransform과 dataFilter 속성의 차이점.

데이터 바인딩시 dataCollection 속성과 더불어 설정할 수 있는 2가지 속성이 더 있는데, 바로 dataTransformdataFilter 속성이다. 이 두 속성은 모두 아래와 같이 함수를 지정해야한다.

<ListView> 
    <Templates> 
        <ItemTemplate name="tpl1"></itemtemplate> 
        <ItemTemplate name="tpl2"></itemtemplate> 
    </Templates> 

    <!-- 데이터 바인딩시 추가로 dataTransform과 dataFilter를 지정할 수 있다. -->
    <ListSection dataCollection='posts' dataTransform='doTransform' dataFilter='doFilter'>
    </ListSection>   
</ListView>

두 함수의 차이는 간단하다. dataTransform 속성은 ItemTemplate에 데이터를 입힐 때 실행되는 함수로써 바인딩되는 콜렉션의 모델을 인자로 받아 가공할수있다. 반면에, dataFilter는 콜랙션 자체를 필터링 할때 사용한다.

3. 데이터 바인딩시 반드시 ListSection에 ListItem을 지정해라!

흔히 착각하는 것중에 하나가 ListSection을 비워두면 “초기에 아무런 값이 없고 나중에 동적으로 채워 넣을수 있겠지?” 하는 생각인데, ListView 자체를 컨트롤러에서 동적으로 직접 create해서 생성하는 경우에는 맞는말이다. 하지만 데이터 바인딩을 한 경우에는 반드시 아래와 같이 ListItem을 지정해야하고, 이렇게 지정된 녀석이 콜렉션의 갯수만큼 반복 된다는 사실을 잊지말자!

<ListView> 
    <Templates> 
        <ItemTemplate name="tpl1"></itemtemplate> 
        <ItemTemplate name="tpl2"></itemtemplate> 
    </Templates> 
    <ListSection dataCollection='posts' dataTransform='doTransform' dataFilter='doFilter'>
        <!-- 콜랙션을 반복하면서 아래에 정의된 아이템을 한벌로 생성한다. -->
        <ListItem template="tpl1"/>  
        <ListItem template="tpl2"/>
    </ListSection>   
</ListView>

성능 이슈

Alloy 이외에 일반적인 성능과 관련된 이야기들이 있었다. 정리하면 다음과 같다.

1. class 아니죠! className 입니다.

TableViewRow의 속성으로 같은 템플릿으로 구성되는 행들을 같은 className으로 지정해야 내부적으로 재활용하게 된다. class와 className을 헷갈리는 경우가 있으니 철자에 주의하자!

2. 디바이스 정보를 얻어오는 API는 한번만 호출하자!

Ti.Platform.xxxx 형태의 API는 비용이 비싸므로 반드시 로컬변수에 사용할 값을 캐시해서 사용한다.

var DEVICE_WIDTH = Ti.Platform.displayCaps.platformWidth;

3. 윈도우를 생성할 때 주의할 점.

윈도우는 비싼 비용을 지불하고 만드는 녀석이다. 특히 컨테이너로서 윈도우 안에 많은 뷰나 컨트롤들이 올라가게 되고, 이벤트를 바인딩 했다가 해제를 제대로 못하는 경우엔 메모리누수가 생길수가 있다. 따라서 습관적으로 윈도우가 닫힐때 $.destroy()를 실행시켜주자.

$.window.addEventListener("close", function(){
    // 특히, dataCollection 메소드를 이용해 데이터 바인딩을 한 경우 반드시 실행해야한다. 
    $.destroy();
});

4. 다중 윈도우를 이용해 뷰를 구성할때 주의할 점.

FaceBook과 같은 레이아웃을 구성할때는 가장 밑단에 깔리는 리스트 뷰는 매번 생성할 필요가 없다. 다만 쓰기나 상세 보기처럼 잠깐 나왔다가 사라지는 윈도우라면 매번 생성해되 윈도우가 닫힐때 걸린 핸들러를 잘 해제해준다. 위 3번 참고.

5. 이미지를 다룰때는 반드시 ImageView에 올리자.

캐시하는 방법은 Image_Best_Practices 문서를 참고한다.

6. 전역 이벤트 리스너를 윈도우와 혼합할때 생기는 메모리 누수

다음 코드처럼 동적으로 생성한 컨테이너를 전역 이벤트 리스너에서 사용한 경우 절대 지워지지 않는다! 주의하자!

var someFunction = function() { 
    var table = Ti.UI.createTableView(), 
        label = Ti.UI.createLabel(), 
        view = Ti.UI.createView();

    Ti.App.addEventListener('bad:move', function(e) {
        // table은 지역변수지만 앱이 종료되기 전까지 해제되지 않는다. 
        table.setData(e.data);
    });

    view.add(table);
    view.add(label);
    return view;
};

그밖에 ACS 이슈들..

1. ACS에서 있는 데이터를 fetch 할때 ORDER를 따로 지정하지 않으면, 랜덤하게 넘어오므로 ORDER를 지정하자.

2. ACS에 RESTful API는 구현되어 있으나 Ti.Cloud 객체에 구현되지 않는 객체도 있다.

3. 서버 이슈를 클라이언트로 끌어들이지 말자!

두번째 타이타늄 티타임 모임 후기

오늘도 어김없이 2주에 한번씩 열리는 티타임 모임에서 오갔던 이야기들을 정리해보려한다.
적어놓지를 않아서 기억나는대로 정리해본다.

1. Mylyn과 Bitbucket 이슈트랙커

연동이 계속 안되서 커넥터를 살펴봤는데, 종은님이 작성하신 포스트에 커넥터 링크가 잘못되어 있었다. 그래서 제대로된 커넥터를 설치하고 연동했더니 매우 잘 된다. Gooooo:D 종은님 땡큐~

2. Alloy 최신(unstable)버전 설치

타이타늄에 새롭게 추가된 ListView의 예제를 부일님이 돌리고 싶어하셔서, 확인해보니 Alloy 1.2.0+ 이상에서만 예제가 동작한다고 해서 Alloy 1.2.0 버전 설치에 대한 이야기를 했다. Alloy 설치 방법은 npm으로 설치하는 방법과 Git레파지토리에서 소스를 직접 받아서 설치하는 방법이 있다. npm 버전은 안정버전인 1.1.x 버전이므로 당연히 레파지토리에 있는 최신 소스를 다운로드 받아서 설치해야1.2.0 버전이 깔린다. 설치방법은 여기를 참고..

git clone https://github.com/appcelerator/alloy.git
cd alloy
[sudo] npm install -g .

3. 타이타늄 가이드 번역

부일님이 다시 타이타늄 가이드 번역을 시작하셨다. 번역하시는 페이지는 아마도 ListView 쪽인듯.. 맞나? 나도 틈틈히 다시 번역을 해야겠다. 번역하실 분들은 여기를 참고~!!

4. 이것은 백본 버그인가본가?

동구밭 앱에서 발생하는 문제였는데,.. 상황은 이렇다. 테이블 뷰에 Row컨트롤을 만들고 Row안에 그려지는 View에 Model이 변경되면 동작하도록 change 이벤트를 바인딩했다. 당연히 모델이 변경되면 View에 바인딩된 핸들러가 실행되어야하는데,.. 테이블이 reset되어 다시 뷰를 그리면 모델 change 이벤트를 받지 못한다. 여러가지 가정을 해보며 종은님과 내가 테스트를 해봤지만,.. 결론은 백본(현재 Alloy의 백본 버전은 0.9.2버전이다.)버그가 아닌가? 를 매우 강력하게 의심하고 있다. 나중에 Alloy 업그레이드 되면서 백본 버전도 바뀌면 해결이 되려나?
아무튼, 결론… 테이블의 Row들이 reset 되어 모델 이벤트를 받지 못하는 상황인 경우엔,.. Row에 모델 이벤트를 바인딩하지 않고, 테이블에 콜렉션 change 이벤트를 바인딩한다. 그리고 콜렉션의 모델 id를 찾아서 해당 Row 컨트롤러의 메소드를 실행하는 방법으로 해결~.. 이글 읽는 사람은 이게 뭔소린지 이해가 되려나? -_-;;

5. 모델.destroy()와 콜렉션.remove()

앞의 백본 문제와 더불어 나를 힘들게 했던 문제는 콜렉션에서 모델을 지울때, 가령 10개의 모델중에 5개의 모델을 한꺼번에 지워야하는 상황이 있다. 이때 모델이 삭제되면 destroy 이벤트를 받아서 테이블을 다시 그리려고 했는데.. 모델이 5개가 지워지면 destroy 이벤트도 5번 발생하게 되고, 고로 테이블도 5번 다시 그려야되는 비효율적인 문제가 발생. 그래서 한꺼번에 5번 발생하는 이벤트를 맨마지막에 한번만 발생하게 하고 싶었다. 그래서 silent:true 옵션을 destroy 할때 넣었는데,.. 도무지 싸일렌트가 먹질 않는다…
그런데!!!! 백본 도큐먼트를 유심히 살펴보니,.. 모델.destroy에는 silent 옵션이 없다는 사실!! 뚜둥~ 대신 이 옵션은 콜렉션 remove에 있었다. 그래서 나의 해결책은,.. destroy 이벤트를 받지 않고, 그냥 콜렉션에서 모델을 지운뒤에 새롭게 콜렉션을 fetch 해버렸다.

6. 이것이 열정이다.

티타임 모임에 새얼굴이 왔다. 현재 2명의 구성원으로 사이트를 구축하신 분인데, 한분은 디자이너이므로 혼자서 개발하셨단다. 전공도 경영학인데 프로그래밍이라니.. 덜덜덜~~ 아무튼 그동안 모바일웹으로 사이트를 구축했다가 성능 문제로 인해 네이티브로 갈아타기로 결정한 것!,.. 하지만 네이티브 경험이 전무한 관계로 타이타늄을 선택하고 모임에 나오셨다.
마치 영석님이 센차터치 성능에 실망하고 타이타늄을 갈아타셨다는 얘기와 오버랩이 되면서.. 앞으로도 모바일 웹으로 가닥을 잡았다가 성능에 데이신 분들이 타이타늄을 많이 찾지 않을까 생각해본다. 뒤늦게 오시는 그 분들을 위해서라도 우리 타이타늄 커뮤니티가 더 활성화 되어야한다.
이상 끝~ 또 뭐 있었떠라….??? 아무튼 열정적인 사람들을 보게 되면 나도 덩달아 동기부여가 되는 것 같다.

PS…
HTML5 웹앱으로 유명세를 탔던 Linkedin 앱도 결국 네이티브로 가기로 결정.. Fackbook이어 Linkedin도 결국 GG인가?
http://venturebeat.com/2013/04/17/linkedin-mobile-web-breakup/

요약,
왜 바꿨냐?
1. 성능이 결정적인 이유는 아니지만 여전히 큰 이슈다. 새버전에서는 더 많은 애니메이션을 넣었다.
2. HTML5 앱은 레퍼런스가 너무 적다. 네이티브는 강력한 도구와 레퍼런스가 널려있어서 우리 개발자들이 덜 고생한다.
3. 사용자들의 인입 경로를 보면 브라우저보다는 앱이 월등히 많다.(웹은 8~10% 나머지는 앱이다.) 우리도 이런 상황을 예상치 못했다. 그래서 둘다 지원하려고 모바일웹을 만들고 이것을 기반으로 웹앱을 만들었지만, 이제는 보다 앱에 집중하려고 한다. 주 사용자가 앱으로 접근하니 보다 네이티브에 집중하는 것은 당연한게 아닐까?