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

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

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

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

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

/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% 나머지는 앱이다.) 우리도 이런 상황을 예상치 못했다. 그래서 둘다 지원하려고 모바일웹을 만들고 이것을 기반으로 웹앱을 만들었지만, 이제는 보다 앱에 집중하려고 한다. 주 사용자가 앱으로 접근하니 보다 네이티브에 집중하는 것은 당연한게 아닐까?

오늘 타이타늄 티타임에서 나온 이야기 정리

오늘 타이타늄 티타임 모임에서 나온 이야기들을 정리해본다.

1. 말풍선 같은 가변 이미지 최적화 하기

기본 UI가 아닌 디자인된 UI를 앱에 적용하려면 많은 이미지를 사용하게 된다. 따라서 앱사이즈에도 영향을 받게 되는데, 20MB를 넘게 되면 3G 환경에서 앱을 다운로드 할수 없기 때문에 이미지 최적화 문제는 매우 크리티컬한 문제가 될수있다. 이런 이미지 문제를 해결하기 위한 첫번째 방법중 하나가 바로 이미지를 재활용해서 가변 디자인에 활용하는 것이다.
iOS의 경우, background*Cap을 활용하고,
Android의 경우, 9 patch tool을 활용한다.

2. Alloy 모델에서 생성한 DB 파일의 위치

예전에 “동구밭에서 삽질한 이야기“라는 발표 슬라이드에도 공유를 했었는데, 타이타늄에 인스톨한 DB는 특정한 위치에 저장이 되고, 강제로 설치된 DB파일을 지우지 않는한 두번 인스톨되지 않는다. 따라서 DB 스키마가 변경이 되면 이전 DB를 날리던지, 아니면 DB를 마이그레이션 해야한다. 참고로 DB뿐만 아니라 App Property로 저장한 값도 파일로 저장되기 때문에 앱을 지우지 않는한 남아있게 된다. 아무튼 시뮬레이터에서 이전 DB 파일의 위치는 다음과 같다.

/Users/[계정이름]/Library/Application%20Support/iPhone%20Simulator/[시뮬레이터버전]/Applications/[앱해쉬아이디]/Library/Private%20Documents/_alloy_.sql

시뮬레이터 경로자체가 다소 복잡하고 길기 때문에 보통은 console창에 찍힌 로그에서 복사한후에 붙여넣기해서 확인해보는게 가장 빠르다.
이전 DB 삭제 방법은 슬라이드를 참고하자!

3. join한 테이블을 이용해 백본 컬렉션 만들기

백본 모델은 기본적으로 key-value store라 불리는 NoSQL DB 형태로 생성되기 때문에 SQL의 join 테이블을 사용 할수가 없다. 두 모델에 있는 필드들을 join 하고 싶다면 보통은 forEach, filter, map 과 같은 순회 함수를 이용해 두 테이블의 컬럼값을 돌면서 하나로 합쳐야한다. 하지만 이 방법은 속도도 그렇고 비효율적인 면이 많다. 이럴때마다 “아~ join 테이블 한방이면 되는데..” 하는 아쉬움을 삼켜야했다. 하지만 역시나 쉬게 해결하는 방법이 있었다. 바로 Collection.fetch([option]) 메소드의 옵션을 사용하는 것이다. 옵션으로 query 속성을 넣으면 SQL문을 직접 사용할수있다.

내가 사용한 전략은 다음과 같다.

  1. 수정이 필요없는 static DB와 사용자의 액션에 따라 변하는 user DB를 따로 구분해둔다.
  2. static DB에 있는 두 테이블을 join해서 user DB에 새로운 테이블을 만든다.
  3. user DB에 join한 테이블을 백본 모델로 만든다.

그리고 샘플코드는 다음과 같다.

this.fetch({
    query : 'SELECT A.cropId, A.missionId, A.day, B.title FROM tb_mission_by_crop A JOIN tb_missions B ON A.missionId = B.missionId'
});

4. MapView에서 userLocation을 사용할때 주기적으로 그리고 자동적으로 현재 위치로 지도 중심이 이동되던 문제.

이 부분은 머리를 맞대고 얘기해본 결과 내가 코드를 잘못짠 것으로 확인됐다. 코드를 잘못 짜게 된 결정적 이유는 Geolocation의 ‘location’ 이벤트를 잘못 해석했기 때문이다.

// 디바이스의 현재 GPS 정보를 얻으면 해당 위치로 중심을 이동시킨다.
Ti.Geolocation.addEventListener('location', onLocation);

위 코드에서 나는 location 이벤트가 디바이스가 GPS 정보를 얻게되면 발생하는 줄 알았다. 물론 틀린 해석은 아니지만 원문은 이렇다.

Fired when a location update is received.

즉, 디바이스의 위치가 변경되면 발생한다!. 따라서 고정 위치라면 상관이 없겠지만 위치가 이동중이라면 location 이벤트가 계속 발생하기 때문에 기대한 결과가 나오지 않았다. 문서를 대충보면 꼭 이런 오류가 발생한다. API 문서는 꼼꼼히 읽을 필요가 있다.

5. 앱 배포를 위한 준비

그동안 만들어왔던 앱을 스토어에 올리기 위해 필요한 절차를 밟아봤다. 먼저 iTunes Connect에서 앱정보를 기술해야한다. 그리고 배포 인증서를 받아서 iTunes Store로 배포 빌드를 돌린다. 배포 빌드를 돌리면 빌드된 앱이 XCode의 아카이빙(Organizer – Archives탭에서 확인)된다. 이렇게 아카이빙 된 앱은 유효성 검사 후에 배포할 수 있다. 유효성 검사를 간단히 마치고 배포를 진행하면 자동으로 스토어에 앱이 제출 된다. 물론 심사과정을 거치고 리젝 당하면 위 과정을 반복해야한다.

6. 테스트 앱 배포 시스템

그동안 개발된 앱들은 빌드해서 개발 인증서와 .plist 파일을 만들어 함께 FTP에 올렸었다. 매번 개발버전이 나올때마다 앞의 과정을 반복했는데,.. 너무 귀찮다. 한방에 자동으로 해주는 툴이나 서비스가 있으면 참 좋겠다 싶어 종은님에게 여쭤봤다. 아니나 다를까 역시나! 귀찮은 작업을 간소화해주는 서비스가 존재한다고 한다. 자세한 내용은 다음 링크를 확인하자.

Alloy 모델과 DB 연동

아직 완성된 글이 아닙니다.
—-
Alloy의 모델은 Backbone의 모델을 사용하므로, Backbone의 API와 Event를 그대로 사용할수있다. 일단 스튜디오에서 Alloy모델을 생성하게 되면 /model 폴더에 해당 파일이 생성되고 기본 구조는 다음과 같다.

1. 모델의 기본 구조

exports.definition = {
 config : {
 // table schema and adapter information
 },
extendModel: function(Model) { 
 _.extend(Model.prototype, {
 // Extend, override or implement Backbone.Model 
 });

 return Model;
 },
extendCollection: function(Collection) { 
 _.extend(Collection.prototype, {
 // Extend, override or implement Backbone.Collection 
 });

 return Collection;
 }
}

일반적인 MVC 모델의 경우 다음 그림과 같이 컨트롤러에서 모델과 뷰를 생성하고 모델을 뷰의 인자로 넘기게 되는데, Alloy도 같은 MVC 구조를 가지기 때문에 Alloy 모델은 컨트롤러에서 생성되어야한다.
< 그림1, 삽입>
– MVC 기본 아키텍쳐 UML

모델 생성은 Alloy.createModel() 메소드를 이용한다. 샘플 코드를 보자.

 var book = Alloy.createModel('book', {title:'Green Eggs and Ham', author:'Dr. Seuss'}); 
 var title = book.get('title');
 var author = book.get('author');
 // Label object in the view with id = 'label'
 $.label.text = title + ' by ' + author;

위 코드에서 book 객체는 백본 모델이기 때문에 백본의 API를 그대로 사용할 수 있다. 그리고 이렇게 생성된 모델을 다른 컨트롤러에서도 사용하고 싶은 경우가 매우 많을 것이다. 이럴때는 Alloy.Models.instance() 메소드를 이용해 접근할수있다.

var book = Alloy.Models.instance('book');

물론 XML 뷰에도 모델을 넘겨서 사용하고 싶을수도 있다. 다음과 같이 XML 뷰와 함께 모델을 정의하면, 컨트롤러는 모델과 뷰를 자동으로 생성한다.

 <Alloy>
 <Model id="myBook" src="book" instance="true"/>
 <Window>
 <TableView id="table" />
 </Window>
</Alloy>

XML 뷰를 통해 자동으로 생성된 모델은 다음과 같은 방법은 접근한다.
– id 지정시, 컨트롤러에서 $.myBook 으로 접근
– src를 이용해 Alloy.Models.book 으로도 접근 가능 (전역변수)
– src는 book.js 와 맵핑된다.
– instance가 true면 싱글톤 모델을 만든다. false면 다른 인스턴스를 만든다.

2. 모델 설정

모델의 config 객체는 columns, defaults, adapters 라는 3개의 객체를 가진다.

  • columns – 테이블 스키마, SQLite가 인식하지 못하는 값은 모두 TEXT 타입로 지정된다.
  • defaults – undefined일 경우 설정되는 기본값
  • adapters – 실제 저장소와 맵핑하기 위한 type과 collection_name을 갖는다.
exports.definition = {
 config: {
 "columns": {
 "title": "String",
 "author": "String"
 },
 "defaults": {
 "title": "-",
 "author": "-"
 },
 "adapter": {
 "type": "sql",
 "collection_name": "books"
 }
 }
}

3. Backbone.Model 클래스 확장하기

기존 모델을 확장해서 필요한 필드나 함수를 만들수있다. 예를 들면 다음과 같다.

exports.definition = {
  config : {
   // table schema and adapter information
  },
  extendModel: function(Model) { 
    _.extend(Model.prototype, {
      // Implement the validate method 
      validate: function (attrs) {
        for (var key in attrs) {
           var value = attrs[key];
           if (key === "title") {
             if (value.length <= 0) {
               return "Error: No title!";
             }
           }
           if (key === "author") {
             if (value.length <= 0) {
               return "Error: No author!";
             } 
           } 
        }
      },
      // Extend Backbone.Model
      customProperty: 'book',
      customFunction: function() {
        Ti.API.info('I am a book model.');
      }, 
   });

   return Model;
 }
}

위 코드를 컨트롤러에서는 다음과 같이 사용한다.

var book = Alloy.createModel('book', {title:'Green Eggs and Ham', author:'Dr. Seuss'}); 
// Since set or save(attribute) is not being called, we can call isValid to validate the model object
if (book.isValid() && book.customProperty == "book") {
 // Save data to persistent storage
 book.save();
}
else {
 book.destroy();
}

4. 콜렉션

콜렉션은 순서가 있는 모델들의 집합을 의미한다. 마찬가지로 Backbone의 콜렉션을 상속받았으므로 똑같이 쓸수있다. 콜렉션 생성은 Alloy.createCollection() 메소드를 사용한다.

var library = Alloy.createCollection('book'); 
library.fetch(); // Grab data from persistent storage

모델과 마찬가지로 전역적으로 사용하고 싶다면 Alloy.Collections 객체를 이용한다.

var library = Alloy.Collections.instance('book');

5. 콜렉션 확장하기

모델과 마찬가지로 확장가능하고 백본에서 구현하지 않고 인터페이스만 뚫어둔 comparator 메소드를 구현해서 콜렉션을 정렬할 수 있다.

exports.definition = {
config : {
 // table schema and adapter information
 },
extendModel: function(Model) { 
 _.extend(Model.prototype, {
 // Extend, override or implement the Backbone.Model methods 
 });
 return Model;
 },
extendCollection: function(Collection) { 
 _.extend(Collection.prototype, {

 // Implement the comparator method.
 comparator : function(book) {
 return book.get('title');
 }
}); // end extend
  return Collection;
 }
}

6. 이벤트 핸들링

on, off, trigger 3개의 주요 메소드를 활용한다. 주의해야할 것은 3.0부터 Alloy모델과 콜렉션은 타이타늄 메소드를 지원하지 않는다. (addEventListener, removeEventListener, fireEvent)
또하나 주의할점,Backbone의 add, change, destroy, fetch, remove, reset 이벤트는 오버라이딩하지 말것!

var library = Alloy.createCollection('book');
function event_callback (context) {
 var output = context || 'change is bad.';
 Ti.API.info(output);
};
// Bind the callback to the change event of the collection.
library.on('change', event_callback);
// Trigger the change event and pass context to the handler.
library.trigger('change', 'change is good.');
// Passing no parameters to the off method unbinds all event callbacks to the object.
library.off();
// This trigger does not have a response.
library.trigger('change');