More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

115
  • date post

    22-Jul-2016
  • Category

    Documents

  • view

    238
  • download

    14

description

데이브 마크, 제프 라마시 지음 | 한동균 옮김 | 임베디드 & 모바일 시리즈_005 | ISBN: 9788992939515 | 35,000원 | 2010년 05월 31일 발행 | 640쪽

Transcript of More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

Page 1: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석
Page 2: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석
Page 3: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

More!

아이폰 3 프로그래밍

Page 4: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

iv

•목 차•

01장 다시 시작하기 1이 책에 대해 ........................................................................................................1

알아야 할 것들 ....................................................................................................2

시작하기 전에 준비해 두어야 하는 것들 .........................................................3

이 책의 구성 ........................................................................................................5

준비 되었는가? ...................................................................................................7

제1부 코어 데이터

02장 코어 데이터의 구조 11코어 데이터의 간략한 역사 ............................................................................ 12

코어 데이터 애플리케이션 만들기 ................................................................ 13

코어 데이터의 개념과 용어 ............................................................................ 15

데이터 모델과 영구 저장소 ............................................................................ 15

데이터 모델 클래스 : NSManagedObjectModel ....................................18

영구 저장소와 영구 저장소 코디네이터 ..................................................20

데이터 모델 돌아보기 ...............................................................................22

엔터티와 데이터 모델 에디터 ........................................................................ 22

엔터티 .........................................................................................................22

프로퍼티 .....................................................................................................23

관리 객체 .......................................................................................................... 25

키-값 코딩 ...................................................................................................26

관리 객체 컨텍스트 ...................................................................................27

Page 5: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

v

종료 시 저장 ...............................................................................................28

영구 저장소에서 데이터 불러오기 ................................................................ 29

페치 결과 컨트롤러 ......................................................................................... 31

페치 결과 컨트롤러 만들기.......................................................................31

페치 결과 컨트롤러 델리게이트 메서드 ..................................................35

페치 결과 컨트롤러로부터 관리 객체 검색 .............................................43

새로운 관리 객체의 생성과 삽입 ..............................................................43

관리 객체 삭제 ...........................................................................................46

모든 것이 갖추어졌다 ..................................................................................... 47

03장 슈퍼 스타트: 데이터 추가, 표현, 삭제 49Xcode 프로젝트 설정 ...................................................................................... 50

애플리케이션 구조 ....................................................................................51

애플리케이션 델리게이트 인터페이스 수정하기 ...................................52

애플리케이션 델리게이트 구현하기 ........................................................53

테이블 뷰 컨트롤러 생성하기 ........................................................................ 54

MainWindow.xib 설정하기 ........................................................................... 55

아웃렛에 연결하기 ....................................................................................58

데이터 모델 디자인하기 ................................................................................. 59

엔터티 추가하기 ........................................................................................60

새로운 엔터티 수정하기 ...........................................................................60

새로운 엔터티에 속성 추가하기 ...............................................................62

Name 속성 추가하기 .................................................................................62

속성 수정하기 ............................................................................................63

HeroListViewController 생성하기 ............................................................... 68

페치 결과 컨트롤러 선언하기 ...................................................................69

프로젝트에 두 개의 아이콘을 끌어다 놓자 .............................................73

HeroListViewController 인터페이스를 디자인하자 .............................73

영웅 뷰 컨트롤러 구현하기.......................................................................76

Page 6: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

vi

실행해보자 ....................................................................................................... 92

다 됐지만 아직 끝난 건 아니다. ..................................................................... 93

04장 디테일 뷰의 악마 95테이블 기반 vs Nib 기반의 디테일 뷰 ........................................................... 96

디테일 편집 뷰에 도전하기 ............................................................................ 97

배열로 테이블 구조 제어하기 ...................................................................... 100

쌍배열 ............................................................................................................. 100

중첩배열 ...................................................................................................101

쌍 중첩배열 ..............................................................................................101

테이블 구조를 배열로 표현하기 .............................................................102

중첩배열, 명확히 말하면 ........................................................................102

SuperDB 프로젝트 업데이트하기 ..........................................................103

속성의 형식 정하기 ....................................................................................... 105

디테일 뷰 컨트롤러 생성하기 ...................................................................... 108

인스턴스 변수와 프로퍼티 선언하기 .....................................................109

보여주기 기능 구현하기 .........................................................................110

새로운 컨트롤러 사용하기......................................................................118

뷰 기능 시험하기 .....................................................................................121

편집 보조 컨트롤러 추가하기 ...................................................................... 121

상위 클래스 생성하기 .............................................................................122

문자열 속성 에디터 생성하기 .................................................................127

날짜 속성 에티터 생성하기.....................................................................131

속성 에디터 사용하기 .............................................................................134

섹션 리스트 구현하기 ................................................................................... 137

일반적인 셀렉션 리스트 컨트롤러 생성하기 ........................................137

악마의 최후 .................................................................................................... 143

Page 7: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

vii

05장 변화를 준비하자: 마이그레이션과 버전 관리 145데이터 모델에 관하여 ................................................................................... 146

데이터 모델이 컴파일되다......................................................................147

데이터 모델은 여러 개의 버전을 가질 수 있다. ....................................147

데이터 모델 버전 식별자 ........................................................................150

버전이 할당된 데이터 모델 사용하기 ....................................................151

마이그레이션 ................................................................................................. 152

경량 vs 표준..............................................................................................152

표준 마이그레이션 ..................................................................................153

경량 마이그레이션을 사용하기 위해 앱 설정하기 ...............................153

이동할 시간 .................................................................................................... 155

06장 커스텀 관리 객체 157데이터 모델 업데이트하기 ........................................................................... 160

나이 속성 추가하기 .................................................................................161

선호 색 속성 추가하기 ............................................................................162

이름 속성에 최소 길이 추가하기 ............................................................163

Hero 클래스 생성하기 .................................................................................. 163

Hero 헤더 수정하기 ...................................................................................... 166

기본값 할당하기 ............................................................................................ 167

유효성 검사 .................................................................................................... 168

한 속성에 대한 유효성 검사....................................................................169

여러 속성에 대한 유효성 검사 ................................................................171

가상 접근자 .................................................................................................... 172

유효성 검사 피드백 추가하기 ...................................................................... 173

ManagedObjectAttributeEditor 헤더 파일 업데이트하기..................174

ManagedObjectAttributeEditor 구현 파일 업데이트하기..................175

하위 클래스에서 유효성 검사를 사용하도록 업데이트하기 ...............177

Page 8: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

viii

값 변환기 생성하기 ....................................................................................... 178

색 속성 에디터 생성하기 .............................................................................. 180

영웅 편집 컨트롤러에 새로운 속성 표시하기 ............................................ 185

표시 문제 ........................................................................................................ 187

영웅 편집 컨트롤러에서 특정 행을 표시만 하기 ....................................... 190

펼쳐보기 지시자 숨기기 .........................................................................190

읽기전용 속성에 대한 탭 다루기 ............................................................190

우리를 물들여 가다 ....................................................................................... 191

07장 관계, 페치드 프로퍼티, 그리고 표현 193애플리케이션 확장하기: 초능력과 보고서 ................................................. 194

관계 ................................................................................................................. 196

일대일 관계 ..............................................................................................197

일대다 관계 ..............................................................................................198

역관계 .......................................................................................................201

삭제 규칙 ..................................................................................................201

페치드 프로퍼티 ............................................................................................ 202

관계와 페치드 프로퍼티를 데이터 모델 에디터에서 생성하기 ................ 203

Power 엔터티 추가하기 ..........................................................................204

Powers 관계 생성하기 .............................................................................205

역관계 생성하기 ......................................................................................206

olderHeroes 페치드 프로퍼티 생성하기 ...............................................206

youngerHeroes 페치드 프로퍼티 생성하기 ..........................................210

sameSexHeroes 페치드 프로퍼티 생성하기 .........................................212

oppositeSexHeroes 페치드 프로퍼티 생성하기 ...................................213

관계와 페치드 프로퍼티를 Hero 클래스에 추가하기 ................................ 215

리팩토링 ......................................................................................................... 217

클래스 이름 바꾸기 .................................................................................218

영웅 인스턴스 변수 리팩토링하기 .........................................................219

Page 9: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

ix

배열 제거하기 ..........................................................................................219

저장하기와 취소하기 버튼 제공하기 .....................................................220

일대다 관계 지원하기 .............................................................................224

새로운 일반화된 컨트롤 사용하기 .............................................................. 242

영웅과 초능력을 위한 팩토리 메서드 추가하기 ...................................242

Nib 인스턴스 삭제하기 ...........................................................................247

HeroListController 업데이트하기.........................................................248

페치드 프로퍼티 속성 컨트롤러 생성하기 ................................................. 248

삭제된 객체 정리하기 ................................................................................... 251

멋진 코어 데이터 ........................................................................................... 255

제2부 더 멀리 탐험해보자

08장 GameKit을 사용한 피어-투-피어 블루투스 259예제 애플리케이션 ........................................................................................ 260

네트워크 통신 모델 ....................................................................................... 263

클라이언트-서버 모델 .............................................................................263

피어-투-피어 모델 ...................................................................................264

하이브리드 클라이언트-서버 / 피어-투-피어 .......................................265

GameKit 세션 ................................................................................................ 266

세션 생성하기 ..........................................................................................266

다른 세션을 찾아 연결하기.....................................................................268

다른 세션 수신하기 .................................................................................268

피어로 데이터 전송하기 .........................................................................269

전송할 정보를 패키지화하기 ..................................................................270

피어로부터 데이터 수신하기 ..................................................................271

연결 닫기 ..................................................................................................271

피어 피커 ........................................................................................................ 272

Page 10: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

x

피어 피커 생성하기 .................................................................................272

피어 연결 다루기 .....................................................................................273

세션 생성하기 ..........................................................................................273

프로젝트 생성하기 ........................................................................................ 274

유휴 타이머 끄기 .....................................................................................274

GameKit 프레임워크 임포트하기 ..........................................................275

인터페이스 수정하기 ..............................................................................276

실행해 보자 .................................................................................................... 309

게임을 실행하자! ........................................................................................... 310

09장 온라인 플레이: Bonjour와 네트워크 스트림 311이 장의 애플리케이션 ................................................................................... 311

전반적인 과정 ................................................................................................ 313

리스너 구성하기 ............................................................................................ 313

콜백 함수와 런 루프 통합 .......................................................................314

소켓 설정하기 ..........................................................................................315

대기 포트 정하기 .....................................................................................318

런 루프에 소켓 등록하기 ........................................................................321

소켓 콜백 함수 구현하기 ........................................................................321

리스너 멈추기 ..........................................................................................322

Bonjour .......................................................................................................... 323

공개 서비스 만들기 .................................................................................324

발행된 Bonjour 서비스 찾기 ..................................................................327

브라우저 델리게이트 메서드 ..................................................................327

발견된 서비스 분석하기 .........................................................................329

스트림 ............................................................................................................. 330

스트림 열기 ..............................................................................................331

스트림과 스트림의 델리게이트 ..............................................................332

Page 11: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xi

스트림에서 데이터 받기 .........................................................................333

스트림을 통해 데이터 전송하기 .............................................................334

종합하기 ......................................................................................................... 334

온라인 플레이를 위해 틱택토 수정하기 ..................................................... 335

패킷 카테고리 추가하기 .........................................................................335

온라인 세션 객체 구현하기.....................................................................338

리스너 객체 생성하기 .............................................................................351

피어 브라우저 생성하기 .........................................................................358

온라인 플레이 지원을 위해 TicTacToeViewController 업데이트하기 367

즐겨보자 ......................................................................................................... 380

10장 웹에 있는 데이터 사용하기 381애플리케이션 뼈대 구성하기 ....................................................................... 383

액션과 아웃렛 선언하기 .........................................................................383

인터페이스 설계하기 ..............................................................................386

원형 구현하기 ..........................................................................................387

Foundation 객체를 사용하여 데이터 가져오기 ......................................... 390

데이터를 동기적으로 가져오기 ................................................................... 393

URL 요청 ..................................................................................................393

비동기적으로 데이터 가져오기 ................................................................... 398

NSURLConnection 델리게이트 메서드 ...............................................399

WebWork에 비동기 데이터 수신 추가하기 ..........................................401

요청 타입과 폼 매개변수 .............................................................................. 405

HTTP 요청 타입 명시하기 .....................................................................405

폼 매개변수 ..............................................................................................407

RequestTypes 애플리케이션 만들기 .....................................................408

HTTP 404 - 결론을 찾을 수 없습니다. ....................................................... 414

Page 12: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xii

11장 MapKit 415이 장의 애플리케이션 ................................................................................... 416

개요와 용어정리 ............................................................................................ 417

맵뷰 ................................................................................................................. 418

맵 종류 ......................................................................................................418

사용자 위치 ..............................................................................................419

좌표 지역 ..................................................................................................420

표시할 지역 설정하기 .............................................................................423

맵뷰 델리게이트 ......................................................................................423

주석 ................................................................................................................. 425

주석 객체 ..................................................................................................425

주석 뷰 ......................................................................................................426

주석 추가와 제거 .....................................................................................427

주석 선택하기 ..........................................................................................427

맵뷰에 주석 뷰 제공하기 ........................................................................428

리버스 지오코딩 ............................................................................................ 429

MapMe 애플리케이션 만들기 ..................................................................... 431

아웃렛과 액션 선언하기 .........................................................................431

인터페이스 만들기 ..................................................................................432

주석 객체 클래스 작성하기.....................................................................434

MapMeViewController 구현하기 .........................................................437

MapKit과 코어 로케이션 프레임워크 연결하기 ..................................448

동쪽으로 가라, 젊은 프로그래머들이여 ..................................................... 448

12장 메일 보내기 449예제 애플리케이션 ........................................................................................ 449

MessageUI 프레임워크 ................................................................................ 451

메일 작성 뷰 컨트롤러 생성하기 ............................................................452

Page 13: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xiii

제목 미리 설정하기 .................................................................................452

수신자 미리 설정하기 .............................................................................452

본문 설정하기 ..........................................................................................453

첨부파일 추가하기 ..................................................................................453

메일 작성 뷰 보여주기 ............................................................................453

메일 작성 뷰 컨트롤러 델리게이트 메서드 ...........................................454

MailPic 애플리케이션 만들기 ..................................................................... 455

아웃렛과 액션 선언하기 .........................................................................455

사용자 인터페이스 만들기......................................................................456

뷰 컨트롤러 구현하기 .............................................................................456

MessageUI 프레임워크 연결하기 ..........................................................463

메일을 보내자 ................................................................................................ 464

13장 아이팟 라이브러리 접근 465예제 애플리케이션 ........................................................................................ 465

아이팟 라이브러리 사용하기 ....................................................................... 467

미디어 아이템 ..........................................................................................469

미디어 아이템 컬렉션 .............................................................................474

미디어 쿼리와 미디어 프로퍼티 술어 ....................................................475

미디어 피커 컨트롤러 .............................................................................478

뮤직 플레이어 컨트롤러 .........................................................................480

간단한 재생기 애플리케이션 만들기 .......................................................... 485

미디어 아이템 컬렉션 기능 추가하기 ....................................................486

아웃렛과 액션 선언하기 .........................................................................491

사용자 인터페이스 만들기......................................................................493

심플 플레이어 뷰 컨트롤러 구현하기 ....................................................497

심플 플레이어를 실행해 보자 .................................................................515

그만! 거친 바다로 가자!................................................................................ 516

Page 14: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xiv

14장 인터페이스가 항상 반응하게 만들기 517동시성 문제 탐구하기 ................................................................................... 519

Stalled 애플리케이션 생성하기 ................................................................... 520

액션과 아웃렛 선언하기 .........................................................................520

인터페이스 디자인하기 ..........................................................................521

Stalled 뷰 컨트롤러 구현하기 .................................................................522

타이머 ............................................................................................................. 525

타이머 생성하기 ......................................................................................525

타이머 정지하기 ......................................................................................526

타이머의 한계 ..........................................................................................527

Stalled에 타이머 추가하기 ........................................................................... 527

배치 객체 만들기 .....................................................................................527

컨트롤러 헤더 업데이트하기 ..................................................................530

nib 업데이트하기 ....................................................................................531

뷰 컨트롤러 구현 업데이트하기 .............................................................531

오퍼레이션 큐 & 동시성 ............................................................................... 537

스레드 .......................................................................................................538

오퍼레이션 ...............................................................................................545

오퍼레이션 큐 ..........................................................................................548

Stalled 애플리케이션에 오퍼레이션 큐 추가하기 ...................................... 550

SquareRootApplication 생성하기 .........................................................550

StalledViewController.h 변경하기 ........................................................557

사용자 인터페이스 수정하기 ..................................................................558

StalledViewController.m 업데이트하기 .................................................... 559

큐에 넣자 ........................................................................................................ 567

Page 15: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xv

15장 디버깅 569디버거 ............................................................................................................. 570

브레이크 포인트 ......................................................................................571

GDB 콘솔 .................................................................................................588

정적 분석 ........................................................................................................ 592

특정 버그 ........................................................................................................ 593

메모리 오버릴리즈하기 ..........................................................................594

무한 반복 ..................................................................................................599

놓친 아웃렛과 액션 커넥션들 .................................................................601

GDB: 결론 단락에서 멈추다 ........................................................................ 602

16장 끝없이 이어지는 길 603잠시 떨어져서 바라보자 ............................................................................... 603

애플의 문서 ..............................................................................................604

메일링 리스트 ..........................................................................................604

토론 포럼 ..................................................................................................604

웹사이트 ...................................................................................................605

블로그 .......................................................................................................606

작별 인사 ........................................................................................................ 607

찾아보기.............................................................. 608

Page 16: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xvi

데닌에게, 당신은 내 인생의 등불입니다.

-데이브

내 인생에서 소중했던 수많은 사람들과 나의 아내 그리고 아이들을 위하여…

-제프

Page 17: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xvii

옮긴이 서문

2009년 11월말. 드디어 국내에 아이폰이 도입되었습니다. 많은 사람들이 아이폰에 열광하였고, 4개월

만에 50만 대가 판매되며 하루가 다르게 우리나라의 많은 부분을 바꿔놓았습니다. 많은 사람들이 더

이상 버스를 타기 위해 추위에 벌벌 떨지 않아도 되었으며, 낯선 곳에서도 아이폰의 지도 어플을 통해

더 이상 길을 해매지 않게 되었습니다.

아이폰의 성공에는 앱스토어라는 새로운 생태계의 힘이 컸습니다. 사람들은 아이폰이란 캔버스 위

에 앱스토어에서 구매한 다양한 앱으로 자신만의 그림을 그려나갈 수 있는 매력에 매료되어 아이폰

을 사용하였고 주변의 지인들에게도 권하기 시작하였습니다.

아이폰이란 텅 빈 캔버스가 있고 이 책을 읽고 계신 여러분은 이 텅 빈 캔버스를 마음대로 채워넣을

수 있는 화가들입니다. 『시작하세요! 아이폰 3 프로그래밍』을 통해서 그림의 기본기를 터득하신 분도

있을 것이고, 이미 많은 그림을 그려보신 분도 있을 것입니다.

『시작하세요! 아이폰 3 프로그래밍』이 전반적으로 API의 사용 방법에 집중했다면 이 책에서는 좀

더 우아한 방법으로 코드의 중복을 막고 유지보수하기 쉬운 방법으로 API를 사용하는 방법까지 고

려하였습니다. 이런 방식의 사고는 학습하기 까다로울지 모르지만 한번 학습하고 나면 비단 아이폰

개발만이 아니라 다른 언어를 갖고 개발할때도 여러분이 작성하는 코드의 질을 향상시켜줄 것입니다.

감사의 글

이 책을 번연할 수 있도록 위키북스와 연을 만들어주신 FLOO의 이창신님과 바쁘신 와중에도 베타

리딩을 해주시느라 수고해주신 SK 커뮤니케이션즈의 김정운님과 다음 커뮤니케이션의 황현석 선배님

에게 감사의 말을 전합니다. 마지막으로 풋내기 번역가를 믿고 아낌없는 전폭적인 지원을 해주신 위키

북스에도 감사의 말을 전합니다.

Page 18: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xviii

지은이 소개

데이브 마크Dave Mark는 오랫동안 맥 기반 소프트웨어 개발자로 활동해 왔

으며, 『Learn C on the Mac』(Apress, 2009), The Macintosh Programming

Primer 시리즈(Addison-Wesley, 1992)와 『Ultimate Mac Programming』

(Wiley, 1995)를 포함한 다수의 맥 개발 서적을 집필한 바 있다. 데이브는

수영을 너무나도 사랑해서, 가급적이면 물가를 떠나지 않으려고 한다. 부

인과 세 아이와 함께 버지니아에서 살고 있다.

제프 라마시Jeff LaMarche는 맥 개발자이자, 공인받은 애플 아이폰 개발자로

서 오랫동안 일해 온 20여 년의 프로그래밍 경력을 지니고 있다. 맥테크

MacTech 잡지와 애플의 개발자 기술지원 서비스 사이트Apple’s Developer

Technical Services에도 코코아Cocoa와 오브젝티브CObjective-C에 관련한 기사

를 기고해왔다. 90년대 후반에 피플소프트PeopleSoft에서 기업용 소프트웨

어 개발자로 일했고, 그 후에는 줄곧 독립 컨설턴트로 활동해왔다.

기술 감수

마크 데일림플Mark Dalrymple은 맥, 유닉스 개발자로서 크로스-플랫폼 도구,

일반 사용자들을 위한 데스크톱 애플리케이션, 고성능 웹서버 등을 개발해

왔다. 『Advanced Mac OS X Programming』(Big Nerd Ranch 2005)과

『Learn Objective-C on the Mac』(Apress 2009)의 대표 저자이기도 하다.

여가시간에는 트럼본과 바순을 연주하고 동물 풍선을 만들곤 한다.

Page 19: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xix

감사의 말

이 책은 유능하고 친절하며 영민한 두뇌를 자랑하는 우리의 가족, 친구, 지인들이 없었다면 완성될 수

없었을 것이다. 우리가 이 책에만 내내 매달려 있는 동안, 여러 가지 일들을 대신 처리해준 우리의 아

내, Terry와 Deneen에게 무한한 감사를 드린다. 이 프로젝트 때문에 우리가 방구석에 틀어박혀 오랫

동안 나오지 않았음에도 불구하고 한 번도 불평하지 않았다. 우리는 복 받은 남편들이다.

또, Apress 출판사의 유능한 직원들이 없었다면 이 책을 집필할 수 없었을 것이다. Clay Andres는

Apress에 우리를 처음 소개시켜주 었을 뿐 아니라, 이 책을 직접 담당해주었다. Dominic Shakesha�

는 우리의 모든 불평에도 불구하고, 늘 만면에 미소를 띤 채로 가능한 모든 해결책들을 강구해 주었

으며, 좀 더 나은 책으로 만들기 위해 노력한 친절한 담당자였다. Kelly Moritz는 우리의 느린 집필 속

도에도 관대한 편집자였다. Douglas Pundick은 엄청난 양의 피드백을 처리해준 기술 편집자였다. 이

들은 책이 올바른 방향으로 나갈 수 있도록 해주었다. 맞춤법을 담당해준 Marilyn Smith와 Ralph

Moore는 함께 일하면 즐거운 사람들이였다. Je�rey Pepper, Frank McGuckin, Angie MacAllister, 그

리고 Appress 제작팀에서 모든일을 담당해 주었다. Leo Cuellar와 Je� Stone�eld는 마케팅 메시지를

구상하고 퍼트리는 역할을 했다. 다시 한 번 Appress에 근무하는 모든 분들께 무한한 감사를 드린다.

기술 감수를 맡아 준, 믿을 수 없이 뛰어난 재능을 갖춘 마크 데일림플에게도 감사를 드린다. 통찰

이 있는 피드백을 주었을 뿐만 아니라 이 책의 모든 코드들을 테스트해 주어서, 우리가 목표하는 길로

거침없이 나아갈 수 있도록 도와주었다.

마지막으로 우리들이 열심히 일할 때 언제나 참 을성 있게 기다려준 우리 아이들에게도 감사한다.

이 책을 Maddie, Gwynnie, Ian, Kai, Daniel, Kelly 그 리고 Ryan에게 바친다.

Page 20: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xx

서 문

이전 책 『시작하세요! 아이폰 3 프로그래밍』의 서문을 보면 “여태껏 이처럼 흥미로운 프로그래밍 플

랫폼은 발견할 수 없었다”라는 문구로 시작한다. 새로운 것들을 접하고 계속해서 배운다는 것은 정말

재미있지 않은가? 아이폰 SDK는 계속해서 진화해왔고 각각의 릴리즈마다 학습해야 하는 새로운 개

념들과 익혀야 하는 새로운 디자인 패턴들을 보여주었다.

『More! 아이폰 3 프로그래밍』이라는 이름이 말하듯이, 이 책에서는 여러분이 『시작하세요! 아이폰

3 프로그래밍』이나 다른 아이폰 서적을 접했거나 혹은 직접 개발을 해 본 경험이 있다는 가정하에 진

행된다. 만약 여러분이 초보라 하여도 너무 걱정할 필요는 없다. 이 책은 특정 개념에 대해 설명을 하

고, 새로운 프로젝트를 직접 생성하고 빌드해보면서 소스코드를 분석하고, 몇몇 팁과 주의할 점을 배

우면서 진행된다.

이 책은 애플의 공식 퍼시스턴스 프레임워크인 코어 데이터(Core Data)에 대한 설명으로 구성된 몇

개의 장(Chapter)으로 시작된다. 영속화(persistence)에 대한 개념이 낯설더라도 걱정할 필요는 없다.

영속화는 여러분의 앱이 실행 중에도 계속해서 데이터를 유지한다는 개념이다. 『시작하세요! 아이폰

3 프로그래밍』에서는 코어 데이터에 대한 간단한 설명을 다루고 있지만, 『More! 아이폰 3 프로그래

밍』에서는 재사용이 가능한 다양한 코드들을 이용하여 코어 데이터에 대해 포괄적으로 학습할 것이

다. 코어 데이터 관련 장들에 대한 학습이 끝나면 여러분은 아이폰 앱에서 코어 데이터를 자유롭게 사

용할 준비가 될 것이다.

그 다음으로 GameKit와 네트워킹에 관한 몇 개의 장이 이어진다. GameKit 프레임워크는 여러분

의 앱에 블루투스 연결을 쉽게 구현할 수 있도록 해준다. two-person game이라는 간단한 게임 구현

을 통해 GameKit에 대해 알아볼 것이다. 그러고 나서 아이폰 네트워킹에 대해 학습한 후, 랜을 통한

네트워크 게임에 대해서 알아볼 것이다. 이 부분은 인터넷 플레이를 위한 준비단계이다. 네트워킹 장

의 최종 목표는 인터넷으로부터 데이터를 가져오고 웹서버와 통신하는 것이다.

이 책의 장들은 여러분이 요청한 주제들로 이루어져 있다. 우리는 여러분이 이 책으로부터 충분한

가치를 얻길 바란다. 이 책에서는 MediaPlayer 프레임워크를 통해 iPod의 기능을 추가하는 부분도

다루고 있으며, MapKit, 애플리케이션에서 e-mail 사용, 최종 장에서는 병렬성과 디버깅 테크닉을 다

루고 있다.

Page 21: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xxi

이 책을 읽고 있는 독자 여러분에게 감사의 말을 전하고 싶다. 언제든지 http://iphonedevbook.

com/forum을 통해서 원하는 정보를 얻길 바란다. 그리고 여러분의 프로젝트에 대한 이야기를 들려

주면 더욱 좋겠다. 우리는 언제든지 기꺼이 여러분의 이야기를 경청할 준비가 되어 있다. 이 책과 함께

즐거운 코딩이 되길!

- 데이브와 제프

Page 22: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

xxii

Page 23: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

1

여전히 아이폰 애플리케이션을 제작하고 있는가? 대단하다! 아이폰은 대단한 성공을 이루어냈고, 그

결과 모바일 애플리케이션이 공급되는 과정이 근본적으로 달라졌으며, 사람들의 모바일 폰에 대한 기

대치도 높아졌다. 아이폰 SDK(So�ware Development Kit)가 2008년 3월 처음 공개된 이후, 애플은 새

로운 기능을 추가하고, 이미 있던 기능들을 개선하기 위해 분주히 움직여왔다. 또, 우리 같은 서드파

티(third party) 개발자들에게 더 많은 양의 기능들을 선사함으로써 더욱 흥미로운 개발 환경을 제공

하고 있다.

이 책에 대해

이 책은 여러분이 더 나은 아이폰 개발을 계속해서 할 수 있도록 가이드라인을 제시해 줄 것이다. 『시

작하세요! 아이폰 3 프로그래밍』에서의 목표는 기본적인 학습곡선을 넘어, 아이폰 애플리케이션 제

작의 기본기를 체득하는 것이었다. 이 책에서는 여러분이 아이폰 애플리케이션 개발에 대한 기본적인

지식을 갖고 있다고 가정할 것이다. 아이폰 SDK 3.0의 새로운 API들을 소개하고, 아이폰 개발에 필요

한 고급 기법들을 다룰 것이다.

『시작하세요! 아이폰 3 프로그래밍』은 각 장별로 독특한 프로젝트나 여러 가지 프로젝트를 보여주

는, 서로 독립적인 장으로 구성되었다. 이 책에서는 3분의 2가량을 이와 유사한 방식으로 접근할 것이

다. 하지만 2장부터 7장까지는 하나의 코어 데이터 애플리케이션을 발전시키는 것에 초점을 맞출 것이

01다시 시작하기

Page 24: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

2 l More! 아이폰 3 프로그래밍

다. 각 장은 코어 데이터의 특정 기능에 대해 설명하고 애플리케이션을 확장해 나갈 것이다. 또한 애플

리케이션의 규모가 커지고 관리하기 어려워지는 것을 막기 위한 기법에 대해서도 다룰 것이다.

알아야 할 것들

이 책은 여러분이 이미 프로그래밍에 대해 어느 정도 알고 있고 『시작하세요! 아이폰 3 프로그래밍』이

나 이와 유사한 자료를 통해서 아이폰 SDK에 대한 기본적인 지식을 갖고 있다고 가정한다. 또한 여러

분이 한두 개의 작은 프로그램 개발을 통해서 어느 정도 SDK를 다루어 봤고, Xcode와 인터페이스 빌

더에 대해 어느 정도 지식을 갖고 있다고 가정한다.

아이폰 개발이 처음인가?

여러분이 아이폰 개발을 처음 접한다면, 이 책을 읽기 전에 먼저 다른 책을 읽어야 한다. 만약 기

본적인 프로그래밍과 C언어의 문법을 모른다면 맥킨토시 프로그래머들을 위해 C언어에 대해 전

반적으로 다룬 <Learn C on the Mac by Dave Mark(Apress, 2008)>을 읽어보자.

http://www.apress.com/book/view/1430218096

C에 대한 이해는 있지만 객체지향 프로그래밍에 대한 경험이 없는 경우, <Learn Objective-C

on the Mac(Apress, 2008)>을 읽어보자. 맥 프로그래밍 전문가인 마크 데일림플과 스콧 내스터

가 쓴 훌륭한 책으로, 주제에 쉽게 접근해가며 서술하는 오브젝티브C 입문서이다.

http://www.apress.com/book/view/1430218150

다음으로, 애플 아이폰 개발 센터 사이트에 방문해 <The Objective-C 2.0 Programming

Language>를 다운로드하자. 이 문서는 매우 상세하고 포괄적인 설명이 담겨 있는 뛰어난 레퍼런

스 가이드이다.

http://developer.apple.com/iphone/library/documentation/Cocoa/Conceptual/ObjectiveC/

물론, 문서를 읽기 전에 로그인해야 한다는 사실은 잊지 말자.

오브젝티브C를 어느 정도 다룰 수 있게 되었다면 아이폰 SDK의 기본적인 기능들을 마스터할 차

례이다. 『시작하세요! 아이폰 3 프로그래밍』을 읽어보자.

http://www.wikibook.kr:8180/JSPWiki/Wiki.jsp?page=IPhone3

Page 25: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

01 다시 시작하기 l 3

시작하기 전에 준비해 두어야 하는 것들

아이폰 소프트웨어를 작성하기에 앞서, 몇 가지 준비할 것이 있다. 입문자라면 레퍼드 운영체제(OS X

10.5.6 이상)가 구동되는 인텔 기반 매킨토시를 준비하자. 사실 노트북이든 데스크톱이든 상관없이,

2006년 이후에 출시된 매킨토시 컴퓨터라면 어떤 기종이든지 무방하지만, 반드시 인텔 기반이고 스

노우 레오파드(Snow Leopard)와 호환되는지 확인해야 한다.

참고

여러분은 사실 레퍼드(OS X 10.5 이상)를 이용하여 아이폰 개발을 할 수 있으나, 스노우 레오파드에서만 사

용 가능한 몇몇 훌륭한 기능들이 Xcode에 추가되었다. 그러므로 여러분이 이전 버전의 OS를 사용하고 있다

면 스노우 레오파드로 업그레이드하길 권장한다.

당연하겠지만 아이폰이나 아이팟 터치가 필요하다. 여러분의 코드 대부분을 아이폰 시뮬레이터를

통해 테스트할 수 있지만, 모든 프로그램이 시뮬레이터에서 작동되는 것은 아니다. 여러분은 애플리

케이션의 공개를 결정하기 전에 실제 기기에서 제대로 동작하는지 확인해보고 싶을 것이다.

최종적으로 등록된 아이폰 개발자로 가입하는 과정이 필요하다. 애플은 여러분이 아이폰 SDK를

다운로드하기 전에 이 과정을 거칠 것을 요구할 것이다. 여러분이 이미 등록된 아이폰 개발자라면 최

신 아이폰 개발 도구를 받고 가입 과정을 생략해도 된다.

등록된 아이폰 개발자 프로그램이 처음이라면, http://developer.apple.com/iphone/ 을 방문하자.

사이트에 들어가면 그림 1-1과 유사한 화면이 보일 것이다. 아이폰 개발 센터 배너 밑으로 페이지의 오

른쪽을 보면 ‘Login and Register’라는 링크를 발견할 수 있을 것이다. ‘Register’ 링크를 클릭한다. 페

이지가 바뀌면 ‘Continue’ 버튼을 클릭한다. 다음으로 나오는 지시사항에 따라 여러분의 애플 아이디

를 사용하거나 새로운 아이디를 생성한다.

등록이 완료되면 여러분은 무료, 표준, 기업용 프로그램 옵션 중 하나를 선택해야 한다. 어느 것을

선택하든 아이폰 SDK와 Xcode, 애플의 통합 개발 환경(IDE: Integrated Development Environment)을

사용할 수 있다. Xcode는 소스코드 생성, 디버깅, 애플리케이션 컴파일, 애플리케이션의 성능을 조절

하는 도구들을 포함하고 있다.

무료 프로그램은 이름에서 알 수 있듯이 무료이다. 이 프로그램을 통해 여러분은 애플리케이션을

아이폰 시뮬레이터에서 실행할 수 있지만, 아이폰이나 아이팟 터치로 애플리케이션을 다운로드할 수

는 없고 애플의 앱스토어를 통하여 애플리케이션을 판매할 수 없다. 이 책에 나오는 몇몇 애플리케이

Page 26: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

4 l More! 아이폰 3 프로그래밍

션들은 반드시 아이폰이나 아이팟 터치 같은 기계상에서 동작해야 한다. 다시 말해 여러분이 무료 프

로그램을 선택하면 이 책의 모든 예제를 실행시킬 수 없다는 것이다. 이런 예제들을 제외하고 학습을

하겠다면, 무료 프로그램을 선택하는 것도 괜찮을 것이다.

그림 1-1 | Apple iPhone Dev Center 웹사이트

표준 프로그램은 99달러로 책정되어 있다. 개발 도구와 기술 지원 자료, 애플리케이션을 애플 앱스토

어에 배포할 수 있는 권리, 그리고 가장 중요한, 여러분의 코드를 시뮬레이터가 아닌 아이폰에서 테스

트하고 디버깅할 수 있는 일련의 도구들을 제공한다. 기업용 프로그램은 299달러이며, 아이폰과 아이

팟 터치를 위한 독립적인 사내(in-house) 애플리케이션을 개발할 수 있도록 설계되어 있다. 두 프로그

램에 대한 자세한 내용은 http://developer.apple.com/iphone/program/ 를 참조하기 바란다.

Page 27: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

01 다시 시작하기 l 5

참고

표준 또는 기업용 프로그램에 가입하려 한다면 지금 당장 가입하는 편이 좋다. 승인과정에 시간이 꽤 걸리고,

애플리케이션을 아이폰 또는 아이팟 터치에서 구동하기 위한 승인 과정이 별도로 필요하기 때문이다. 너무

걱정할 필요는 없다. 이 책의 초반 몇 장에 나오는 모든 프로젝트들과 이 책에 나오는 대부분의 애플리케이션

들은 아이폰 시뮬레이터에서도 잘 동작한다.

아이폰은 무선 인프라를 이용해서 네트워크에 항상 연결될 수 있는 모바일 디바이스이기 때문에 애

플은 결함여부나 별도의 인허가 없이도 프로그램을 작성하고 배포할 수 있었던 맥 개발자들보다 훨

씬 더 많은 제약을 아이폰 개발자들에게 부과했다. 사실 애플의 본래 의도는 제약을 추가하려는 것보

다는 악성 프로그램 또는 잘못 작성된 프로그램들이 공유 네트워크의 성능을 저하시킬 수 있는 기회

를 최소화하기 위한 것이라고 보는 편이 맞다. 자, 이쯤 되면 아이폰 개발이 마치 불구덩이를 뛰어넘는

것처럼 어렵게 느껴질 수도 있을 것이다. 하지만, 애플은 가능한 한 개발자들이 괴로워하지 않을 개발

프로세스를 만들려고 많은 노력을 기울이고 있다.

이 책의 구성

앞에서 말했듯이 2장부터 7장까지는 애플의 퍼시스턴스 프레임워크인 코어 데이터를 다룬다. 나머지

장에서는 아이폰 SDK 3.0의 특정 기능들이나 『시작하세요! 아이폰 3 프로그래밍』에서는 다루기에 복

잡한 기능들에 대해서 다룬다.

책의 이후 구성이 어떻게 되어 있는지 간단히 살펴보자.

2장에서는 코어 데이터에 대해 소개할 것이다. 이 장에서는 왜 코어 데이터가 아이폰 개발에 있 ▒

어서 왜 중요한 부분인지 살펴보고 간단한 코어 데이터 애플리케이션을 분석하면서 코어 데이

터의 각 부분이 어떻게 애플리케이션을 지원하는지 확인할 것이다.

3장에서는 데이터를 보여주고, 추가하고, 삭제하는 기능들에 대해 알아본다. 코어 데이터의 ▒

용어와 구조에 대해서 알아본 후, 데이터 삽입, 검색 등의 기본적인 기능들에 대해 알아볼 것

이다.

4장에서는 코어 데이터에 의해 저장된 데이터를 사용자들이 수정하고 교체할 수 있게 해주는 ▒

방법에 대해서 알아본다. 일반적이며 재사용이 가능한 뷰를 만드는 테크닉을 살펴봄으로써,

동일한 코드로 다른 형태의 데이터를 표시할 수 있다.

Page 28: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

6 l More! 아이폰 3 프로그래밍

5장에서는 사용자들이 애플리케이션의 이전 데이터들을 계속적으로 사용하고 있는 상황에 ▒

서 애플의 도구들을 이용하여 애플리케이션의 데이터 모델을 수정하는 방법을 알아본다.

6장에서는 코어 데이터의 진정한 위력을 경험하기 위해서 클래스들을 특정 데이터의 인스턴스 ▒

들로 표현하도록 분류하는 것을 알아본다. 어떻게 사용자 관리 객체들을 사용하는지 살펴보

고, 이로 인해 얻을 수 있는 장점들에 대해 알아볼 것이다.

코어 데이터의 마지막 장인 7장에서는 애플리케이션을 확장할 수 있는 몇몇 방법들에 대해서 ▒

알아볼 것이다. 애플리케이션을 리팩토링함으로써 이전 장에서 만들었던 애플리케이션에 별

도의 클래스를 추가하지 않고 데이터를 확장할 수 있다.

8장에서 알아볼 내용은 SDK 3.0에서 소개된 가장 근사한 기능 중 하나인 GameKit 프레임워 ▒

크이다. 이 프레임워크는 아이폰과 아이팟 터치용 멀티플레이 게임을 블루투스를 통해 통신하

면서 쉽게 구현할 수 있도록 한다. 더불어 간단한 2인용 게임을 개발해봄으로써 GameKit 프

레임워크에 대한 이해를 높인다.

GameKit 프레임워크로는 와이파이 ▒ (Wi-Fi)나 인터넷을 통한 멀티플레이 게임을 만들 수 없다.

9장에서는 이전 장에서 개발한 2인용 게임을 확장함으로써 블루투스가 아닌 다른 네트워크에

서도 플레이가 가능하도록 만들어본다.

아이폰은 항상 네트워크에 연결되어 있는 장비다. 그러므로 웹이나 인터넷에서 데이터를 어떻 ▒

게 가져오는지 알아두면 많은 도움이 될 것이다. 10장에서는 웹서버와 통신하는 몇몇 기술들

을 살펴본다.

11장에서는 아이폰 SDK 3.0의 새로운 기능 중 하나인 MapKit에 대해서 알아볼 것이다. 이 프 ▒

레임워크는 여러분이 구글 맵을 애플리케이션에서 직접 다룰 수 있게 해준다.

아이폰 SDK의 이전 버전에서는 애플리케이션에서 이메일을 보내려면 Mail 애플리케이션을 ▒

실행해야 했다. 그러나 3.0 버전에서는 애플리케이션에서 바로 메일을 발송할 수 있다. 12장에

서는 이런 기능들을 어떻게 구현하는지 알아본다.

아이폰 또는 아이팟 터치는 저장된 사용자의 전체 오디오 트랙의 라이브러리에 접근하는 것이 ▒

가능하다. 13장에서는 오디오 트랙을 찾고, 검색하고, 재생하는 기술들을 알아본다.

장시간 실행되는 연산들은 아이폰의 사용자 인터페이스(UI: User Interface)를 교착상태에 빠 ▒

뜨린다. 14장에서는 동시성 구현에 대해 알아본다.

Page 29: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

01 다시 시작하기 l 7

어느 프로그램도 완벽한 것은 없다. 버그와 결함은 프로그래밍 과정에서 자연스러운 것이다. ▒

15장에서는 아이폰 SDK 프로그램에서 버그를 찾고 수정하는 기법을 알아본다.

슬프지만 모든 여정은 끝나게 되어 있다. 16장에서는 이 책을 마무리하며, 몇몇 유용한 정보를 ▒

제공할 것이다.

준비 되었는가?

『시작하세요! 아이폰 3 프로그래밍』에서도 말했듯이 아이폰은 여러분에게 개발의 기쁨을 선사하는

흥미로운 첨단기술이자 매우 놀랍고 새로운 컴퓨팅 플랫폼이다. 이 책을 통해서 여러분은 아이폰 개

발에 한 발 더 앞으로 내딛고, SDK에 대해서 더 깊이 배우며, 새롭고 진보된 다양한 주제들을 접할 수

있을 것이다.

책을 보면서 단순히 책의 코드들을 한두 번 따라하는 데서 그치지 말고 여러분 스스로 프로젝트를

만들어 보기를 바란다. 다음 프로젝트로 넘어가기 전에 여러분이 지금 무엇을 하고 있는지 정확히 인

지하기 바란다. 코드를 수정하는 것을 두려워해서는 안 된다. 실험해보고, 코드를 꼬아도 보고, 그 결

과를 지켜보기도 하면서 되새기고 반복하라.

아이폰 SDK를 설치하였는가? 페이지를 넘겨 이제 시작해 보자. 앞으로의 여정이 여러분을 기다리

고 있다.

Page 30: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

Mor

e iP

hone

3 D

evel

opm

ent

Page 31: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

제 1 부

코어 데이터

코어 데이터는 파일 시스템에 데이터를 유지하기 위한 애플의 프레임워

크이다. 코어 데이터를 사용하여 프로그램의 데이터를 객체로 다루며 효

율적으로 데이터를 저장하고, 검색한 후 가져올 수 있다. 이 후 몇장에서

는 코어 데이터를 이용하여 기존의 영속화 메커니즘보다 더 빠르고 고성

능으로 앱을 개발하는 방법에 대해 살펴보도록 하겠다.

Page 32: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

10 l More! 아이폰 3 프로그래밍

Page 33: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

11

코어 데이터는 애플리케이션의 데이터를 자동으로 아이폰의 파일 시스템으로 저장하는 프레임워크이

자 도구 집합이다. 코어 데이터는 일종의 객체 관계 맵핑(ORM: Object-Relational Mapping)에 해당하는

데, ORM은 Objective-C 객체에 저장된 데이터를 가져다가 SQLite 같은 데이터베이스에 쉽게 저장할

수 있는 형태로 변환 또는 매핑하거나 일반 파일로 변환해주는 세련된 방법이다.

처음 코어 데이터를 사용한다면 이것은 하나의 마술과 같이 느껴질 것이다. 객체가 단순히 객체 그

자체로 다뤄지고, 마치 그들 스스로가 어떻게 데이터베이스나 파일 시스템에 저장되는지 알고 있는 것

처럼 보일 것이다. 여러분은 SQL 문장을 만들거나 파일 관리자를 호출할 필요가 전혀 없다. 코어 데이

터는 복잡하고 어려운 프로그래밍 작업(task)으로부터 여러분을 해방시켜 준다. 코어 데이터를 사용하

면 애플리케이션에서 훨씬 더 복잡한 데이터 모델을 이용할 수 있고, 이는 SQLite, 객체 아카이브, 플

랫 파일 1 을 직접 사용하는 것보다 훨씬 빠르다.

복잡성을 숨기는 방식은 코어 데이터를 ‘부두 프로그래밍’으로 만들 수도 있다. 부두 프로그래밍이

란 위험한 프로그래밍 기법으로 여러분이 이해할 필요가 없는 코드를 애플리케이션에 추가하는 기

법이다. 가끔 이 불가사의한 코드는 프로젝트 템플릿의 형태로 등장한다. 또는 여러분이 원하는 기

능을 수행하는 세부적인 사항을 알 필요가 없는, 다운로드한 유틸리티 라이브러리에서도 볼 수 있다.

부두 코드는 여러분이 원하는 기능을 수행하며, 이를 분석하기 위해 시간을 들일 필요가 없다. 이 코

1   (옮긴이) 단일 레코드형 레코드의 순서적 집합으로 이루어진 파일. 플랫 파일에는 레코드들의 관계를 지배하는 계층 구조 정보가 없다.

02코어 데이터의 구조

Page 34: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

12 l More! 아이폰 3 프로그래밍

드는 코드 자체의 오류가 발생하기 전까지 가만히 자리를 잡고 마술을 부린다. 일반적으로 항상 이렇

게 되는 건 아니지만 자신이 만든 애플리케이션 코드 전체를 이해하지 못한다는 것은 좀 더 연구를

해야 한다든가, 적어도 이처럼 불가사의한 코드를 다룰 수 있고 경험해본 사람을 찾아야 한다는 것을

의미한다.

요점은 현재 진행하는 많은 프로젝트에서 코어 데이터가 쉽게 불가사의한 코드가 될 수 있다는 것

이다. 비록 코어 데이터가 무슨 일을 하는지 전부 다 알 필요는 없지만 어느 정도 시간을 들여 코어 데

이터의 구조를 전반적으로 이해할 필요는 있다.

이 장에서는 코어 데이터의 간단한 역사를 살펴보고, 코어 데이터 템플릿에 대해 좀 더 깊이 있게 알

아볼 것이다. 또, Xcode의 기본적인 코어 데이터 템플릿을 해부해 보는 것도 앞으로 등장할 복잡한 코

어 데이터 프로젝트를 이해하는 데 많은 도움이 될 것이다.

코어 데이터의 간략한 역사

코어 데이터는 꽤 오래되긴 했지만 아이폰에 적용된 것은 아이폰 SDK 3.0부터다. 코어 데이터는 Mac

OS X 10.4(Tiger)에 처음 도입됐지만 코어 데이터의 근원은 대략 15년 전으로 거슬러 올라가 NeXT

사의 WebObjects 웹 개발 도구 세트의 일부인 엔터프라이즈 오브젝트 프레임워크(EOF: Enterprise

Objects Framework)라고 하는 NeXT 프레임워크에서 유래했다.

처음 소개됐을 당시 EOF는 원격지의 데이터베이스와 협업하는 꽤 혁명적인 도구였다. 비록 지금은

거의 대부분의 언어를 지원하는 좋은 ORM 도구가 많지만, WebObjects가 등장한 초기에는 대다수

의 웹애플리케이션은 영속적인 데이터 저장을 위해 개발자가 직접 만든 SQL을 사용하거나 파일 시스

템을 이용했다. 그때 당시만 해도 웹애플리케이션 개발은 상당한 시간과 인력이 필요한 일이었다. EOF

의 일부인 WebObjects는 복잡한 웹애플리케이션을 구현하는 데 필요한 시간을 상당수 줄여주었다.

WebObjects의 일부를 구성하는 EOF는 코코아의 전신인 NeXTSTEP에서도 사용되었다. 애플이

NeXT를 인수했을 때 애플의 개발자들은 EOF의 많은 개념을 받아들여 코어 데이터라는 새로운 영

속화 도구(persistence tool)를 만들었다. 코어 데이터는 EOF가 이전에 웹애플리케이션에 대해 수행

했던 일을 데스크톱 애플리케이션에 대해서도 수행해 준다. 코어 데이터는 파일 시스템에 쓰거나 임

베디드 데이터베이스와 상호작용하는 데 필요한 코드를 제거하여 개발자의 생산성을 극적으로 끌

어올렸다.

이제, 코어 데이터 Xcode 템플릿을 살펴보도록 하자.

Page 35: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 13

코어 데이터 애플리케이션 만들기

Xcode를 실행하고 File 메뉴의 [New Project...]를 선택하거나 N을 누른다. New Project 도움창

(assistant)이 뜨면 왼쪽 부분의 iPhone OS 항목 밑에 있는 Application을 선택하고 오른쪽 윗부분에

서 Navigation-based Application을 선택하자. 이때, 그림 2-1처럼 Use Core Data for Storage 항목

을 체크해야 한다. 이 체크박스는 Xcode에게 모든 코드와 기타 부가적인 것들에서 코어 데이터를 사

용할지 여부를 알려준다. 모든 Xcode 프로젝트 템플릿에 이 옵션이 있는 것은 아니지만 Navigation-

based Application과 Window-based Application 템플릿에서는 이 옵션을 사용할 수 있다.

그림 2-1 | 코어 데이터를 사용하는 프로젝트를 Xcode에서 생성하자

프로젝트 이름에는 CoreData라고 입력한다. 이제 빌드하고 실행해 보자. 시뮬레이터와 실제 장비에

서도 잘 실행될 것이다. 그림 2-2와 유사한 모습을 확인할 수 있을 것이다.

Page 36: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

14 l More! 아이폰 3 프로그래밍

그림 2-2 | 코어 데이터를 사용하는 Navigation-based Application

프로젝트가 컴파일되면 이 애플리케이션이 생성된다.

오른쪽 상단에 있는 더하기 아이콘을 탭해보면 테이블에 더하기 버튼을 탭한 시간이 새로운 행으

로 추가될 것이다. 왼쪽 상단에 있는 Edit 아이콘을 탭해보면 행을 지울 수도 있다. 흥미롭지 않은가?

경고

초기 버전의 코어 데이터 Navigation-based Application 템플릿에는 작은 버그가 하나 있다. 테이블의 마

지막 행을 지우면 애플리케이션이 충돌을 일으킨다. 이 버그는 SDK 3.1에서 수정되었다.

이 간단한 애플리케이션 안에서는 수많은 일들이 일어난다. 한번 살펴보자. 클래스나 파일, 혹은 데

이터베이스와 상호작용하는 코드도 없이 더하기 버튼을 누르는 것으로 객체가 생성되고, 데이터가 채

워지고, 데이터가 SQLite 데이터베이스에 자동으로 저장된다.

애플리케이션이 어떻게 동작하는지 봤을 것이다. 이제 이 동작 뒤에서 어떤 행위가 이뤄지는지 살

펴보자.

Page 37: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 15

코어 데이터의 개념과 용어

대다수의 복잡한 기술이 그러하듯 코어 데이터도 입문자가 어려워할 만한 자체적인 용어를 사용하고

있다. 코어 데이터에 대한 미스터리를 깨고 코어 데이터의 명명법에 대해 알아보자.

그림 2-3은 코어 데이터의 아키텍처를 간략하게 나타낸 다이어그램이다. 지금 전부를 다 이해하려

고 하지는 말자. 나중에 이것들이 어떻게 서로 작용하는지 알아보기 위해 이 그림들을 다시 보게 될

것이다.

데이터 모델

영구저장소영구 저장소코디네이터

엔터티 설명 기반으로 하다

가져온다

술어

관리 객체

관리 객체 컨텍스트 페치 리퀘스트

그림 2-3 | 코어 데이터 구조를 고수준에서 본 그림이다. 이 장에서 각각의 구성요소들을 살펴보도록 하겠다.

여기서는 5가지 주요 개념에 초점을 맞추겠다. 이 장을 보려면 아래의 개념을 잘 이해하고 있어야

한다.

▒ 영구 저장소 (persistent store)

▒ 데이터 모델 (Data Model)

▒ 영구 저장소 코디네이터 (persistent store coordinator)

▒ 관리 객체와 관리 객체 컨텍스트 (managed object and managed object context)

▒ 페치 리퀘스트 (Fetch request)

다시 한 번 말하지만 이 개념들을 지금 다 외우려고 하지 말자. 이 장을 읽어나가다 보면 각 개념이

어떻게 작용하는지 알게 될 것이다.

데이터 모델과 영구 저장소

기반 저장소(backing store)라고도 하는 영구 저장소는 코어 데이터의 데이터를 저장하는 곳이다. 아이

폰에서는 기본적으로 코어 데이터는 애플리케이션의 도큐먼트 폴더에 들어 있는 SQLite 데이터베이

스를 영구 저장소로 사용한다. 하지만 코드를 한 줄만 수정해도 다른 코드에 영향을 주지 않고 영구

저장소를 변경할 수 있다. 실제 코드가 어떻게 바뀌는지는 잠시 후에 보도록 하자.

Page 38: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

16 l More! 아이폰 3 프로그래밍

경고

일단 애플리케이션을 앱스토어에 등록하면 영구 저장소의 형태를 바꿔서는 안 된다. 어떤 이유에서건 영구

저장소의 형태를 바꾸려면 기존 영구 저장소에 저장된 사용자의 데이터를 옮기는 코드를 작성해야 하며, 그

렇지 않으면 사용자는 기존의 데이터를 잃을 수밖에 없다.

모든 영구 저장소는 영구 저장소가 저장할 수 있는 데이터 타입을 정의한 하나의 데이터 모델과 연

결되어 있다. Xcode의 Groups&Files 아래에 Resources 폴더를 열어보면 CoreData.xcdatamodel이

란 파일을 볼 수 있을 것이다. 이 파일은 프로젝트의 기본 데이터 모델이다. 앞에서 선택한 프로젝트

템플릿은 하나의 영구 저장소를 생성하고 하나의 데이터 모델과 연결돼 있다. CoreData.xcdatamodel

을 클릭해보면 그림 2-4와 같은 Xcode의 데이터 모델 에디터가 나올 것이다. 애플리케이션을 설계할

때, 데이터 모델 에디터에서는 애플리케이션의 데이터 모델을 생성할 수 있다.

이 장에서는 템플릿과 함께 제공되는 데이터 모델에 대해 알아보겠다. 이어지는 3장에서는 데이터

모델 에디터를 사용하여 사용자 데이터 모델을 실제로 구현해보겠다.

데이터 모델 에디터를 살펴보자. 에디터 윈도우 가운데에는 둥근 사각형 하나가 있다. 이 사각형은

엔터티(entity)라고 알려져 있다. 사실상 엔터티는 다양한 데이터 요소를 하나의 우산 밑에 두어 감싸

는 것과 같은, 객체지향 세계의 클래스 정의와 비슷하다. 이 특별한 엔터티는 이벤트라고 하며, 이것은

속성(Attribute)과 관계(Relationships)로 구성되어 있다. 기본적으로 timeStamp라는 하나의 속성이 생

성되고, 관계는 생성되지 않는다.

엔터티 사각형의 밖을 클릭해 보자. 사각형의 타이틀 바가 분홍색으로 바뀔 것이다. 다시 엔터티를

클릭해 보자. 타이틀 바가 파란색으로 바뀌고 이것은 엔터티가 선택됐음을 의미한다.

이 엔터티는 템플릿의 일부로 생성되었다. 이 템플릿을 이용해서 코어 데이터 애플리케이션을 만들

면 이벤트 엔터티를 손쉽게 얻을 수 있다. 데이터 모델을 설계할 때는 대부분의 이벤트 엔터티를 삭제

할 것이고 맨 처음부터 새로운 엔터티를 만들 것이다.

Page 39: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 17

그림 2-4 | 데이터 모델 에디터는 데이터 모델을 시각적으로 설계할 수 있도록 해준다.

조금 전에 코어 데이터 샘플 애플리케이션을 시뮬레이터에서 실행해 봤다. 더하기 버튼을 누르면 새

로운 이벤트 인스턴스가 생성된다. 앞으로 몇 페이지에 걸쳐 상세하게 볼 엔터티는 코어 데이터를 사

용하지 않는 애플리케이션에서 데이터를 저장할 때 쓰는 오브젝티브C의 데이터 모델 클래스를 대체

한다.

다시 데이터 모델 에디터로 돌아가 이것이 어떻게 동작하는지 살펴보자. 지금은 영구 저장소가 코어

데이터의 데이터가 저장되는 장소라고만 기억하자. 그리고 모든 영구 저장소는 오직 단 하나만의 데이

터 모델을 가진다는 것도 함께 기억하자.

Page 40: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

18 l More! 아이폰 3 프로그래밍

데이터 모델 클래스 : NSManagedObjectModel

직접 애플리케이션의 데이터 모델에 접근하진 않겠지만 메모리상에서 오브젝티브C 클래스가 데이터

모델을 나타내고 있다는 사실을 알아둘 필요가 있다. 이 클래스는 NSManagedObjectModel이라 하

며, 템플릿은 프로젝트 폴더 안에 NSManagedObjectModel 데이터 모델 파일을 기반으로 인스턴스

를 생성한다. 생성된 코드를 한번 보자.

프로젝트 윈도우의 Groups&Files 부분에서 Classes 그룹을 선택하고 CoreDataAppDelegate.m

파일을 클릭한다. 에디터의 윗부분에 있는 function 메뉴를 클릭하면 이 클래스에 속하는 메서드의

목록을 볼 수 있다(그림 2-5). -managedObjectModel을 선택하면 화면이 CoreData.xcdatamodel 파

일 기반의 객체 모델을 생성하는 메서드로 이동할 것이다.

그림 2-5 | 에디터 페인의 팝업 메뉴

메서드는 아래 코드와 같을 것이다.

/**

Returns the managed object model for the application.

If the model doesn't already exist, it is created by merging all of the models

found in the application bundle.

*/

- (NSManagedObjectModel *)managedObjectModel {

if (managedObjectModel != nil) {

return managedObjectModel;

Page 41: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 19

}

managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

return managedObjectModel;

}

여기서 처음으로 하는 일은 managedObjectModel이 nil인지 검사하는 것이다. 이 접근자 메서드는

지연 로딩(lazy loading) 2 을 사용한다. 밑에 있는 인스턴스 변수는 접근자 메서드가 처음 호출되기 전까

지는 실제로 인스턴스화되지 않는다. 이런 이유로 managedObjectModel에 직접적으로 접근해서는

안 된다(물론 접근자 메서드에서 접근하는 것은 예외다). managedObjectModel에 접근할 때는 항상

접근자 메서드를 사용하자. 그렇게 하지 않으면 아직 생성되지 않은 객체를 호출하는 결과가 일어날

수도 있다.

데이터 모델의 클래스는 NSManagedObjectModel이라 한다. 이 장의 뒤에서 보겠지만 코어 데이터에서는

데이터의 인스턴스를 관리 객체(managed objects)라고 부른다.

managedObjectModel이 nil이면 우리가 설정한 데이터 모델을 얻게 된다. 영구 저장소가 어떻

게 하나의 데이터 모델과 결합하는지 기억하는가? 앞서 이야기한 내용도 맞지만, 그게 다가 아니다.

.xcdatamodel 파일을 하나의 NSManagedObjectModel의 인스턴스에 넣을 수 있고, 여러 개의 파일

에 있는 엔터티를 하나의 데이터 모델로 합칠 수도 있다. 아래 한 줄의 코드는 Xcode 프로젝트에 있는

모든 .xcdatamodel 파일을 가져와 하나의 NSManagedObjectModel로 만든다.

managedObjectModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];

예를 들어, 새로운 데이터 모델 파일을 만들고 프로젝트에 추가하면, 기존의 CoreData.xcdatamodel

파일과 새로운 파일을 모두 포함하는 하나의 관리 객체로 통합된다. 이런 특징은 애플리케이션의 데

이터 모델을 여러 개의 더 작은 파일로 만들어 관리하기 쉽게 만들어준다.

코어 데이터를 사용하는 대다수의 애플리케이션은 하나의 영구 저장소와 하나의 데이터 모델로 구

성되어 있다. 이런 특징 덕분에 기본 템플릿의 코드는 대부분의 경우 잘 작동하고, 데이터 모델을 여러

개의 파일에 나누어 저장할 수 있다. 즉, 코어 데이터는 여러 개의 영구 저장소 사용을 지원한다는 것

이다. 예를 들면, 애플리케이션의 일부 데이터는 SQLite 영구 저장소에 저장하고 일부는 바이너리 파

2   (옮긴이) 지연 로딩은 객체의 초기화를 객체가 필요한 시점까지 연기하는 디자인 패턴이다. 지연 로딩을 적절하게 사용하면 프로그

램의 성능을 향상시킬 수 있다. http://en.wikipedia.org/wiki/Lazy_loading

Page 42: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

20 l More! 아이폰 3 프로그래밍

일에 저장하는 경우 관리 객체 모델을 개별적으로 불러오려면 initWithContentsOfURL:을 써서 코

드 템플릿을 바꿔야 한다.

영구 저장소와 영구 저장소 코디네이터

영구 저장소는 실제로 오브젝티브C 클래스로 표현되지는 않는다. 대신 NSPersistentStoreCoordinator

라고 하는 클래스가 영구 저장소로의 접근을 제어한다. 실질적으로 이 클래스는 다른 클래스가 영구

저장소에 읽기와 쓰기 신호를 보내면 이들을 중간에서 취해 이 신호들을 직렬화함으로써 한 파일이 동

시에 호출되면서 파일이나 데이터베이스가 잠기는 것(locking)을 방지한다.

관리 객체 모델처럼 템플릿은 애플리케이션에서 영구 저장소 코디네이터의 인스턴스를 생성해 반환

하는 일을 대신하는 메서드(델리게이트 메서드)를 제공한다. 저장소(store)를 생성하고, 이를 데이터

모델과 연동하고, 디스크에 위치하는 것(템플릿 안에서 처리되는 것들)을 제외하면 영구 저장소 코디

네이터와 상호작용할 일은 거의 없을 것이다. 여러분은 상위 수준의 코어 데이터 호출을 사용할 것이

며, 코어 데이터가 데이터의 검색과 저장을 위해 영구 저장소와 상호작용할 것이다.

영구 저장소 코디네이터를 반환하는 메서드를 살펴보자. CoreDataAppDelegate.m 파일의 function

메뉴의 팝업에서 -persistentStoreCoordinator를 선택하자.

/**

Returns the persistent store coordinator for the application.

If the coordinator doesn't already exist, it is created and the application's store

added to it.

*/

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator {

if (persistentStoreCoordinator != nil) {

return persistentStoreCoordinator;

}

NSURL *storeUrl = [NSURL fileURLWithPath: [[self applicationDocumentsDirectory]

stringByAppendingPathComponent: @"CoreData.sqlite"]];

NSError *error;

persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]

initWithManagedObjectModel: [self managedObjectModel]];

if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil URL:storeUrl options:nil error:&error]) {

// Handle error

Page 43: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 21

}

return persistentStoreCoordinator;

}

관리 객체 모델처럼 persistentStoreCoordinator 접근자 메서드는 지연 로딩을 사용한다. 즉, 영구

저장소 코디네이터에 접근하기 전까지는 인스턴스화하지 않는다. 인스턴스화가 되면 애플리케이션 샌

드박스의 도큐먼트 폴더에 위치한 CoreData.sqlite라는 파일에 접근하는 경로를 생성한다. 템플릿은

파일 이름을 항상 프로젝트 이름에서 따온다. 파일의 이름을 바꾸고 싶다면 바꿔도 되지만 사용자가

이 파일을 절대로 볼 수 없기 때문에 파일 이름을 바꾸는 것은 중요한 문제가 아니다.

경고

파일의 이름을 바꾸기로 마음먹었더라도 앱스토어에 애플리케이션을 올린 뒤에는 파일 이름을 바꾸면 안 된

다. 이름을 바꾸면 애플리케이션 사용자들은 기존 데이터를 잃게 될 것이다.

다음 코드를 살펴보자

if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil URL:storeUrl options:nil error:&error]) {

이 메서드의 첫 번째 매개변수는 영구 저장소의 타입을 결정하는 NSSQLiteStoreType이다.

NSSQLiteStoreType은 상수로서 코어 데이터가 영구 저장소로 SQLite 데이터베이스를 사용할 것이

라고 알려준다. SQLite 데이터베이스 대신에 바이너리 파일을 애플리케이션의 영구 저장소로 사용하

고 싶다면 NSSQLiteStoreType 대신 NSBinaryStoreType 상수를 쓸 수 있다. 대부분의 경우 기본 설

정이 최선의 선택이다. 만약 어쩔 수 없는 이유로 바꿔야 하는 경우가 아니라면 기본 설정을 그대로 쓰

도록 하자.

참고

아이폰에서 코어 데이터가 제공하는 세 번째 영구 저장소 타입은 인메모리 저장소(in-memory store)다. 인

메모리 저장소는 데이터를 데이터베이스나 파일에 저장하는 것이 아니라 메모리상에 저장하는 캐싱 메커니

즘을 구현할 때 주로 사용한다. 인메모리 저장소를 사용하려면 저장 타입으로 NSInMemoryStoreType을

설정해주면 된다.

Page 44: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

22 l More! 아이폰 3 프로그래밍

데이터 모델 돌아보기

코어 데이터의 다른 부분을 살펴보기 전에 이제까지 살펴본 것들이 어떻게 서로 동작하는지 간략히

살펴보자. 그림 2-3을 다시 참조하는 것이 좋을 것이다.

영구 저장소(또는 기반 저장소)는 아이폰 파일 시스템에 SQLite 데이터베이스나 바이너리의 형태로

존재하는 파일이다. 데이터 모델 파일(하나 이상의 .xcdatamodel 파일에 포함되는)은 애플리케이션

의 데이터 구조를 표현한다. 이 파일은 Xcode에서 수정할 수 있다. 데이터 모델은 영구 저장소 코디네

이터에게 영구 저장소에 저장돼 있는 모든 데이터의 형식을 말해준다. 영구 저장소 코디네이터는 데이

터의 저장, 검색을 요청하는 다른 코어 데이터 클래스에서 쓰인다.

엔터티와 데이터 모델 에디터

데이터 모델 에디터로 돌아가서 템플릿의 일부로 제공되는 단순한 데이터 모델을 자세히 들여다보자.

CoreData.xcdatamodel을 클릭해보자. 에디터는 그림 2-4와 같은 모습일 것이다.

엔터티

이전에 말했듯이 중앙에 있는 둥근 사각형은 엔터티이다. 사실상 엔터티는 오브젝티브C 클래스 선언

과 유사하다. 코어 데이터를 사용하지 않을 때 생성되는 데이터 모델 클래스는 코어 데이터를 사용하

면 엔터티로 변환된다.

각 엔터티의 이름은 대문자로 시작한다. 현재 화면에 보이는 엔터티의 이름은 이벤트다. 앞서 실행

해 본 템플릿 애플리케이션에서는 더하기 버튼을 누를 때마다 새로운 이벤트의 인스턴스가 생성되고

애플리케이션의 영구 저장소에 저장되었다.

데이터 모델 에디터에서 엔터티가 선택됐음을 색상으로 알 수 있다. 선택된 엔터티는 파란색 타이틀

바와 8개의 크기 조절 핸들로 표시될 것이다. 선택되지 않은 엔터티는 불그스름한 회색을 띠는 타이틀

바로 나타나며 크기 조절 핸들은 표시되지 않는다. 이벤트 엔터티의 타이틀 바를 클릭해서 이벤트를

선택하자.

이벤트 엔터티를 선택하고 엔터티 페인(entity pane)이라고 하는 왼쪽 윗부분에 있는 페인을 보자. 엔

터티 페인은 데이터 모델에 정의된 모든 엔터티의 목록을 보여준다. 이 프로젝트에 사용된 템플릿은

이벤트라는 하나의 엔터티를 생성했다. 엔터티 페인에서 이벤트를 선택하는 것은 아래 페인에서 둥근

Page 45: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 23

사각형을 선택하는 것과 같다. 아래 페인에서 엔터티의 바깥 부분을 클릭하여 엔터티 선택을 해제하

고, 엔터티 페인에 이벤트를 클릭해 보자. 아래 페인에 있는 엔터티가 선택될 것이다. 왼쪽 위와 아래쪽

의 페인은 같은 엔터티 목록을 다른 뷰로 보여준다.

프로퍼티

엔터티 페인이 데이터 모델의 모든 엔터티 목록을 표현할 때, 프로퍼티 페인이라고 하는 윗쪽 중간의

페인은 선택된 엔터티에 속하는 프로퍼티 목록을 보여준다.

엔터티 그 자체로는 데이터를 저장하는 메커니즘이 없다. 대신 엔터티는 한 개 이상의 프로퍼티로

구성돼 있다. 엔터티 페인에서 엔터티를 선택하면, 해당 엔터티의 프로퍼티가 프로퍼티 페인에 표시

된다.

엔터티는 많은 프로퍼티로 구성될 수 있다. 프로퍼티에는 속성, 관계, 페치드(fetched) 프로퍼티, 페

치 리퀘스트가 있다.

속성

엔터티를 생성할 때 가장 많이 사용되는 프로퍼티는 코어 데이터의 엔터티에서 오브젝티브C 클래스

의 인스턴스 변수처럼 사용되는 속성이다. 데이터 모델 에디터를 보면(또는 그림 2-4) 이벤트 엔터티가

이름이 timeStamp인 프로퍼티 하나를 갖고 있음을 볼 수 있다. timeStamp 속성은 이벤트 인스턴스

가 생성됐을 때의 날짜와 시간을 갖고 있다. 애플리케이션에서 더하기 버튼을 누르면 새로운 행이 추

가되고 이벤트의 timeStamp가 화면에 표시된다.

인스턴스 변수처럼 각 속성은 속성 페인의 세 번째 행의 팝업 메뉴에서 선택할 수 있는 타입 중 하

나를 갖는다. 팝업 메뉴를 자세히 보자. 엔터티 페인에서 이벤트가 선택됐는지 확인하고 속성 페인에

서 timeStamp를 선택한다. timeStamp 속성의 세 번째 행의 Date란 값을 기억해 두자. 세 번째 행을

클릭해 보면 그림 2-6처럼 속성 타입 옵션을 팝업 메뉴로 볼 수 있다. 확인만 하고 값을 바꾸지는 말자.

다른 속성 타입에 대해서는 이어지는 장에서 데이터 모델을 직접 만들 때 알아보겠다.

timeStamp와 같은 날짜 속성은 NSDate의 인스턴스에 대응된다. 날짜 속성에 새로운 값을 넣으려

면 NSDate의 인스턴스를 대응시키면 된다. 문자열 속성은 NSString의 인스턴스에 대응되며, 대부분

의 숫자 타입은 NSNumber의 인스턴스에 대응된다.

Page 46: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

24 l More! 아이폰 3 프로그래밍

데이터 모델 에디터에 있는 다른 버튼이나 텍스트 필드, 체크 박스에 대해 너무 걱정하지 말자. 향후 진행되

는 장에서 각각 어떤 동작을 하는지 알게 될 것이다.

그림 2-6 | 속성 타입 팝업 메뉴는 Type이나 Destination 행을 클릭하면 나타난다.

관계

이름이 암시하듯 관계는 두 엔터티 사이의 관계를 정의한다. 템플릿 애플리케이션에서 이벤트 엔터티

는 아무런 관계도 갖고 있지 않다. 7장에서 관계를 다루겠지만, 관계를 이해하기 좋은 예가 있다.

Employee(종업원)란 엔터티를 생성하고 데이터 구조 안에 종업원의 고용주를 저장한다고 가정하

자. NSString으로 고용주 속성을 삽입할 수 있지만 뭔가 제한되어 보인다. 좀 더 유연하게 접근한다면

Employer(고용주)라는 엔터티를 생성하고 Employee와 Employer 엔터티 사이에 관계를 설정할 수

있을 것이다.

관계는 일대일(to one)이나 일대다(to many) 3 가 될 수 있고, 이러한 관계는 특정 객체를 연결할 목

적으로 고안된 것이다. 종업원이 부업을 하지 않고 직업을 하나만 가진다고 가정하면 Employee

3   (옮긴이) 일대일 관계와 일대다 관계가 원서에는 각각 to-one relationships와 to-many relationships로 표시되어 있다. 이는 one-to-

one relationships와 one-to-many relationships와 같은 개념이지만 애플의 문서에서 사용된 용어를 저자들이 책에 그대로 반영하면

서 해당 용어를 사용하였다.

Page 47: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 25

와 Employer 사이의 관계는 일대일 관계가 된다. 반면 고용주가 여러 명의 종업원을 고용한다면

Employee와 Employer 사이의 관계는 일대다 관계가 된다.

이것을 오브젝티브C의 관점에서 본다면 하나의 관계는 다른 오브젝티브C 클래스의 인스턴스를 가

리키는 포인터를 인스턴스 변수로 사용하는 것과 유사하다. 일대다 관계는 여러 개의 객체를 담을 수

있는 NSMutableArray나 NSSet과 같은 콜렉션 클래스(collection class)를 사용하는 것과 유사하다.

페치드 프로퍼티

페치드 프로퍼티(fetched property)는 하나의 관리 객체로부터 생성된 쿼리와 같다. 예를 들어

Employee에 birthdate란 속성을 추가한다고 가정하자. 현재 Employee와 같은 birthdate란 속성을

갖고 있는 모든 Employee를 찾기 위해 sameBirthdate라는 페치드 프로퍼티를 추가할 수도 있다.

관계와 달리 페치드 프로퍼티는 객체와 함께 로드되지 않는다. 예를 들어 Employee가 Employer로

이어지는 관계를 맺고 있다면, Employee 인스턴스가 로드될 때 이에 대응하는 Employer도 로드될 것

이다. 하지만 Employee가 로드될 때 sameBirthdate 는 수행되지 않는다. 이것은 일종의 지연 로딩이

다. 페치드 프로퍼티에 대해서는 7장에서 더 자세하게 배울 것이다.

페치 리퀘스트

페치드 프로퍼티가 하나의 관리 객체로부터 생성된 쿼리와 같다면 페치 리퀘스트(fetch request)는 고

정된 쿼리를 실행하는 클래스 메서드와 유사하다. 예를 들어 키가 80인치(약 2m) 이상인 Employee

를 반환하는 canChangeLightBulb라는 이름의 페치 리퀘스트를 만들 수 있다. 전구를 교환하고 싶다

면 언제든지 페치 리퀘스트를 실행시킬 수 있다. 페치 리퀘스트가 실행될 때 코어 데이터는 전구를 갈

수 있는 잠재적인 Employee의 현재 목록을 찾기 위해 영구 저장소를 검색한다.

앞으로 공부할 여러 장에서 여러 페치 리퀘스트를 프로그래밍적으로 생성할 것이다. 이번 장에서는

후반부의 ‘페치 결과 컨트롤러 만들기’에서 가볍게 다룰 것이다.

관리 객체

엔터티는 데이터의 구조를 정의하지만 실제로 데이터를 갖고 있지는 않다. 데이터의 인스턴스를 관리

객체(managed object)라고 한다. 코어 데이터에서 사용하는 모든 엔터티의 인스턴스는 NSManaged

Object나 NSManagedObject를 상속받은 클래스의 인스턴스일 것이다.

Page 48: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

26 l More! 아이폰 3 프로그래밍

키-값 코딩

NSDictionary 클래스는 유일한 키를 사용하여 값을 검색하거나 객체를 데이터 구조에 저장할 수

있게 해준다. NSDictionary 클래스처럼 속성 값을 설정하고 검색하기 위해 NSManagedObject는

valueForKey:와 setValue:forKey: 메서드를 제공한다. 또한 NSManagedObject에는 관계와 관련된

추가적인 메서드도 있다. 예를 들어 특정한 관계를 표현하는 NSMutableSet의 인스턴스를 검색할 수

있다. 이 뮤터블 세트에 관리 객체를 추가하거나 삭제하는 것은 이것이 표현하는 관계에서 객체가 추

가되거나 삭제된다는 것을 의미한다.

NSDictionary가 생소하다면 Xcode를 실행하고 도큐멘테이션 뷰어에서 NSDictionary와 관련된

내용을 검색해 보기 바란다. 염두에 둘 중요한 개념으로 키-값 코딩(key-value coding) 또는 KVC라는

것이 있다. 코어 데이터는 관리 객체로부터 데이터를 저장하거나 검색하기 위해 KVC를 사용한다.

템플릿 애플리케이션에서는 NSManagedObject의 인스턴스가 하나의 이벤트를 나타낸다고 생각

하면 된다. 아래와 같이 timeStamp 속성에 저장된 값을 valueForKey:를 호출하여 검색할 수 있다.

NSDate *timeStamp = [managedObject valueForKey:@”timeStamp”];

timeStamp는 날짜 타입의 속성이기 때문에 valueForKey:에 의해 반환되는 객체가 NSDate의 인스

턴스가 되리라는 것을 알 수 있다. 이와 유사하게 값을 저장할 때는 setValue: ForKey:를 사용한다. 아

래는 managedObject의 timeStamp 속성에 현재 날짜와 시간을 저장하는 코드다.

[managedObject setValue:[NSDate date] forKey:@”timeStamp”];

KVC에는 키패스(keypath)라는 개념이 있다. 키패스를 이용하면 하나의 문자열을 이용해 객체

의 계층 구조를 탐색할 수 있다. 예를 들어 Employee 엔터티가 Employer란 엔터티를 가리키는

whereIWork란 관계를 맺고 있고, Employer 엔터티에 name이라는 속성이 있다면 키패스를 사용하

여 아래와 같이 Employee의 인스턴스의 name에 저장된 값을 얻을 수 있다.

NSString *employerName = [managedObject valueForKeyPath:@”whereIWork.name”];

valueForKey: 대신 valueForPath:를 사용한 것과 키패스 값을 위해 점 구분자를 둔 것에 주목하자.

KVC는 점을 이용하여 문자열을 파싱한다. 즉, 이 경우에는 whereIWork와 name의 두 가지 값으로

분리되어 파싱된다. 처음 나오는 키(whereIWork)를 활용하여 이 키에 상응하는 객체를 검색한다. 그러

고 나서 키 패스의 다음 값(name)을 갖고 이전의 호출에서 반환된 객체로부터 키에 저장되어 있는 값

을 검색한다. Employer는 일대일 관계이기 때문에 키패스의 첫 부분은 Employee의 고용주를 표현하

는 관리 객체 인스턴스를 반환할 것이다. 그러고 나서 키패스의 두 번째 부분은 Employer를 표현하는

관리 객체로부터 이름을 검색하는 데 사용된다.

Page 49: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 27

참고

코코아를 접해본 적이 있다면 이미 KVC와 키패스에 대해 생소하지 않을 것이다. 접해본 적이 없더라도 걱

정할 필요는 없다. 이 개념들은 머지않아 자연스럽게 이해하게 될 것이다. 키패스는 직관적으로 이해가 가

능하다.

관리 객체 컨텍스트

코어 데이터는 엔터티와 코어 데이터를 구성하는 나머지 것들 사이에서 게이트웨이 역할을 하는 객체

를 하나 유지한다. 이 게이트웨이를 관리 객체 컨텍스트라고 한다(종종 단순히 컨텍스트라고도 한다).

컨텍스트는 불러오거나 생성된 모든 객체의 상태를 유지한다. 컨텍스트는 객체가 마지막으로 저장되

거나 불려온 순간부터 현재까지의 변경 사항을 추적한다. 예를 들어 객체를 불러오거나 검색하면 컨

텍스트와 대조하는 과정을 거친다. 수정된 부분을 영구 저장소에 커밋(commit)하면 컨텍스트를 저장

하게 된다. 관리 객체의 수정된 사항을 되돌리고(undo) 싶다면 관리 객체 컨텍스트에게 되돌려 줄 것

을 요청하면 된다(관리 객체 컨텍스트는 데이터 모델에서 되돌리기(undo)와 다시실행(redo)을 구현하

기 위해 필요한 모든 작업을 다루고 있다).

아이폰 애플리케이션을 만들 때 거의 대부분 하나의 컨텍스트만 사용할 것이다. 하지만 애플리케

이션에서는 하나 이상의 컨텍스트를 가질 수 있다. 예를 들어 애플리케이션에서 스레딩을 지원하거나

NSOperationQueue와 같은 동시성 작업을 처리할 때 컨텍스트는 다중 스레드 환경에서 안전하지 않

고 스레드 사이에서 공유가 되지 않기 때문에 하나 이상의 컨텍스트가 필요하다. 이는 조심하지 않으

면 동일한 관리 객체가 각각 다른 값을 갖고 다른 장소에 존재할 수 있음을 의미한다.

모든 애플리케이션은 애플리케이션이 동작하기 위해 적어도 하나의 객체 컨텍스트를 필요로 하므

로 템플릿은 친절하게도 이를 제공해 준다. CoreDataAppDelegate.m을 다시 클릭하고 function 메뉴

에서 -managedObjectContext를 선택하자. 아래와 같은 메서드를 볼 수 있을 것이다.

/**

Returns the managed object context for the application.

If the context doesn't already exist, it is created and bound to the persistent store

coordinator for the application.

*/

- (NSManagedObjectContext *) managedObjectContext {

if (managedObjectContext != nil) {

return managedObjectContext;

}

Page 50: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

28 l More! 아이폰 3 프로그래밍

NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];

if (coordinator != nil) {

managedObjectContext = [[NSManagedObjectContext alloc] init];

[managedObjectContext setPersistentStoreCoordinator: coordinator];

}

return managedObjectContext;

}

이 메서드는 상당히 직관적이다. managedObjectContext가 nil인지 검사하는 지연 로딩을 사

용한다. 만약 nil이 아니면 그 값을 그대로 반환한다. 만약 managedObjectContext가 nil이면

NSPersistentStoreCoordinator가 있는지 검사한다. NSPersistentStoreCoordinator가 있으면 새로

운 managedObjectContext를 생성하고, 이어서 setPersistentStoreCoordinator:를 이용해서 현재의

coordinator를 managedObjectContext로 묶는다. 메서드가 끝나면 managedObjectContext가 반

환된다.

관리 객체 컨텍스트는 영구 저장소를 직접적으로 이용하지 않고 영구 저장소 코디네이터를 거친다.

그 결과 모든 관리 객체 컨테스트에는 기능 수행을 위해 영구 저장소 코디네이터를 가리키는 포인터가

필요하다. 하지만 많은 관리 객체 컨텍스트들은 같은 영구 저장소 코디네이터와 동작할 수 있다.

종료 시 저장

애플리케이션 델리게이트에서 스크롤을 올려 변경 사항이 발생하면 이를 컨텍스트에 저장하는

applicationWillTerminate: 메서드를 보자. 변경 사항은 영구 저장소에 저장된다. 이름에서 알 수 있

듯이 이 메서드는 애플리케이션이 종료되기 직전에 호출된다.

/**

applicationWillTerminate: saves changes in the application's managed object context

before the application terminates.

*/

- (void)applicationWillTerminate:(UIApplication *)application {

NSError *error;

if (managedObjectContext != nil) {

if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {

// Handle error.

NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

exit(-1); // Fail

}

}

}

Page 51: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 29

이 코드는 꽤 괜찮은 기능을 갖고 있다. 하지만 데이터를 저장하고 싶지 않은 순간도 있을 것이다.

예를 들어 사용자가 새로운 엔터티를 생성한 후 애플리케이션을 종료하려 할 때 엔터티에 데이터가

하나도 들어 있지 않다면? 이런 경우 정말로 빈 관리 객체를 영구 저장소에 저장하고 싶은가? 아마 그

렇지 않을 것이다. 뒤에 나오는 장에서 이와 같은 상황을 다루는 방법에 대해 알아 볼 것이다.

영구 저장소에서 데이터 불러오기

앞에서 만든 코어 데이터 애플리케이션을 실행하고 더하기 버튼을 몇 차례 눌러보자(그림 2-7). 시뮬

레이터를 종료하고 애플리케이션을 다시 실행해 보자. 이전에 실행했을 때 생성된 타임 스탬프가 영구

저장소에 저장되고 다시 실행했을 때 해당 타임스탬프를 불러온 것을 확인할 수 있다.

RootViewController.m을 클릭하면 어떻게 이런 동작이 실행됐는지 확인할 수 있다. 파일 이름에

서 추측할 수 있듯이 RootViewController는 애플리케이션의 루트 뷰 컨트롤러로 동작하는 뷰 컨트

롤러 클래스이다. 이 클래스는 그림 2-7에서 볼 수 있는 템플릿 애플리케이션의 유일한 뷰에 대한 뷰

컨트롤러이다.

파일 이름을 클릭하면 function 메뉴에서 viewDidLoad: 메서드를 찾을 수 있다(클래스의 첫 번째

메서드여서 이미 화면에 보이겠지만). 기본적인 메서드 구현은 아래와 같다.

- (void)viewDidLoad {

[super viewDidLoad];

// Set up the edit and add buttons.

self.navigationItem.leftBarButtonItem = self.editButtonItem;

UIBarButtonItem *addButton = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self

action:@selector(insertNewObject)];

self.navigationItem.rightBarButtonItem = addButton;

[addButton release];

NSError *error = nil;

if (![[self fetchedResultsController] performFetch:&error]) {

//Update to handle the error appropriately.

NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

exit(-1); //Fail

}

}

Page 52: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

30 l More! 아이폰 3 프로그래밍

그림 2-7 | 템플릿 애플리케이션은 종료할 때 데이터를 저장한다. 애플리케이션을

다시 실행하게 되면 영구 저장소에 존재하는 데이터를 불러온다.

이 메서드에서 가장 먼저 하는 일은 super를 호출하는 것이다. 그 다음 Edit와 Add 버튼을 설정

한다. RootViewController가 UIViewController를 상속받는 것에 주목하자. UIViewController

는 Edit button을 반환하는 editButtonItem이라는 프로퍼티를 제공한다. 점 표기법을 사용해서

editButtonItem을 찾고 le�BarButtonItem의 변경자(mutator) 4 로 넘긴다. 이제 Edit 버튼은 내비게이

션 바의 왼쪽 버튼이 되었다.

Add 버튼을 보자. UIViewController가 Add 버튼을 제공하지 않기 때문에 alloc을 사용하여 새로

하나를 생성하고 내비게이션 바의 오른쪽 버튼으로 추가한다.

아래 코드는 조금 낯설 것이다.

NSError *error = nil;

if (![[self fetchedResultsController] performFetch:&error]) {

이 코드는 fetchedResultsController 메서드에 의해 반환된 오브젝트의 performFetch:를 호출

한다. fetchedResultsController 메서드는 SDK 3.0에 새로 포함된 컨트롤러 클래스인 NSFetched

ResultsController의 인스턴스를 반환한다. 페치 결과 컨트롤러가 어떻게 동작하는지 알아보자.

4   (옮긴이) 변경자는 변수를 설정해준다. 흔히 변경자 메서드를 세터(setter) 메서드라 한다.

Page 53: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 31

페치 결과 컨트롤러

개념적인 측면에서 페치 결과 컨트롤러는 아이폰 SDK에서 볼 수 있는 일반적인 다른 컨트롤러들과 다

르다. 코코아 바인딩을 써봤거나 NSArrayController와 같은 맥에서 쓸 수 있는 일반적인 클래스들을

사용해 보았다면 기본 개념이 친숙할 것이다. 일반적인 컨트롤러 클래스들이 친숙하지 않다면, 뒤에

나오는 간단한 설명들을 보도록 하자.

UINavigationController, UITableViewController, UIViewController와 같은 아이폰 SDK의 일

반적인 클래스들의 대부분은 특정 타입의 뷰에 대한 컨트롤러로서 동작하게 설계되었다. 하지만 뷰

컨트롤러는 자주 쓰이긴 하지만 코코아 터치가 제공하는 유일한 컨트롤러 클래스 타입은 아니다. 대

표적으로 NSFetchedResultsController는 뷰 컨트롤러가 아닌 컨트롤러 클래스이다.

NSFetchedResultsController는 코코아 데이터의 페치 리퀘스트에서 반환한 객체를 관리하도록 고

안된 것이다. NSFetchedResultsController는 작업 묶음을 다루기 때문에 다른 컨트롤러보다 코어 데

이터의 데이터를 표시하기 쉽게 만들어준다. 예를 들어, 메모리 부족에 대한 경고를 받으면 불필요한

객체를 메모리에서 제거하고 나중에 그것들이 필요할 때 다시 불러올 것이다. 페치 결과 컨트롤러에

대해 델리게이트(delegate)가 선언돼 있다면 델리게이트는 갖고 있는 데이터에 변화가 일어났을 때 이

를 알아차릴 것이다.

페치 결과 컨트롤러 만들기

여기서는 페치 리퀘스트를 만드는 것을 시작으로 해당 페치 리퀘스트를 이용해서 페치 결과 컨트롤러

를 생성할 것이다. 템플릿에서는 RootViewController.m 파일의 fetchedResultsController 메서드에

서 페치 결과 컨트롤러가 생성된다. fetchedResultsController는 새로운 페치 리퀘스트를 생성하는 것

으로 시작한다. 페치 리퀘스트는 기본적으로 로드될 데이터의 세부사항을 나열한 것이다. 페치 리퀘

스트를 사용할 때 페치 리퀘스트에게 불러올 엔터티가 어느 것인지를 알려줘야 한다. 또한 페치 리퀘

스트에 데이터가 정리될 때의 순서를 결정하는 정렬 기술자(sort descriptor)를 붙이고 싶을 때가 있을

것이다.

페치 리퀘스트가 완료되면 페치 결과 컨트롤러가 생성된다. 페치 결과 컨트롤러는 NSFetched

ResultsController 클래스의 인스턴스다. 페치 결과 컨트롤러는 데이터를 가능한 최신 상태로 유지하

기 위해 페치 리퀘스트를 사용한다는 것을 잊지 말자.

페치 결과 컨트롤러가 생성되고 나면 최초 페치를 수행한다. 이것은 PerformFetch 메시지를 페치 결

과 컨트롤러에 보냄으로써 RootViewController.m의 끝에 있는 viewDidLoad 메서드에서 수행된다.

Page 54: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

32 l More! 아이폰 3 프로그래밍

이제 테이블 뷰를 위한 데이터 소스와 델리게이트가 준비되었다. 테이블 뷰가 테이블 섹션의 개

수를 알려고 할 때에는 numberOfSectionsInTableView: 메서드를 호출할 것이다. 현재 프로젝트

에서는 적절한 메시지를 fetchedResultsController에게 제공함으로써 섹션 정보를 얻는다. 아래

RootViewController.m의 코드를 보자.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {

return [[fetchedResultsController sections] count];

}

tableView:numberOfRowsInSection:에서 이것과 같은 방식을 적용하고 있다.

- (NSInteger)tableView:(UITableView *)tableView

numberOfRowsInSection:(NSInteger)section {

id <NSFetchedResultsSectionInfo> sectionInfo =

[[fetchedResultsController sections] objectAtIndex:section];

return [sectionInfo numberOfObjects];

}

뭔가 보이는가? 여러분은 이 일들을 직접 했어야 했다. 하지만 페치 결과 컨트롤러가 데이터를 관리

하는 모든 작업을 대신 해준다. 이로 인해 많은 개발 시간을 절약할 수 있을 것이다.

페치 결과 컨트롤러의 생성 과정을 자세히 보자. RootViewController.m의 function 메뉴를 이용

해 fetchedResultsController 메서드로 이동하자. 아래와 같은 코드를 볼 수 있을 것이다.

- (NSFetchedResultsController *)fetchedResultsController {

if (fetchedResultsController != nil) {

return fetchedResultsController;

}

/*

Set up the fetched results controller.

*/

// Create the fetch request for the entity.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

// Edit the entity name as appropriate.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event"

inManagedObjectContext:managedObjectContext];

[fetchRequest setEntity:entity];

// Set the batch size to a suitable number.

[fetchRequest setFetchBatchSize:20];

Page 55: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 33

// Edit the sort key as appropriate.

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]

initWithKey:@"timeStamp" ascending:NO];

NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

// Edit the section name key path and cache name if appropriate.

// nil for section name key path means "no sections".

NSFetchedResultsController *aFetchedResultsController =

[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest

managedObjectContext:managedObjectContext sectionNameKeyPath:nil

cacheName:@"Root"];

aFetchedResultsController.delegate = self;

self.fetchedResultsController = aFetchedResultsController;

[aFetchedResultsController release];

[fetchRequest release];

[sortDescriptor release];

[sortDescriptors release];

return fetchedResultsController;

}

이 메서드는 지연 로딩 방식을 이용한다. 이 메서드에서 맨 처음 하는 일은 fetchedResultsController

가 nil인지 확인하는 것이다. fetchedResultsController가 이미 존재하면 존재하는 fetchedResults

Controller가 그대로 반환된다. 그렇지 않은 경우에는 새로운 fetchedResultsController를 생성한다.

fetchedResultsController를 생성하는 첫 단계로 NSFetchRequest와 NSEntityDescription을 생성

한 후 NSEntityDescription을 NSFetchRequest에 첨부해 줘야 한다.

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

// Edit the entity name as appropriate.

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Event"

inManagedObjectContext:managedObjectContext];

[fetchRequest setEntity:entity];

현재 페치 결과 컨트롤러를 생성하고 있으며 페치 리퀘스트는 이것의 일부라는 점을 기억하자. 다음

으로 일괄 처리(batch)의 크기를 20으로 설정한다. 이것은 코어 데이터에게 페치 리퀘스트가 한 번에

20개의 결과를 검색하리라는 것을 알려준다. 파일 시스템의 블럭 크기라고 생각하면 된다.

Page 56: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

34 l More! 아이폰 3 프로그래밍

// Set the batch size to a suitable number.

[fetchRequest setFetchBatchSize:20];

다음으로 NSSortDescriptor를 생성하고 timeStamp를 키로 사용하도록 설정한다. 타임 스탬프는

오름차순으로 정렬된다(앞선 날짜가 먼저 나온다).

// Edit the sort key as appropriate.

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]

initWithKey:@"timeStamp" ascending:YES];

정렬 기술자의 배열이 만들어졌다. 여기서는 정렬 기술자를 하나만 사용할 것이므로 sortDescriptor

와 nil만 initWithObjects에 넘겨 배열에 원소가 하나만 들어있다고 알려준다(템플릿에서 initWith

Object를 사용했을 수도 있다).

NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

ascending:YES를 ascending:NO로 바꾸고 애플리케이션을 다시 실행해보자. 어떤 결과가 나올까?

실행해본 후 설정을 원래대로 돌려놓는 것을 잊지 말자.

페치 리퀘스트를 영구 저장소에 저장된 관리 객체의 부분집합으로 한정하려면 술어(predicate)를 사용하면

된다. 마크 데일림플(Mark Dalrymple)과 스콧 네스터(Scott Knaster)가 저술한 ‘Learn Objective-C on

the Mac(Apress, 2008)’에서는 한 장에 걸쳐 술어를 설명하고 있다. 기본 템플릿에서는 술어를 사용하지

않지만 뒤에 나오는 장에서 술어에 대해 다룰 것이다.

페치 리퀘스트와 컨텍스트를 사용해서 NSFetchedResultsController를 생성했다. 세 번째와 네 번

째 매개변수인 sectionNameKeyPath와 cacheName에 대해서는 3장에서 다루겠다.

// Edit the section name key path and cache name if appropriate.

// nil for section name key path means "no sections".

NSFetchedResultsController *aFetchedResultsController =

[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest

managedObjectContext:managedObjectContext sectionNameKeyPath:nil

cacheName:@"Root"];

다음으로 self를 델리게이트로 설정해 주고 fetchedResultsController를 방금 생성한 페치 결과 컨

트롤러로 설정해준다.

Page 57: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 35

aFetchedResultsController.delegate = self;

self.fetchedResultsController = aFetchedResultsController;

최종적으로 지역변수를 릴리즈하고 새로 생성된 fetchedResultsController를 반환해준다.

[aFetchedResultsController release];

[fetchRequest release];

[sortDescriptor release];

[sortDescriptors release];

return fetchedResultsController;

}

여기서 자세한 부분에 대해서는 너무 걱정하지 말자. 여기서는 일단 전체적인 큰 그림만 그리자. 이

후 장에서 세부적인 내용에 초점을 맞추겠다.

페치 결과 컨트롤러 델리게이트 메서드

페치 결과 컨트롤러는 반드시 델리게이트를 가져야 하며, 이 델리게이트는 반드시 뒷부분에서 설명하

는 4개의 메서드를 제공해야 한다. 이 4개의 메서드는 NSFetchedResultsControllerDelegate 프로토

콜에 선언돼 있다. 페치 결과 컨트롤러는 관리 객체 컨텍스트를 모니터하고 컨텍스트에서 변경 사항

이 있으면 델리게이트를 호출한다.

콘텐트를 변경할 델리게이트 메서드

페치 결과 컨트롤러에서 관리하는 객체가 삭제 또는 변경되거나 페치 결과 컨트롤러의 페치 리퀘스

트의 조건을 충족하는 새로운 객체가 삽입되는 등의 변경 사항을 감지하면 페치 결과 컨트롤러는

controllerWillChangeContent: 메서드를 사용하여 변경 사항을 적용하기 전에 델리게이트에게 알려줄

것이다.

대다수의 경우 페치 결과 컨트롤러는 테이블 뷰와 같이 쓰일 것이고 델리게이트 메서드에서는 테

이블 뷰에 지속적으로 업데이트를 하라고 알려주기만 하면 된다. 아래 코드는 이에 대한 방법을 보

여준다.

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller{

[self.tableView beginUpdates];

}

이처럼 Xcode 프로젝트 템플릿은 대부분의 페치 결과 컨트롤러 델리게이트의 작업을 다루

Page 58: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

36 l More! 아이폰 3 프로그래밍

지 않는다. 그러므로 개발자들은 이 메서드를 컨트롤러 클래스들에 추가해 줘야 한다. 만약 이미

controllerWillChangeContent: 메서드가 존재한다면 위의 메서드로 교체해 준다.

참고

NSFetchedResultsController는 SDK 3.0에 새로 추가된 객체다. 이것을 사용하는 코어 데이터

Navigation-based Application 프로젝트 템플릿은 초기 릴리즈에서 조금씩 바뀌어 왔다. 최초 버전

에서는 페치 결과 컨트롤러 델리게이트 메서드가 하나도 구현되지 않았다. 이후 버전에서는 controller

WillChangeContent: 메서드가 구현되었으나, 테이블이 리로드될 때만 호출되었다. 이 절에서 구현한 코드

는 일반화돼 있고 강건한 코드다. 따라서 이 코드를 그대로 템플릿에 복사해 넣어도 된다.

콘텐트 변경 후 호출되는 델리게이트 메서드

페치 결과 컨트롤러가 변경을 가하고 나면 controllerDidChangeContent: 메서드를 이용하여 자

신의 델리게이트에게 이러한 사실을 알려줄 것이다. 테이블 뷰를 쓴다면 테이블 뷰에 controller

WillChangeContent:가 이제 완료됐으니 업데이트하라고 알려줘야 할 것이다. 아래와 같은 코드로 이

러한 내용을 표현할 수 있다.

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller{

[self.tableView endUpdates];

}

객체 변경 후 호출되는 델리게이트 메서드

페치 결과 컨트롤러가 특정 객체의 변경을 인식하면 controller:didChangeObject:forChangeType:n

ewIndexPath: 메서드를 사용하여 델리게이트에게 알려준다. 이 메서드는 페치 결과 컨트롤러에 의해

관리되는 객체의 변경 사항을 반영하기 위해 업데이트, 삽입, 삭제, 테이블 뷰에서의 행 이동이 수행돼

야 하는 곳에 위치한다. 아래 코드는 테이블 뷰를 업데이트하는 델리게이트 메서드의 코드다.

- (void)controller:(NSFetchedResultsController *)controller

didChangeObject:(id)anObject

atIndexPath:(NSIndexPath *)indexPath

forChangeType:(NSFetchedResultsChangeType)type

newIndexPath:(NSIndexPath *)newIndexPath {

switch(type) {

case NSFetchedResultsChangeInsert:

[self.tableView insertRowsAtIndexPaths:[NSArray

arrayWithObject:newIndexPath]

withRowAnimation:UITableViewRowAnimationFade];

Page 59: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 37

break;

case NSFetchedResultsChangeDelete:

[self.tableView deleteRowsAtIndexPaths:[NSArray

arrayWithObject:indexPath]

withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeUpdate: {

NSString *sectionKeyPath = [controller sectionNameKeyPath];

if (sectionKeyPath == nil)

break;

NSManagedObject *changedObject = [controller

objectAtIndexPath:indexPath];

NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];

id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];

for (int i = 0; i < [keyParts count] - 1; i++) {

NSString *onePart = [keyParts objectAtIndex:i];

changedObject = [changedObject valueForKey:onePart];

}

sectionKeyPath = [keyParts lastObject];

NSDictionary *committedValues = [changedObject

committedValuesForKeys:nil];

if ([[committedValues valueForKeyPath:sectionKeyPath]

isEqual:currentKeyValue])

break;

NSUInteger tableSectionCount = [self.tableView numberOfSections];

NSUInteger frcSectionCount = [[controller sections] count];

if (tableSectionCount != frcSectionCount) {

// 섹션을 삽입해야 한다.

NSArray *sections = controller.sections;

NSInteger newSectionLocation = -1;

for (id oneSection in sections) {

NSString *sectionName = [oneSection name];

if ([currentKeyValue isEqual:sectionName]){

newSectionLocation = [sections indexOfObject:oneSection];

break;

}

}

if (newSectionLocation == -1)

return; // 오, 뭔가 문제가 있다.

if (!((newSectionLocation == 0) && (tableSectionCount == 1)

&& ([self.tableView numberOfRowsInSection:0] == 0)))

Page 60: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

38 l More! 아이폰 3 프로그래밍

[self.tableView insertSections:[NSIndexSet

indexSetWithIndex:newSectionLocation]

withRowAnimation:UITableViewRowAnimationFade];

NSUInteger indices[2] = {newSectionLocation, 0};

newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indices

length:2] autorelease];

}

}

case NSFetchedResultsChangeMove:

if (newIndexPath != nil) {

[self.tableView deleteRowsAtIndexPaths:[NSArray

arrayWithObject:indexPath]

withRowAnimation:UITableViewRowAnimationFade];

[self.tableView insertRowsAtIndexPaths: [NSArray

arrayWithObject:newIndexPath]

withRowAnimation: UITableViewRowAnimationRight];

}

else {

[self.tableView reloadSections:[NSIndexSet

indexSetWithIndex:[indexPath section]]

withRowAnimation:UITableViewRowAnimationFade];

}

break;

default:

break;

}

}

위 코드는 대부분 상당히 직관적이다. 행이 삽입되면 메서드에서는 forChangeType의 type 매

개변수로 NSFetchedResultsChangeInsert 타입을 받고 새로운 행을 테이블에 추가한다. 행이 삭

제되면 NSFetchedResultsChangeDelete 타입을 받고 테이블에서 해당하는 행을 삭제한다. type

이 NSFetchedResultsChangeMove라면 행이 움직였다는 것을 알 수 있다. 즉, 예전 위치를 지우고

newIndexPath에서 정의한 위치를 새로운 위치로 입력해야 한다.

이 메서드에서 덜 직관적인 부분은 페치 결과 컨트롤러 객체 중 하나가 변경 사항을 관리할 때 발생

하는 NSFetchedResultsChangeUpdate이다. 대부분 이 동작이 발생했을 때 여러분이 할 일은 없을

것이다.

문제는 업데이트가 테이블 뷰에 영향을 끼칠 수 있다는 것이다. 만약 행의 순서를 정하는 필드를 바

꾸거나 행을 섹션으로 나눈다면 이를 처리해줘야 할 것이다. 예를 들어 새로운 섹션으로 객체가 옮겨

Page 61: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 39

지는 현상을 야기시킬 수 있는 객체의 변경이 발생해도, 이 객체가 새로운 섹션으로 이동하거나 삽입

되는 것에 대해서는 어떤 통보도 받지 못할 것이다. 하지만 NSFetchedResultsChangeUpdate에 있는

근사한 코드가 이 문제를 해결해 준다. 이 코드를 전부 이해하지 못했다고 해서 걱정할 필요는 없다.

이 코드는 일반적으로 쓰이므로 그대로 사용하면 된다.

페치 결과 컨트롤러 객체 업데이트 다루기

여러분은 객체를 업데이트하기 위한 세부적인 로직을 알 필요가 없다. 아래 코드를 별도로 이해

할 필요 없이 그냥 사용하기만 하면 된다. 그래도 세부적인 로직을 알고 싶다면 델리게이트 메서

드에서 객체의 변화를 다루는 과정을 살펴보겠다. 하지만 세부 로직을 살펴보기 전에 코어 데이터

의 다른 부분을 보기를 권장한다.

맨 처음 해야 할 일은 페치 결과 컨트롤러가 사용하고 있는 sectionKeyPath를 찾는 것이다. 이

값은 테이블의 데이터를 섹션으로 나눠주는 키 값이다. 이 값이 nil이면 아무 동작도 하지 않고

switch 문을 빠져 나온다. 반대의 경우에는 이 메서드를 호출한 객체의 레퍼런스를 가져온다.

case NSFetchedResultsChangeUpdate: {

NSString *sectionKeyPath = [controller sectionNameKeyPath];

if (sectionKeyPath == nil)

break;

NSManagedObject *changedObject = [controller

objectAtIndexPath:indexPath];

다음으로 섹션 이름의 키패스를 컴포넌트로 분리한다. 대부분 섹션 이름의 키패스를 @“foo”처럼

하나의 프로퍼티 이름으로 하지만 @“foo.bar”처럼 점으로 구분해서 여러 개의 컴포넌트를 포함

할 수도 있다. 7장에서는 코어 데이터의 관계에 대해 설명할 때 키패스가 왜 여러 개의 컴포넌트를

가질 수 있는지 자세히 알아보겠다.

NSArray *keyParts = [sectionKeyPath

componentsSeparatedByString:@”.”];

키패스에 의해 호출되는 객체를 찾기 위해 키패스를 통해 루프를 반복할 필요가 있다. 보통 이 객

체는 바뀐 객체일 것이다. 하지만 키패스가 점으로 분리된 여러 개의 값을 갖고 있다면 이것이 중

첩 객체(nested object)라는 사실을 알 수 있으며 이 객체를 검색해야 할 것이다. 여기서 키패스의

마지막 문자열은 객체가 아니라 프로퍼티를 참조하기 때문에 제외한다.

id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];

for (int i = 0; i < [keyParts count] - 1; i++) {

Page 62: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

40 l More! 아이폰 3 프로그래밍

NSString *onePart = [keyParts objectAtIndex:i];

changedObject = [changedObject valueForKey:onePart];

}

changedObject는 이제 섹션 키가 있는 객체를 참조하고 루프를 종결한다. 이로써 changedObject

는 마지막 컴포넌트만을 갖게 된다. 객체와 섹션에 행을 나누기 위해 사용되는 특정 프로퍼티에

대해 알아보았다. 다음으로 프로퍼티를 위해 이전 값을 결정하고 값이 바뀌었는지 확인해야 한다.

우리가 알고 있는 것은 단지 이 객체가 바뀌었다는 것뿐이다. 만약 값이 바뀐 적이 없으면 이 메서

드를 멈출 것이고 이 섹션의 나머지 코드를 수행할 것이다.

sectionKeyPath = [keyParts lastObject];

NSDictionary *committedValues = [changedObject

committedValuesForKeys:nil];

if ([[committedValues valueForKeyPath:sectionKeyPath]

isEqual:currentKeyValue])

break;

만약 코드가 멈추지 않고 아래 코드까지 실행된다면 섹션으로 나뉘는 객체에게 영향을 미치는

바뀐 값을 다루는 셈이다. 그렇기 때문에 페치 결과 컨트롤러와 테이블이 다르다면 섹션 수를 비

교해봐야 한다. 객체가 새로운 섹션으로 이동되는 것은 새로운 섹션이 삽입돼야 한다는 것을 나

타내는 것이 아니기 때문이다. 기존의 있는 섹션으로 이동할 수도 있는 것이다.

NSUInteger tableSectionCount = [self.tableView numberOfSections];

NSUInteger frcSectionCount = [[controller sections] count];

if (tableSectionCount != frcSectionCount) {

페치 결과 컨트롤러와 테이블이 같은 수의 섹션을 갖고 있지 않다면 페치 결과 컨트롤러의 섹

션을 다시 루프를 돌며 어디에 새로운 섹션이 삽입돼야 하는지 살펴봐야 한다. 루프를 돌며 섹

션 이름과 바뀐 객체의 섹션 키 값이 같은지 비교한다. 일치하는 섹션을 찾으면 섹션의 인덱스를

newSectionLocation에 저장하고 루프를 멈춘다.

// 섹션을 삽입해야 한다.

NSArray *sections = controller.sections;

NSInteger newSectionLocation = -1;

for (id oneSection in sections) {

NSString *sectionName = [oneSection name];

if ([currentKeyValue isEqual:sectionName]) {

newSectionLocation = [sections

indexOfObject:oneSection];

Page 63: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 41

break;

}

}

다음으로 newSectionLocation이 -1이 아닌지 확인한다. 이론상으로는 이런 일이 발생해선 안 되

지만, 새로운 섹션을 삽입할 때 런타임 예외를 발생시킬 수 있기 때문에 확인할 필요가 있다.

if(newSectionLocation == -1)

return;

최종적으로 필요하다면 새로운 섹션을 삽입한다. 테이블 뷰는 최소 한 개 이상의 섹션을 갖고 있

다는 것을 기억하자. 빈 테이블은 이미 한 개의 섹션을 갖고 있기 때문에 굳이 섹션을 추가할 필

요는 없다. 객체 변경 알림을 받았을 때 객체가 이미 존재하기 때문에 직접 첫 번째 행을 삽입할

일은 거의 없을 것이다. 하지만 이 객체는 페치 결과 컨트롤러의 페치 리퀘스트에 명시되어 있지

않았을 것이다. 변경된 내용은 페치 결과 컨트롤러의 결과집합(result set)의 첫 번째 객체로 들어

가야 한다.

if(!((newSectionLocation == 0) && (tableSectionCount == 1)

&& ([self.tableView numberOfRowsInSection:0] == 0)))

[self.tableView insertSections:[NSIndexSet

indexSetWithIndex:newSectionLocation]

withRowAnimation:UITableViewRowAnimationFade];

새로운 NSIndexPath를 생성하고 이것을 newIndexPath에 할당했다. newIndexPath 변수는 객

체가 이동한 사실을 인지했을 때 행이 어디로 이동해야 하는지를 가리켜야 한다. 변경 사항을 업

데이트하기 위해 이 값은 항상 nil일 것이다. 새로 생성된 섹션에서 첫 번째 행을 가리키는 인덱스

패스를 생성한다. 그곳이 바로 행을 삽입하고 싶은 위치이기 때문이다.

NSUInteger indices[2] = {newSectionLocation, 0};

newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indices

length:2] autorelease];

}

}

다음 case 문 전에 break 문을 쓰지 않은 것을 기억하자. 다음 case 문은 새로 추가된 섹션의 기존

에 있던 행을 제거하고 새로운 행을 추가한다.

case NSFetchedResultsChangeMove:

...

Page 64: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

42 l More! 아이폰 3 프로그래밍

어렵게 보여도 좌절하지 말자. 코딩할 때 이런 코드를 작성할 일은 거의 없다. 앞에서 말했듯이 이

것이 어떻게 또는 왜 쓰이는지에 대해서는 고민할 필요가 없다.

섹션 변경 후 호출되는 델리게이트 메서드

마지막으로 객체의 변경이 테이블의 섹션의 숫자에 영향을 끼칠 때 페치 결과 컨트롤러는 controller:

didChangeSection:atIndex:forChangeType: 메서드를 호출한다. 페치 결과 컨트롤러를 생성할 때

sectionNameKeyPath를 설정했다면 테이블에서 섹션의 추가와 삭제를 관리하기 위해 이 델리게이트

메서드를 구현해야 한다. 이 메서드를 구현하지 않으면 테이블과 페치 결과 컨트롤러의 섹션의 수가 일

치하지 않을 때 런타임 에러가 발생할 것이다. 아래 코드는 거의 모든 상황에 적용되는 사실상 표준처

럼 쓰이는 델리게이트 메서드의 구현 코드다.

-(void)controller:(NSFetchedResultsController *)controller

didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo

atIndex:(NSUInteger)sectionIndex

forChangeType:(NSFetchedResultsChangeType)type{

switch(type){

case NSFetchedResultsChangeInsert:

if(!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)

&& ([self.tableView numberOfRowsInSection:0] == 0)))

[self.tableView insertSections:[NSIndexSet

indexSetWithIndex:sectionIndex]

withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeDelegate:

if(!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1)

&&([self.tableView numberOfRowsInSection:0] == 0)))

[self.tableView deleteSections:[NSIndexSet

indexSetWithIndex:sectionIndex]

withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeMove:

case NSFetchedResultsChangeUpdate:

default:

break;

}

}

Page 65: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 43

일단 이 네 개의 델리게이트 메서드를 구현하고 나서 새로운 관리 객체를 추가하게 되면 페치 결과

컨트롤러는 이를 감지할 것이고 테이블이 자동으로 업데이트될 것이다. 객체를 삭제하거나 변경할 때

도 컨트롤러는 이를 감지할 것이다. 페치 결과 컨트롤러에 영향을 주는 변경 사항은 자동으로 테이블

뷰에 적당한 애니메이션 효과와 함께 적절한 업데이트를 수행할 것이다. 결국 데이터 집합(data set)에

영향을 주는 변경 사항을 만들 때마다 reloadData를 호출하지 않아도 된다는 것이다.

페치 결과 컨트롤러로부터 관리 객체 검색

전에 메서드에서 일일이 처리하던 것을 페치 결과 컨트롤러가 대신해 줌에 따라 테이블 뷰 델리게이

트 메서드는 더 짧아지고 직관적으로 바뀌었다. 예를 들어 특정 셀에 대응하는 객체를 검색하려면

tableView:cellForRowAtIndexPath:와 tableView:didSelectRowAtIndexPath:를 사용해야 했지만,

페치 결과 컨트롤러의 objectAtIndexPath:를 호출하면서 indexPath 매개변수를 넘기면 앞의 방법과

똑같은 결과를 얻을 수 있다.

NSManagedObject *managedObject = [fetchedResultsController

objectAtIndexPath:indexPath];

새로운 관리 객체의 생성과 삽입

에디터의 function 메뉴에서 샘플 애플리케이션에서 더하기 버튼을 클릭했을 때 호출되는 메서드인

insertNewObject를 선택하자. 이 메서드는 어떻게 새로운 관리 객체가 생성되는지를 볼 수 있는 간단

하면서도 좋은 예제다. 이것을 관리 객체 컨텍스트에 추가하고 영구 저장소에 저장한다.

- (void)insertNewObject {

// Create a new instance of the entity managed by the fetched results

// controller.

NSManagedObjectContext *context =

[fetchedResultsController managedObjectContext];

NSEntityDescription *entity = [[fetchedResultsController fetchRequest] entity];

NSManagedObject *newManagedObject = [NSEntityDescription

insertNewObjectForEntityForName:[entity name]

inManagedObjectContext:context];

// If appropriate, configure the new managed object.

[newManagedObject setValue:[NSDate date] forKey:@"timeStamp"];

// Save the context.

Page 66: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

44 l More! 아이폰 3 프로그래밍

NSError *error;

if (![context save:&error]) {

// Handle the error...

}

}

이 코드가 맨 처음 하는 일이 페치 결과 컨트롤러에서 관리 객체 컨텍스트를 가져온다는 것이라는

데 주목하자. 이 간단한 예제에서 애플리케이션의 델리게이트로부터 같은 컨텍스트를 검색할 수 있었

다. 기본 코드가 페치 결과 컨트롤러의 컨텍스트를 사용하는 이유로는 몇 가지가 있다. 우선 페치 결

과 컨트롤러를 가리키는 인스턴스 변수가 있기 때문에 단 한 줄의 코드로 컨텍스트를 얻을 수 있다.

NSManagedObjectContext *context =

[fetchedResultsController managedObjectContext];

하지만 더 중요한 것은 페치 결과 컨트롤러가 어느 컨텍스트의 관리 객체가 어디에 속하는지 알고

있기 때문에 여러 개의 컨텍스트로 애플리케이션을 만든다면 페치 결과 컨트롤러에서 컨텍스트를 가

져오더라도 그것이 올바른 컨텍스트인지 확인해야 한다는 것이다.

새로운 객체를 삽입할 때 페치 리퀘스트를 생성할 때와 마찬가지로 코어 데이터에게 어떤 종류의

엔터티를 생성하고 싶은지 알려주기 위해 엔터티 설명(Entity description)을 생성해야 한다. 페치 결과

컨트롤러는 어느 객체가 엔터티를 관리하는지 알고 있다. 즉 아래와 같은 코드로 엔터티에 대한 정보

를 알 수 있다.

NSEntityDescription *entity = [[fetchedResultsController fetchRequest]entity];

이제 새로운 객체를 생성하고 컨텍스트에 삽입하려면 NSEntityDescription 클래스의 메서드를 쓰

면 된다.

NSManagedObject *newManagedObject = [NSEntityDescription

insertNewObjectForEntityForName:[entity name]

inManagedObjectContext:context];

새로운 객체를 삽입하고자 하는 컨텍스트의 인스턴스 메서드를 사용하지 않고 NSEntity

Description 클래스의 메서드를 사용하는 것이 이상하게 보일 수 있다. 하지만 원래 이렇게 하는 것이

맞다.

관리 객체는 이제 컨텍스트에 삽입됐지만 여전히 영구 저장소에 존재한다. 영구 저장소로부터 관리

객체를 삽입하기 위해서는 반드시 컨텍스트를 저장해야 한다.

Page 67: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 45

NSError *error;

if(![context save:&error]){

// Handle the error...

}

테이블 뷰에서 reloadData를 호출하지 않은 것에 주목하자. 페치 결과 컨트롤러는 기준에 만족하

는 새로운 객체가 삽입된 것을 알게 될 것이고 테이블을 자동으로 다시 로드하는 델리게이트 메서드

를 호출할 것이다.

NSManagedObjectContext를 이용하여 새로운 엔터티 삽입하기

NSManagedObjectContext에 새로운 객체를 삽입하기 위해 NSManagedObjectContext

의 인스턴스 메서드를 사용하는 것보다 NSEntityDescription의 메서드를 사용하는 게 어렵

다면 카테고리를 사용해서 NSManagedObject에 인스턴스 메서드를 추가할 수 있다. 이렇게

하려면 NSManagedObject-Insert.h와 NSManagedObject-Insert.m 파일을 추가해야 한다.

NSManagedObject-Insert.h에는 아래 코드를 넣어준다.

#import <Cocoa/Cocoa.h>

@interface NSManagedObjectContext(insert)

-(NSManagedObject *) insertNewEntityWithName:(NSString *)name;

@end

NSManagedObject-Insert.m는 아래와 같이 작성한다.

#import "NSManagedObjectContext-insert.h"

@implementation NSManagedObjectContext(insert)

-(NSManagedObject *) insertNewEntityWithName:(NSString *)name

{

return [NSEntityDescription insertNewObjectForEntityForName:name

inManagedObjectContext:self];

}

@end

두 파일을 모두 저장한다.

이 두 개의 파일을 Xcode 프로젝트에 추가하고 새로운 메서드를 쓰고 싶은 곳에 import

NSManagedObject-Insert.m를 추가할 수 있다. 그러면 아래와 같은 NSEntityDescription를 호

출하는 코드를 바꿀 수 있다.

Page 68: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

46 l More! 아이폰 3 프로그래밍

NSManagedObject *newManagedObject = [NSEntityDescription

insertNewObjectForEntityForName:[entity name]

inManagedObjectContext:context];

좀 더 짧고 직관적으로 바꾸면 아래와 같다.

[context insertNewEntityWithName:[entity name]];

근사하지 않은가?

관리 객체 삭제

페치 결과 컨트롤러를 사용할 때 관리 객체를 삭제하는 것은 상당히 쉽다. function 메뉴를 이용해서

tableView:commitEditingStyle:forRowAtIndexPath: 메서드로 가보자. 이 메서드는 아래와 같을 것

이다.

// Override to support editing the table view.

- (void)tableView:(UITableView *)tableView

commitEditingStyle:(UITableViewCellEditingStyle)editingStyle

forRowAtIndexPath:(NSIndexPath *)indexPath {

if (editingStyle == UITableViewCellEditingStyleDelete) {

// Delete the managed object for the given index path

NSManagedObjectContext *context =

[fetchedResultsController managedObjectContext];

[context deleteObject:[fetchedResultsController

objectAtIndexPath:indexPath]];

// Save the context.

NSError *error;

if (![context save:&error]) {

// Update to handle the error appropriately.

NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

exit(-1); // Fail

}

}

}

Page 69: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

02 코어 데이터의 구조 l 47

이 메서드는 우선 사용자가 삭제 트랜잭션에 있는지 확인한다(삭제와 삽입에 같은 메서드가 쓰인다

는 사실을 기억하자).

if (editingStyle == UITableViewCellEditingStyleDelete) {

다음으로 컨텍스트를 검색한다.

NSManagedObjectContext *context =

[fetchedResultsController managedObjectContext];

그리고 컨텍스트는 해당 객체를 삭제할지 확인한다.

[context deleteObject:[fetchedResultsController

objectAtIndexPath:indexPath]];

그 다음 영구 저장소에 변경 사항을 커밋하기 위해 관리 객체 컨텍스트의 save: 메서드가 호출된다.

NSError *error;

if (![context save:&error]) {

// Update to handle the error appropriately.

NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

exit(-1); // Fail

}

모든 것이 갖추어졌다

이제 코어 데이터의 기본적인 사용법에 익숙해졌을 것이다. 이 장에서는 코어 데이터 애플리케이션의

구조와 엔터티와 프로퍼티를 사용하는 과정을 살펴봤다. 영구 저장소, 관리 객체, 관리 객체 컨텍스트

가 애플리케이션 델리게이트에 의해 어떻게 생성되는지에 대해서도 살펴봤다. 또한 관리 객체를 생성

하기 위해 프로그램에서 사용할 수 있는 엔터티를 만들기 위해 데이터 모델 에디터를 사용하는 방법,

영구 저장소로부터 데이터를 검색, 삽입, 삭제하는 방법에 대해서도 알아봤다.

이제 이론은 이걸로 충분하다. 이제 다음 장으로 넘어가 코어 데이터 애플리케이션을 만들어보자.

Page 70: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

48 l More! 아이폰 3 프로그래밍

Page 71: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

49

앞장의 내용을 별 어려움 없이 이해할 수 있었다면 이제 2장에서 살펴본 기본 템플릿을 넘어 좀 더 심

화된 내용을 공부할 준비가 됐다.

이 장에서는 슈퍼 영웅의 데이터를 기록하기 위한 애플리케이션을 만들어 보겠다. 여기서 만들어볼

애플리케이션은 Window-based Application을 기반으로 한다. 슈퍼 영웅의 엔터티를 설계하기 위해

서 데이터 모델 에디터를 사용할 것이다. 그러고 나서 UIViewController로부터 상속받아 슈퍼 영웅

들을 추가하고, 표현하고, 삭제할 수 있게 해주는 컨트롤러 클래스를 생성할 것이다. 4장에서는 이 애

플리케이션을 더 확장하고 사용자가 슈퍼 영웅 데이터를 수정할 수 있게 코드를 추가할 것이다.

그림 3-1을 보면서 이 장에서 만들 애플리케이션이 어떤 동작을 할지 생각해보자. 보기에는 템플릿

앱과 비슷해 보인다. 템플릿 앱과의 주요한 차이점은 애플리케이션의 심장부인 엔터티와 화면 하단에

추가된 탭바다. 이제 시작해 보자.

03슈퍼 스타트: 데이터 추가, 표현, 삭제

Page 72: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

50 l More! 아이폰 3 프로그래밍

그림 3-1 | 이 장이 끝났을 때 SuperDB 애플리케이션의 모습

Xcode 프로젝트 설정

Xcode를 아직 실행하지 않았다면 실행하고 N을 눌러 New Project 창을 띄운다.(그림 3-2)

그림 3-2 | 우리의 오랜 친구인 Xcode의 새 프로젝트 도우미

Page 73: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 51

지난 장에서는 Navigation-based Application 템플릿을 사용했다. 내비게이션 애플리케이션을 생

성할 때 이 템플릿은 애플리케이션에서 필요한 많은 코드를 제공하는 좋은 템플릿이다. 하지만 어

느 부분에 코드가 추가되고 수정되는지 좀 더 쉽게 설명하고, 애플리케이션이 어떻게 생성되는지 이

해하는 데 도움이 될 수 있게 『시작하세요! 아이폰 3 프로그래밍』(위키북스, 2009)에서 했던 것처럼

SuperDB 애플리케이션을 처음부터 만들어 보겠다.

Window-based Application을 선택하고 Use Core Data for storage 체크박스가 체크돼 있는지 확

인하자. 프로젝트 이름은 SuperDB로 입력하자.

프로젝트 윈도우가 나오면 Classes와 Resources 그룹을 열어 이 장에서 주로 사용할 파일을 사용하

기 쉽게 만들어두자.

애플리케이션 구조

그림 3-1에서 볼 수 있듯이 여기서는 탭바와 내비게이션 컨트롤러를 모두 사용하는 애플리케이션을

만들 것이다. 코드를 작성하기 전에 애플리케이션의 구조에 대해 생각해봐야 한다. 예를 들어 내비게

이션 컨트롤러, 탭바 컨트롤러, 또는 다른 컨트롤러 중 어느 컨트롤러가 애플리케이션의 루트 뷰 컨트

롤러가 될지 생각해야 한다.

애플리케이션 구조에 정답은 없다. 한 가지 명확한 접근법은 UITabBarController를 루트 뷰 컨트

롤러로 만들고 내비게이션 컨트롤러를 각각의 탭바로 분리하는 것이다. 각 탭이 다른 유형의 데이터를

보여주는, 전혀 다른 뷰에 대응하는 상황에서는 이 방법이 알맞다. 『시작하세요! 아이폰 3 프로그래

밍』의 7장에서 이 방법을 쓴 이유는 모든 탭이 각기 다른 뷰 컨트롤러(각기 다른 아울렛outlet과 액션

을 비롯해)에 대응했기 때문이다.

여기에서는 두 개의 탭을 구현하고(이어지는 장에서 탭을 더 추가하겠다) 각 탭은 같은 데이터를 단

지 순서만 바꿔서 보여줄 것이다. 탭 하나가 선택되면 테이블은 슈퍼 영웅의 이름순으로 정렬될 것이

다. 다른 탭이 선택되면 같은 데이터가 보이지만 테이블은 슈퍼 영웅의 비밀 신원을 기준으로 정렬될

것이다.

어느 탭을 선택하든 테이블을 탭하면 선택한 슈퍼 영웅의 정보를 수정할 수 있는 새로운 뷰(이어지

는 장에서 추가하겠다)가 열릴 것이다. 선택된 탭에 관계없이 추가 버튼을 누르면 같은 엔터티의 새로

운 인스턴스가 추가된다. 선택된 탭은 영웅의 정보를 보거나 편집하기 위해 뷰에서 뷰로 이동하는 행

위에 관여하지 않는다.

이 애플리케이션에서 탭바는 하나의 테이블에 표현되는 데이터를 보여주는 방법을 변경할 뿐이다.

Page 74: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

52 l More! 아이폰 3 프로그래밍

즉, 다른 뷰 컨트롤러로 전환할 필요가 없다. 왜 동일한 데이터 집합에 대해 여러 내비게이션 컨트롤러

인스턴스가 있어야 하는가? 테이블 컨트롤러를 하나만 쓰면서 어느 탭이 선택되었는가에 따라 데이터

를 표시하는 방법만 바꾸는 것은 어떨까? 이런 접근법을 토대로 여기서는 UITabBarController를 전

혀 사용하지 않을 것이다.

루트 뷰 컨트롤러는 내비게이션 컨트롤러가 될 것이고 탭바는 사용자의 입력을 받는 용도로만 쓰일

것이다. 각 탭에 분리된 내비게이션 컨트롤러와 테이블 뷰 컨트롤러를 생성한다면 사용자가 어느 것을

보든지 간에 최종 결과는 동일하게 보일 것이다. 이런 방식을 쓰면 더 적은 양의 메모리를 쓸 것이며 다

른 내비게이션 컨트롤러끼리의 동기화 문제를 걱정할 필요가 없다는 장점이 있다.

애플리케이션의 루트 뷰 컨트롤러는 UINavigationController의 인스턴스가 될 것이다. UINavi

gationController를 루트 뷰로 사용하기 위해 HeroListViewController라는 커스텀 뷰 컨트롤러를 생

성할 것이다. HeroListViewController는 선택된 탭에 따라 정해진 순서에 맞게 슈퍼 영웅 리스트를

화면에 표현할 것이다.

앱이 어떻게 동작하는지 보자. 애플리케이션이 시작되면 UINavigationController 인스턴스는

nib 파일로부터 생성되고 내비게이션 컨트롤러의 뷰가 애플리케이션의 윈도우에 하위 뷰(subview)

로 추가되어 화면에서 볼 수 있게 된다. 윈도우의 나머지 부분은 모두 콘텐트 페인의 하위 컨트롤

러 뷰가 차지한다. 다음으로 HeroListViewController의 인스턴스가 nib로부터 로드될 것이고 뷰

와 연관된 nib 파일은 내비게이션 컨트롤러의 콘텐트 페인에 하위 뷰로서 추가될 것이다. 이 뷰

(HeroListViewController와 연관된 뷰)는 탭바와 테이블 뷰를 가진다.

4장에서는 슈퍼 영웅 상세뷰를 구현한 테이블 뷰 컨트롤러를 추가하겠다. 사용자가 슈퍼 영웅 리스

트에서 하나를 탭하면 상세 컨트롤러가 내비게이션 스택에 푸쉬(push)되고 상세 컨트롤러의 뷰가 임

시로 UINavigationController의 콘텐트 뷰 안의 HeroListViewController의 뷰를 대신할 것이다. 지

금은 상세뷰에 대해 걱정할 필요가 없다. 앞으로 진행할 내용만 잘 따라오면 된다.

애플리케이션 델리게이트 인터페이스 수정하기

위에서 설명한 방법대로 시작하려면 우선 애플리케이션 델리게이트에 애플리케이션의 루트 뷰 컨트

롤러에 대한 아웃렛을 선언해줘야 한다. SuperDbAppDelegate.h를 클릭하고 아래에서 굵게 표시한

코드를 추가하자.

@interface SuperDBAppDelegate : NSObject <UIApplicationDelegate>{

NSManagedObjectModel *managedObjectModel;

NSManagedObjectContext *managedObjectContext;

Page 75: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 53

NSPersistentStoreCoordinator *persistentStoreCoordinator;

UIWindow *window;

UINavigationController *navController;

}

@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel;

@property (nonatomic, retain, readonly) NSManagedObjectContext

*managedObjectContext;

@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator

*persistentStoreCoordinator;

@property (nonatomic, retain) IBOutlet UIWindow *window;

@property (nonatomic, retain) IBOutlet UINavigationController *navController;

- (NSString *)applicationDocumentsDirectory;

@end

아마 눈치챘겠지만 navController 아웃렛이 애플리케이션의 루트 뷰 컨트롤러로 동작하는

UINavigationController의 인스턴스를 가리킬 것이다. 다른 뷰 컨트롤러는 표시돼야 할 때 내비게이

션 스택에 푸쉬될 것이고 동작이 완료되면 스택으로부터 팝(pop)될 것이다.

애플리케이션 델리게이트 구현하기

인터페이스 빌더로 넘어가기 전에 SuperDBAppDelegate.m 파일에 아래 코드를 추가해서 애플리케이

션 델리게이트를 마무리하자.

#import "SuperDBAppDelegate.h"

@implementation SuperDBAppDelegate

@synthesize window;

@synthesize navController;

#pragma mark -

#pragma mark Application lifecycle

- (void)applicationDidFinishLaunching:(UIApplication *)application {

// Override point for customization after app launch

Page 76: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

54 l More! 아이폰 3 프로그래밍

[window insertSubview:navController.view atIndex:0];

[window makeKeyAndVisible];

}

...

위 코드가 별로 친숙하지는 않을 것이다. 항상 하던 대로 프로퍼티를 @synthesize로 선언했다.

applicationDidFinishLaunching: 메서드에서 애플리케이션의 루트 뷰 컨트롤러인 navController의

view 프로퍼티를 contentView의 하위 뷰로 추가해서 사용자에게 보이게 했다.

이제 SuperDbAppDelegate.m의 아래로 스크롤을 내려보자. 좋은 메모리 사용자가 되기 위해

dealloc 메서드를 추가해준다. 아래 코드를 파일의 밑부분에 추가해준다.

...

- (void)dealloc {

[managedObjectContext release];

[managedObjectModel release];

[persistentStoreCoordinator release];

[window release];

[navController release];

[super dealloc];

}

@end

계속 진행하기 전에 SuperDbAppDelegate.h와 SuperDbAppDelegate.m을 저장했는지 확인하자.

테이블 뷰 컨트롤러 생성하기

애플리케이션의 루트 뷰 컨트롤러는 UINavigationController가 될 것이다. 즉, 앱의 루트 뷰 컨트롤러

에 대해 별도로 클래스를 선언하지 않아도 된다. 하지만 영웅들의 리스트를 보여주고 내비게이션 컨

트롤러의 스택에 대한 루트 역할을 하려면 컨트롤러 클래스를 생성해야 한다. 비록 영웅들의 리스트

를 보여주는 데 테이블을 사용하지만 UITableViewController의 하위 클래스를 생성하지는 않을 것

이다. 인터페이스에 탭바도 추가해야 하기 때문에 UIViewController의 하위 클래스를 생성할 것이고

인터페이스 빌더로 인터페이스를 생성할 것이다. 영웅들의 리스트를 표현하게 될 테이블은 뷰 컨트롤

러의 콘텐트 페인의 하위 뷰가 될 것이다.

Page 77: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 55

Groups&Files의 Classes 폴더를 클릭하고 새 파일 도우미 창을 띄우기 위해 N을 입력하거나 File

메뉴의 New File...을 선택하자.

새 파일 도우미 창이 뜨면(그림 3-3) 왼쪽 상단에 있는 iPhone OS의 Cocoa Touch Class를 선택하

고 오른쪽 상단의 UIViewController subclass를 선택하자. UITableViewController subclass 체크박

스에 체크가 해제돼 있는지 확인하자. 테이블 뷰와는 달리 nib 파일을 사용해야 되기 때문에 XIB for

user interface 체크박스가 체크된 상태인지 확인하자. Next 버튼을 클릭한다.

그림 3-3 | 새 파일 도우미에서 오브젝티브C 하위 클래스 템플릿을 선택한다.

파일 이름에 HeroListViewController.m을 입력하고 Also create “HeroListViewController.h”

체크박스를 체크한다. 리턴 키를 누르면 프로젝트에 파일이 추가된다. 파일이 추가된 후 HeroList

ViewController.xib 파일을 Classes 폴더에서 드래그해서 Resources 폴더로 옮긴다.

MainWindow.xib 설정하기

인터페이스 빌더를 열면 그림 3-4와 비슷한 모습을 볼 수 있다. 이제 인터페이스 빌더가 익숙할 것이

다. 하지만 다시 한 번 간략하게 각 창의 이름을 되짚어보자. 타이틀 바에 MainWindow.xib라고 표시

된 왼쪽 상단의 창은 nib 파일의 메인 창이다. 그 아래에 있는 창은 애플리케이션에 단 하나밖에 없는

UIWindow를 표현하는 윈도우 창이다. 윈도우 창이 닫혀 있다면 nib 파일의 메인 창에서 윈도우 아

Page 78: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

56 l More! 아이폰 3 프로그래밍

이콘을 더블클릭하면 창이 다시 열릴 것이다.

nib의 메인 창 오른쪽에 작은 타이틀 바를 포함하고 있는 창은 현재 선택된 아이템의 속성을 변경

할 수 있는 인스펙터(Inspector)다. 마지막으로 맨 오른쪽에 있는 창은 nib에서 사용할 수 있는 미리 설

정된 아이템을 갖고 있는 라이브러리 윈도우다.

그림 3-4 | 인터페이스 빌더 안의 MainWindow.xib

라이브러리의 맨 상단에 위치한 콤보박스에서 Controllers를 선택한다(Library의 Cocoa Touch

밑에 있다). Controllers를 선택하면 아래의 아이템 목록에서 Navigation Controller 아이콘이 보

일 것이다(그림 3-5). 이 아이콘을 nib 파일의 메인 창으로 드래그한다. 그러면 nib의 메인 창에는

Navigation Controller라는 아이콘이 추가로 생길 것이고(아이콘 뷰 모드를 사용하고 있다면 긴 이

름의 끝을 잘라버리기 때문에 Navigation Co...로 보일 것이다) 새로운 창이 화면에 뜰 것이다.

새로운 창은 점선으로 이루어지고 View라고 적힌 둥근 회색 사각형과 Root View Controller라는

타이틀을 갖는다. 이것은 내비게이션 컨트롤러가 작동하기 위해서는 최소한 한 개의 자식 뷰 컨트롤

러를 가져야 한다는 점을 보여준다. 인터페이스 빌더에서는 루트 뷰 컨트롤러를 설정할 수 있다. 루트

뷰 컨트롤러를 설정하는 가장 쉬운 방법은 nib의 메인 창에 위치한 세 가지 View Mode 아이콘 중에

Page 79: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 57

서 가운데 아이콘을 클릭해서 리스트 뷰 모드로 바꾸는 것이다(그림 3-7).

그림 3-5 | 내비게이션 컨트롤러 아이콘. 사용하고 있는 인터페이스 빌더의 버전에 따라 라이브러리가 기본적으로

아이템을 표시하는 방법이 다르다. 단지 아이콘만 보일 수도 있고(왼쪽) 아이콘과 짧은 설명이 보일 수도 있다(오른

쪽). 라이브러리가 아이템을 표시하는 방법은 가운데 페인에서 마우스 오른쪽 버튼을 클릭해서 바꿀 수 있다.

그림 3-6 | nib에 내비게이션 컨트롤러를 추가하면 새로운 창이 뜰 것이다.

그림 3-7 | 리스트 뷰 모드의 nib의 메인 창

Page 80: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

58 l More! 아이폰 3 프로그래밍

리스트 뷰 모드에서 Navigation Controller의 옆에 펼쳐보기 삼각형(disclosure triangle)이 있는 것

을 볼 수 있다. 이것은 이 밑으로 하위 아이템이 있다는 것을 의미한다. 아이템은 다른 타입의 하위 아

이템을 가질 수 있다. 뷰 컨트롤러 클래스는 일반적으로 각 클래스에서 제어하는 뷰나 각 클래스에서

관리해야 하는 하위 뷰 컨트롤러를 갖는다. 펼쳐보기 삼각형을 클릭해서 Navigation Controller를 열

어보자. 내비게이션 바 인스턴스와 View Controller (Root View Controller)라는 이름이 꽤 긴 뷰 컨트

롤러를 볼 수 있을 것이다. 뷰 컨트롤러는 내비게이션 컨트롤러의 루트 뷰 컨트롤러를 나타낸다. 앞에

서 말했듯이 HeroListViewController는 내비게이션 컨트롤러의 루트 뷰 컨트롤러처럼 동작하게끔

설계됐다. 루트 뷰 컨트롤러 클래스를 HeroListViewController로 바꿔줘야 한다.

View Controller (Root View Controller)를 클릭하고 4를 눌러 아이덴티티 인스펙터를 띄운다

(그림 3-8). 아이덴티티 인스펙터의 클래스를 UIViewController에서 HeroListViewController로 바

꾸자. 이것은 애플리케이션이 실행될 때 HeroListViewController에 대한 하나의 인스턴스를 생성할

것이다.

그림 3-8 | 아이텐티티 인스펙터는 내비게이션 컨트롤러의 루트 뷰 컨트롤러에

속한 클래스를 커스텀 컨트롤러 클래스로 바꿀 수 있게 해준다.

이제 nib 안에 내비게이션 컨트롤러와 커스텀 컨트롤러 클래스가 완성됐다.

아웃렛에 연결하기

앞서 내비게이션 컨트롤러를 위한 애플리케이션 델리게이트 안에 아웃렛을 생성했고, nib에

UINavigationController의 인스턴스를 추가했다. 이제 아웃렛을 연결해보자. SuperDBApp 델리게이

트에서 nib의 메인 창에 위치한 Navigation Controller로 컨트롤 버튼을 누른 채로 드래그(이하, 컨트

Page 81: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 59

롤-드래그)하자. 검은색 팝업 메뉴가 뜨면 navController라는 아웃렛을 선택해서 연결한다.

이제 nib가 다 정리됐다. Xcode로 다시 돌아가자.

데이터 모델 디자인하기

2장에서 봤듯이 Xcode의 데이터 모델 에디터는 애플리케이션의 데이터 모델을 디자인하는 곳이다.

프로젝트 창의 Resources 그룹에 있는 SuperDB.xcdatamodel을 클릭하자. 데이터 모델 에디터가 뜰

것이다(그림 3-9).

그림 3-9 | 빈 데이터 모델 에디터가 애플리케이션의 데이터 모델을 만들어주길 기다리고 있다.

2장에서 다뤘던 템플릿과는 달리 아무것도 없는 데이터 모델을 볼 수 있다. 맨 처음 데이터 모델에

추가해야 하는 것은 엔터티이다. 엔터티는 클래스 선언과 유사하다는 점을 기억하자. 비록 직접 데이

터를 저장하지는 않지만, 데이터 모델에 하나의 엔터티도 없다면 애플리케이션은 데이터를 저장할 수

없을 것이다.

Page 82: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

60 l More! 아이폰 3 프로그래밍

엔터티 추가하기

이 애플리케이션의 목적은 슈퍼 영웅들의 정보를 관리하는 것이므로 당연히 영웅을 나타내는 엔터티

를 생성할 것이다. 이 장은 간단하게 영웅들의 이름, 비밀 신원, 생일, 성별 데이터를 관리하는 것으로

시작하겠다. 이후의 장에서 더 많은 데이터 요소를 추가하겠다.

데이터 모델 에디터의 좌측 상단에 있는 엔터티 페인의 좌측 하단을 보면 더하기와 빼기 아이콘을

볼 수 있다(그림 3-10). 더하기 아이콘은 데이터 모델에 새로운 엔터티를 추가하고, 빼기 아이콘은 현

재 선택된 엔터티를 삭제한다. 삭제할 엔터티가 없기 때문에 빼기 버튼은 선택이 불가능한 상태다. 새

로운 엔터티를 추가하기 위해 더하기 버튼을 클릭하자.

그림 3-10 | 엔터티 페인의 더하기와 빼기 버튼은 데이터 모델에서 엔터티를 추가하거나 뺄 수 있게 해준다.

더하기 버튼을 클릭하면 이름이 Entity인 새로운 엔터티가 엔터티 페인에 나타날 것이다. 이 엔터티

는 자동으로 선택된다. 즉, 데이터 모델 에디터의 오른쪽 상단에 있는 디테일 페인이 새로운 엔터티의

세부 사항을 보여줄 것이고 데이터 모델 에디터 하단에 위치한 에디팅 페인의 엔터티도 선택될 것이다

(그림 3-11).

새로운 엔터티 수정하기

데이터 모델에 새로운 엔터티를 추가했다. 이제 엔터티의 이름을 변경해보자. 가장 쉬운 방법은 디테

일 페인에서 변경하는 것이다. 편리하게도 디테일 페인의 Name 텍스트 필드는 선택돼 있으면서 동시

에 하이라이트되어 있을 것이다. 엔터티의 이름을 Hero라고 입력하자.

디테일 페인의 Name 필드 아래로 Class란 텍스트 필드가 있다. NSManagedObject의 기본값으로

놔둔다. 6장에서 기능 추가를 위해 NSManagedObject의 커스텀 하위 클래스를 생성할 때 이 필드를

어떻게 사용하는지 보게 될 것이다.

Page 83: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 61

그 아래로 Parent란 이름의 팝업 메뉴가 있다. 데이터 모델에서도 오브젝티브C의 하위 클래스와 유

사하게 부모 엔터티를 선택할 수 있다. 다른 엔터티를 부모로 설정하면 새로운 엔터티는 부모의 프로

퍼티와 사용자가 설정한 다른 설정를 받게 된다.

그림 3-11 | 엔터티 페인의 더하기 버튼을 클릭하면 엔터티 페인에는 Entity라는 선택된

새로운 행이 생기고 다이어그램 뷰에서는 새로운 엔터티를 둥근 사각형으로 표시해 주며

디테일 페인에서는 선택된 엔터티에 대한 정보를 표시한다.

Parent 팝업 메뉴 밑에는 Abstract 체크박스가 있다. 이 체크박스 설정은 런타임에서 관리 객체를

생성할 때 해당 엔터티가 사용되지 않게 해준다. 여러 엔터티가 몇몇 프로퍼티를 공유하면 추상 엔터

티를 생성한다. 이 예제에서는 공통 필드를 유지하고 공통 필드를 쓰는 모든 엔터티를 추상 엔터티의

자식으로 만들기 위해서 추상 엔터티를 생성해야 한다. 이것은 공통 필드에 대한 수정이 필요할 때 추

상 엔터티만 수정하면 모든 엔터티에 반영된다는 것을 의미한다.

Parent 팝업의 값은 No Parent Entity로 두고 Abstract 체크 박스는 체크하지 않는다.

Page 84: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

62 l More! 아이폰 3 프로그래밍

참고

디테일 페인 좌측 상단의 버튼 바에 대해서 궁금할지도 모르겠다. 이 버튼들은 거의 쓰이지 않는 더 세세한

매개변수를 설정할 수 있게 해준다. 우리는 General 버튼(지금 있는 기본 버튼)이 선택됐을 때의 표시 옵션

을 제외하고는 어떠한 설정 옵션도 바꾸지 않을 것이다.

세세한 옵션에 관심이 있다면 Core Data Programming Guide(http://developer.apple.com/

documentation/Cocoa/Conceptual/CoreData/)와 Core Data Model Versioning and Data Migration

Guide(http://devworld.apple.com/documentation/Cocoa/Conceptual/CoreDataVersioning/

index.html)를 읽어보자.

새로운 엔터티에 속성 추가하기

이제 이 엔터티에 기반을 둔 관리 객체가 데이터를 저장할 수 있게 하기 위해 속성을 추가한다. 이 장

에서는 이름, 비밀 신원, 생일, 성별이라는 4개의 속성을 사용한다.

데이터 모델 에디터에서 엔터티 페인의 오른쪽에 있는 것은 프로퍼티 페인이다. 프로퍼티 페인은 현

재 선택된 엔터티에 속성을 포함하는 프로퍼티를 추가할 수 있는 곳이다. 프로퍼티 페인의 좌측 하단

을 보면 엔터티 페인의 좌측 하단에서 봤던 것과 유사한 버튼을 볼 수 있다. 프로퍼티는 하나 이상의

타입이 존재하기 때문에 더하기 버튼은 작은 삼각형을 갖고 있다. 더하기 버튼을 클릭하면 팝업 메뉴

가 생성되면서 어떤 타입의 프로퍼티를 추가할지 골라야 한다. 이제 4가지 속성을 더해보자.

Name 속성 추가하기

프로퍼티 페인의 더하기 버튼을 클릭한다. 클릭하면 그림 3-12와 같은 팝업메뉴를 볼 수 있을 것이다.

속성을 추가할 것이기 때문에 팝업메뉴에서 Add Attribute를 선택한다.

그림 3-12 | 프로퍼티 페인의 더하기 버튼을 클릭하면 추가하고 싶은 프로퍼티의 종류를 고를 수 있다.

Page 85: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 63

속성 수정하기

Hero 엔터티는 이제 newAttribute라는 속성을 하나 갖게 된다. 새로운 엔터티를 하나 생성하면 새로

추가된 속성이 자동적으로 선택되고 정보가 디테일 페인에 표시되는 것처럼 디테일 페인에 방금 생성

한 속성의 정보가 표시될 것이다. 앞에서 봤듯이 Name 필드가 선택돼 있을 것이고 속성의 이름을 입

력할 수 있을 것이다. 그림 3-13처럼 name을 입력한다.

그림 3-13 | 속성의 이름을 입력한 후의 디테일 페인

Hero 엔터티 이름을 대문자 H로 시작하고 name 속성의 이름을 소문자 n으로 표시한 것은 실수가 아니다.

이것은 엔터티와 프로퍼티에 적용되는 명명 규약(naming convention)이다. 엔터티는 대문자로 시작되고,

프로퍼티는 소문자로 시작한다. 엔터티나 프로퍼티가 하나 이상의 단어로 이뤄져 있으면 각 단어의 첫 글자

를 대문자로 입력한다.

Name 필드 아래에는 Optional, Transient, Indexed라는 세 개의 체크박스가 있다. Optional이 체

크돼 있다면 속성에 아무 값도 할당돼 있지 않더라도 해당 엔터티는 저장될 것이다. 만약 체크하지 않

는다면 이 엔터티에 기반을 둔 관리 객체를 저장하려 할 때 name 속성이 nil이면 검증 오류가 발생해

저장할 수 없을 것이다. 이러한 경우 name은 영웅들을 구별할 수 있는 주 속성이다. Optional 체크박

스를 클릭해 체크를 해제하여 이 필드의 값이 필요하게끔 만들자.

두 번째에 있는 Transient 체크 박스는 속성이 영구 저장소에 저장되지 않게 만들어준다. 이 속성은

비표준 데이터인 커스텀 속성을 만드는 데도 쓰일 수 있다. Transient에 대해서는 너무 신경 쓰지 말

자. 일단 체크를 해제하고 이 부분에 대해서는 6장에서 자세히 다루겠다.

마지막 체크박스는 데이터 저장소에 해당 속성의 인덱스를 더하라고 알려주는 Indexed다. 모든 영

구 저장소가 인덱스를 지원하지는 않지만 기본 저장소(SQLite)는 지원한다. 데이터베이스는 필드를

Page 86: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

64 l More! 아이폰 3 프로그래밍

기준으로 검색하거나 정렬할 때 검색 속도를 높이기 위해 인덱스를 사용한다. 슈퍼 영웅들을 이름순

으로 정렬할 것이므로 SQLite가 속성의 데이터를 저장할 행에 인덱스를 생성하도록 Indexed 체크박

스를 체크하자.

경고

적당한 인덱스 사용은 SQLite 영구 저장소의 성능을 극대화한다. 하지만 불필요한 곳에 인덱스를 추가하면

성능이 오히려 떨어질 수도 있다. Indexed를 체크할 이유가 없다면 체크하지 않은 상태로 두자.

속성 종류

모든 속성은 저장할 수 있는 종류의 데이터를 판별하는 타입을 갖고 있다. Type 드롭다운 메뉴(현재

Unde�ned로 설정됨)를 클릭하면 코어 데이터가 지원하는 다양한 자료형을 볼 수 있다(그림 3-14). 이

것들은 6장에서 보게 될 커스텀 속성을 구현하지 않고 저장할 수 있는 다양한 종류의 데이터다. 각 자

료형은 값을 저장하거나 검색하는 데 사용되는 오브젝티브C 클래스에 대응하며 관리 객체에 값을 설

정할 때는 항상 정확한 객체를 사용해야 한다.

UndifinedInteger 16Integer 32Integer 64DecimalDoubleFloatStringBooleanDataBinary dataTransformable

그림 3-14 | 코어 데이터에서 지원하는 데이터 종류

정수 자료형

Integer 16, Integer 32, Integer 64는 모두 부호화된 정수(자연수)를 저장한다. 세 종류의 유일한 차이

점은 저장할 수 있는 값의 최솟값과 최댓값의 크기다. 일반적으로 가장 작은 크기의 정수형을 사용하

도록 한다. 예를 들어 변수가 천 이상의 값을 갖지 않는다면 Integer32나 Integer 64가 아니라 Integer

16을 선택해야 한다. 이 세 종류의 정수형이 저장할 수 있는 최솟값과 최댓값은 다음과 같다.

Page 87: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 65

자료형 최솟값 최댓값

Integer 16 -32,768 32, 767

Integer 32 -2,147,483,648 2,147,483,647

Integer 64 -9,223,372,036,854,775,808 9,223,372,036,854,775,807

런타임에는 NumberWithInt:나 numberWithLong:과 같은 팩토리 메서드로 생성된 NSNumber

의 인스턴스를 관리 객체의 정수형 속성에 설정한다.

10진수와 실수 자료형

Decimal, Double, Float은 10진수를 저장한다. Double과 Float은 C 자료형 중 double과 �oat과 유사

하게 부동 소수점을 표현한다. 10진수의 부동 소수점 표현은 데이터를 표현하기 위해 고정된 크기의

바이트를 사용하기 때문에 항상 근사치이다. 숫자가 소수점 왼쪽으로 커질수록 소수점 아랫부분을

표현할 수 있는 바이트가 적어진다. Double 자료형은 하나의 숫자를 저장하기 위해 64비트를 사용하

는 반면, Float 자료형은 하나의 숫자를 저장하기 위해 32비트를 사용한다. 두 자료형은 다양한 목적

으로 쓰일 수 있다. 하지만 통화와 같이 작은 반올림 오차에도 문제가 되는 경우 코어 데이터는 반올

림 오차에 영향을 받지 않는 Decimal 자료형을 지원한다. Decimal 형은 내부적으로 고정 소수점을

사용하여 유동 소수점에서 발생할 수 있는 반올림 오차에 영향을 받지 않는 38자리 숫자를 저장할

수 있다.

런타임에 NSNumber 팩토리 메서드인 numberWithFloat:이나 numberWithDouble:에서 생성

된 NSNumber 인스턴스를 사용해서 Double과 Float 속성을 설정한다. 이에 비해 Decimal 속성은

NSDecimalNumber 클래스의 인스턴스를 사용해 설정해야만 한다.

문자열 자료형

문자열(String) 자료형은 가장 흔하게 쓸 자료형 중 하나다. 문자열 속성은 어떤 언어로 썼든 간에 내부

적으로 유니코드를 사용하고 있는 텍스트를 저장할 수 있다. 문자열 속성은 NSString의 인스턴스를

사용하여 런타임에 설정된다.

불린 자료형

불린(Boolean) 값(YES나 NO)은 불린 자료형을 이용해 저장될 수 있다. 불린 속성은 numberWith

BOOL: 메서드로 NSNumber 인스턴스를 사용해 런타임에 설정된다.

Page 88: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

66 l More! 아이폰 3 프로그래밍

날짜 자료형

날짜나 타임스탬프는 Date 자료형을 이용해 코어 데이터에 저장될 수 있다. 날짜 속성은 NSDate 인스

턴스를 사용하여 런타임에 설정된다.

바이너리 자료형

바이너리(Binary) 자료형은 어떠한 바이너리 데이터라도 저장할 수 있는 자료형이다. 바이너리 속성은

NSData 인스턴스를 사용해 런타임에 설정된다. NSData 인스턴스에 넣을 수 있는 모든 데이터는 바이

너리 속성에 저장될 수 있다. 하지만 일반적으로 바이너리 자료형을 검색하거나 정렬할 수는 없다.

변형가능한 자료형

변형가능한(Transformable) 자료형은 코어 데이터의 자료형뿐만 아니라 코어 데이터의 자료형으로 존

재하지 않는 어떠한 오브젝티브C 클래스를 기반으로도 속성을 만들 수 있게 해주는 값 변환기(value

transformer)처럼 동작하는 특별한 자료형이다. 변환가능한 자료형은 UIImage 인스턴스를 저장하거

나 UIColor 인스턴스를 저장하는 데 사용할 수 있다. 변환가능한 자료형에 대해서는 6장에서 자세히

살펴보겠다.

Name 속성의 자료형 정하기

이름은 명백히 텍스트이기 때문에 이 속성은 문자열 속성이다. Type의 드롭다운 메뉴에서 String을

선택한다. 선택하면 디테일 페인에 새로운 필드가 몇 개 추가될 것이다(그림 3-15). 인터페이스 빌더의

인스펙터처럼 데이터 모델 에디터의 디테일 페인도 문맥을 인식한다. 몇몇 문자열 같은 속성의 자료형

은 추가적인 설정 옵션을 갖는다.

그림 3-15 | String 타입을 선택한 후의 디테일 페인

MinLength:와 MaxLength: 필드는 글자수의 최소 개수와 최대 개수를 정할 수 있게 해준다. 이 필

드에 관리 객체를 저장하기 위해 MinLength:보다 작거나 MaxLength:보다 큰 값을 갖는 값을 입력

Page 89: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 67

하면 이 속성을 저장할 때 검증 오류가 발생할 것이다.

이런 제약조건이 사용자 인터페이스가 아니라 데이터 모델에서 일어난다는 것을 기억하자. 사용자

인터페이스를 통해 제약조건을 명확하게 명시하지 않는 한 이 같은 검증은 데이터 모델을 실제로 저장

할 때까지 일어나지 않을 것이다. 대부분의 인스턴스에서 최소나 최대 길이를 강제하면 사용자 인터페

이스에서도 이와 같은 조치를 취해야 한다. 그렇지 않으면 사용자는 데이터를 입력한 후 저장하기 전

까지 잘못 입력한 값에 대해 알지 못할 것이다. 6장에서 이와 관련된 내용을 더 다루겠다.

다음 필드는 정규식을 나타내는 Reg. Ex. 필드다. 이 필드는 입력된 텍스트를 패턴으로 표현할 수

있는 특별한 문자열인 정규식을 사용해서 더 많은 검증을 할 수 있게 해준다. 예를 들어 속성을 이용

해서 IP 주소를 텍스트로 저장할 때는 \b\d\{1,3}\d.\d{1,3}\.\d{1,3}\.\d{1,3}\b라는 정규식을 사용해서

오직 유효한 숫자 값만 입력됐는지 확인할 수 있다. 여기서는 정규식을 사용하지 않을 것이므로 이 필

드를 비워 둔다.

참고

정규식은 많은 책에서 다루고 있는 매우 복잡한 주제다. 정규식에 대한 내용은 이 책의 범위를 넘어서지만 정

규식을 이용한 데이터 모델 차원의 검증에 관심이 있다면 정규식의 기본 문법과 관련된 자료의 링크가 담긴

위키피디아의 정규식 페이지(http://en.wikipedia.org/wiki/Regular_expression)를 참고하기 바란다.

마지막으로 이 프로퍼티에 기본값을 설정할 수 있는 Default Value 필드를 사용할 수 있다. 이 필드

에 값을 입력하면 이 엔터티에 기반하는 모든 관리 객체는 자동적으로 대응하는 필드에 입력된 값을

갖는 프로퍼티 세트(property set)를 갖게 될 것이다. 즉, Untitled Hero란 값을 이 필드에 입력하면 새

로운 Hero 관리 객체를 생성할 때 name 프로퍼티가 자동적으로 Untitled Hero란 값으로 설정된다.

와우! 정말 훌륭한 아이디어 같다. Untitled Hero를 필드에 입력하자. 그리고 저장하자.

속성의 나머지 부분 추가하기

Hero 엔터티에는 세 가지 속성이 더 필요하다. 이제 모자란 부분을 추가해보자. 프로퍼티 페인의 더하

기 버튼을 다시 클릭하고 Add Attribute를 선택한다. 속성의 이름을 secretIdentity로 정하고 타입을

String으로 선택한다. 모든 슈퍼 영웅은 비밀 신원을 갖기 때문에 Optional 체크 박스는 해제하는 게

좋겠다. 비밀 신원을 이용하여 정렬 및 검색을 할 것이므로 Indexed 박스를 체크하자. Default Value

에는 Unknown 이라고 입력한다. Optional 체크박스를 체크하지 않음으로써 이 필드의 입력을 의무

화했기 때문에 기본값을 설정하는 것이 좋다. 나머지 필드는 그대로 둔다.

Page 90: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

68 l More! 아이폰 3 프로그래밍

경고

name과 secretIdentity에 기본값을 줘야 한다. 그렇지 않으면 프로그램이 올바로 동작하지 않는다. 프로그

램이 충돌을 일으키면 소스 코드 파일과 nib 파일을 저장했는지 다시 한 번 확인해 보자.

더하기 버튼을 한번 더 눌러 다른 속성을 추가하자. Name 필드에 birthdate라 입력하고 타입은

Date로 설정하자. 다른 필드는 기본 설정된 값으로 두자. 모든 영웅들의 생일을 모를 수도 있기 때문

에 이 속성은 Optional로 둔다. 현재로서는 생일을 이용해 검색이나 정렬을 하지 않을 것이기 때문에

Indexed 체크박스를 체크하지 않는다. 여기서 최솟값, 최댓값, 기본값 등을 설정함으로써 몇몇 검증

을 추가할 수 있지만 크게 필요하지는 않다. 기본값은 없는 게 맞을 듯하다. 그리고 최솟값이나 최댓값

설정은 불사의 영웅이 존재할 가능성을 막고 시간을 초월하는 영웅의 존재도 부정하기 때문에 하지

않도록 한다.

이제 추가해야 할 속성은 성별밖에 없다. 성별 정보를 저장하는 방법은 다양하다. 편의를 위해 (그

리고 6장에서 몇몇 도움이 되는 기술을 설명하는 데 도움을 주기 위해) Male이나 Female이라는 문

자열로 저장하겠다. 속성을 추가하고 Name 필드에 sex라 입력하고 String 타입을 선택하자. 양성 돌

연변이나 남성, 여성으로는 표현할 수 없는 영웅이 있을 수도 있으니 Optional 체크박스를 체크된 상

태로 유지하자. 정규식을 사용해서 Male이나 Female만 입력할 수 있게 제한할 수도 있지만 데이터

모델에서 제한하기보다는 사용자 인터페이스에서 선택 리스트를 사용해 사용자가 입력할 때 제한하

도록 하겠다.

이제 SuperDB 애플리케이션의 첫 번째 판을 위한 데이터 모델 구현이 완료됐다. 저장하고 이제 컨

트롤러를 구현해보자.

HeroListViewController 생성하기

그림 3-1을 다시 보면 애플리케이션이 영웅들의 리스트를 표현하고 이름이나 비밀 신원에 따라 정렬

할 수 있음을 확인할 수 있다. 이 장의 앞부분에서 다뤘듯이 두 개의 정렬 옵션을 조절하기 위해 분리

된 컨트롤러를 사용하지 않고 컨트롤러를 하나만 사용하겠다. 영구 저장소로부터 결과를 검색하기

위해 2장에서 본 페치 결과(fetched result) 컨트롤러의 템플릿 코드를 사용하겠다. 하지만 테이블 뷰 컨

트롤러를 사용하지 않고 nib에 사용자 인터페이스를 직접 디자인할 것이다. 다음으로 넘어가기 전에

필요한 아웃렛을 정의해보자.

Page 91: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 69

페치 결과 컨트롤러 선언하기

Groups&Files에서 HeroListViewController.h를 클릭하자. 페치 결과 컨트롤러에 쓰일 프로퍼티와

인스턴스 변수를 선언할 필요가 있으므로 파일을 아래와 같이 수정하자.

#import <UIKit/UIKit.h>

#define kSelectedTabDefaultsKey @"Selected Tab"

enum {

kByName,

kBySecretIdentity,

};

@interface HeroListViewController : UIViewController

<UITableViewDelegate, UITableViewDataSource, UITabBarDelegate,

UIAlertViewDelegate, NSFetchedResultsControllerDelegate>

{

UITableView *tableView;

UITabBar *tabBar;

@private

NSFetchedResultsController *_fetchedResultsController;

}

@property (nonatomic, retain) IBOutlet UITableView *tableView;

@property (nonatomic, retain) IBOutlet UITabBar *tabBar;

@property (nonatomic, readonly) NSFetchedResultsController

*fetchedResultsController;

- (void)addHero;

- (IBAction)toggleEdit;

@end

이 코드는 이전 장에서 본 코드와는 조금 다른 형태다. 무엇이 달라졌는지 보자. 우선 사용자 기본

설정에서 환경설정 값을 저장하거나 검색하기 위한 키로 사용될 상수를 정의했다. 프로그램이 실행되

면 사용자가 마지막으로 프로그램을 썼을 때 위치해 있던 탭에서 다시 시작하게 만들고 싶다. 이 상수

는 애플리케이션의 환경설정 안에 설정 정보를 저장하는 데 사용될 것이다.

Page 92: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

70 l More! 아이폰 3 프로그래밍

다음으로 탭바에서 사용되는 각 탭에 상수를 할당하는 열거형을 선언했다. 숫자 0은 코드의 문맥

상 많은 의미를 가질 수 있지만 상수 kByName은 By Name이라는 탭을 참조한다는 사실을 확연히

드러낸다.

그 다음 클래스가 각종 프로토콜을 따르도록 한다. UITableViewController를 상속받지 않을 것이

므로 UITableViewDelegate와 UITableViewDataSource를 따르도록 수동으로 설정해줘야 한다. 또

한 이 클래스는 탭바의 델리게이트 역할도 해야 하기 때문에 UITabBarDelegate도 따르게 한다. 그렇

게 함으로써 액션 메서드를 활용하지 않아도 사용자가 새로운 탭을 선택했음을 파악할 수 있다.

심각한 오류가 발생하면 프로그램을 종료하기 전에 사용자에게 경고 메시지를 보여줄 것이다. 경

고가 발생한 것을 알아차리려면 경고에 대한 델리게이트도 돼야 한다. 이런 이유로 UIAlertView

Delegate도 따라야 한다. 템플릿 코드는 콘솔에 에러 기록을 남기고 종료하지만 여기서는 좀 더 사용

자 친화적으로 구현해 사용자가 무엇이 잘못되었는지 알 수 있게 했다.

마지막으로 페치 결과 컨트롤러를 사용하고 데이터가 변경됐음을 감지하기 위해 NSFetchedResult

sControllerDelegate를 따른다.

그런 다음 탭바와 테이블 뷰에 대한 아웃렛을 제공하기 위한 인스턴스 변수를 생성했다. 모든 인스

턴스 변수를 비공개(private) 범위에 두고 클래스가 직접 접근하는 것을 막기 위해 @private 키워드로

명시했다. 그러고 나서 페치 결과 컨트롤러를 저장할 비공개 인스턴스 변수를 생성했다. 인스턴스 변

수를 _fetchedResultsController라고 부르는 것에 주목하자. 하지만 몇 줄 아래를 보면 이 프로퍼티의

이름은 실제로 밑줄이 없는 fetchedResultsController이다.

기본적으로 프로퍼티는 인스턴스 변수의 이름이 프로퍼티의 이름과 같기를 바란다. 하지만 이것은

단지 기본적인 행동일 뿐 반드시 그래야 하는 것은 아니다. @synthesize 키워드를 사용하여 구현 파

일 안의 프로퍼티를 합성(synthesize)할 때 프로퍼티의 데이터를 저장하는 데 사용되는 인스턴스 변

수의 이름을 정할 수 있다. 정해진 이름은 아무런 의미를 갖지 않을 수도 있다. 이 이름은 프로퍼티 이

름과 유사하거나 상관 관계를 가질 필요가 전혀 없다.

이 프로퍼티를 합성할 때는 아래 코드를 사용한다.

@synthesize fetchedResultsController=_fetchedResultsController;

프로퍼티의 이름은 항상 @synthesize 키워드 바로 뒤에 붙지만 등호 뒤로는 해당 프로퍼티에서 사

용될 인스턴스의 이름이 나온다. 프로퍼티와 같은 이름을 쓰지만 밑줄이 앞에 붙는 이런 명명 규약은

애플의 예제 코드에서도 자주 볼 수 있다. 일부 개발자들은 이런 명명 규약을 모든 프로퍼티에 적용하

Page 93: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 71

기도 한다. 우리는 다른 객체가 인스턴스 변수와 섞이는 것을 원치 않는 경우에만 이와 같은 방식을 쓸

것이다.

이런 명명 규약은 프로퍼티와 인스턴스 변수를 혼동하는 것을 방지해 준다. 각각 다름 이름을 사용

함으로써 프로퍼티를 사용하려고 할 때 인스턴스 변수를 직접적으로 사용하는 것처럼 보이지 않는다.

이전 장에서 fetchedResultsController는 지연 로딩을 한다는 것을 봤을 것이다. 이 때문에 접근자

(accessor)에 fetchedResultsController가 적절히 로딩됐는지 확실히 보장해 줄 것이므로 접근자를 통

해 fetchedResultsController를 참조하는 것은 굉장히 중요하다. 그 이유에 대해서는 이 장에서 보게

될 것이다. 이런 명명 규약과 @private 키워드의 사용은 접근자에 의해 페치 결과 컨트롤러가 로드되

지 않았을 때 문제를 발생시킬 수 있는, 의도하지 않은 인스턴스 변수로의 직접적인 접근을 방지해 줄

것이다.

참고

개발자 중에 애플에서 밑줄 접두사를 예약해 둬서 이것을 써서는 안 된다고 주장하는 이가 있다. 이것은 오해

다. 사실 애플은 밑줄 접두사를 메서드의 이름에 쓸 용도로 예약하고 있다. 인스턴스 변수의 이름에 밑줄 접

두사를 사용하는 것은 이런 예약과는 관련이 없다. 인스턴스 변수에 밑줄 사용에 대한 제약이 없다는 것은 애

플의 명명 규약에서 확인할 수 있다.

http://developer.apple.com/documentation/Cocoa/Conceptual/CodingGuideLines/Articles/

NamingIvarsAndTypes.html

fetchedResultsController 프로퍼티가 readonly 키워드와 함께 선언돼 있다는 것에 주목하자. 접근

자 메서드의 페치 결과 컨트롤러는 지연 로딩될 것이다. fetchedResultsController가 다른 클래스들에

서 설정되는 것을 원치 않기 때문에 이런 문제를 막기 위해 readonly로 선언했다.

합성된 인스턴스 변수

자주 다루지 않으면서 우리가 전혀 사용해보지도 않은 ‘합성된 인스턴수 변수’(synthesized instance

variables)라고 하는 오브젝티브C 2.0 런타임의 새로운 기능이 있다. 오브젝티브C 2.0 런타임은 프

로퍼티를 선언하고 이에 속하지 않는 인스턴스 변수를 할당해 주지 않더라도 실제로 인스턴스 변

수를 생성해준다. 아래는 완전히 유효한 클래스 인터페이스다.

#import <Cocoa/Cocoa.h>

@interface TestController : NSObject {

}

Page 94: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

72 l More! 아이폰 3 프로그래밍

@property (retain) NSString *testString;

@end

testString에 대한 인스턴스 변수(필요로 하지 않는)가 없다는 점에 주목하자. 64비트 코코아 애

플리케이션이나 아이폰 애플리케이션을 컴파일한다면 필요하지 않을 것이다.

그러나 불행히도 아이폰 시뮬레이터는 32비트 맥 애플리케이션이며 32비트 맥 애플리케이션은

인스턴스 변수 합성을 사용할 수 없다. 이 말은 즉, 시뮬레이터에서 동작되는 아이폰 프로그램은

합성된 인스턴스 변수의 장점을 이용할 수 없다는 의미다. 아래 클래스는 실제 기기를 대상으로

컴파일할 때는 완전 유효한 클래스이지만, 시뮬레이터를 대상으로 컴파일하면 오류가 발생한다.

#import <UIKit/UIKit.h>

@interface MyViewController : UITableViewController {

}

@property (nonatomic, retain) NSManagedObject *myObject;

@end

아이폰에서는 이 기능을 활용할 수 있으며 아래와 같이 플랫폼 매크로를 써서 시뮬레이터에서도

컴파일 가능한 프로그램을 만들 수 있다.

#import <UIKit/UIKit.h>

@interface MyViewController : UITableViewController {

#if TARGET_IPHONE_SIMULATOR

NSManagedObject *myObject;

#endif

}

@property (nonatomic, retain) NSManagedObject *myObject;

@end

하지만 한 가지 함정이 있다. 합성된 인스턴스 변수는 같은 클래스 안에서도 직접적으로 접근이

불가능하다. 접근자와 변경자 메서드를 모든 곳에서 사용해야만 한다.

그러면 인스턴스 변수를 선언해서 얻는 장점은 뭘까? 아이폰에서는 시뮬레이터에서 프로그램을

실행하기 전까지 인스턴스 변수의 타입을 정하지 않아도 된다. 이것은 대다수의 아이폰 개발자들

이 이 기능을 사용하더라도 당장 얻을 수 있는 혜택은 없음을 의미한다. 훗날 좀 더 편리하게 바뀔

지는 모르겠지만 현재로서는 소스 코드 입력을 줄이기 위해 아이폰에서 계속 테스트를 하지 않

는 이상 실제로 얻을 수 있는 이익은 없다.

Page 95: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 73

프로젝트에 두 개의 아이콘을 끌어다 놓자

HeroListViewController를 구현하기 전에 인터페이스를 디자인하고 아웃렛을 연결하기 위해 인터페

이스 빌더를 보자. 계속 진행하기 전에 우선 인터페이스 빌더에서 사용할 이미지 파일 두 개를 Xcode

프로젝트에 추가하자. 프로젝트 압축 파일 중에 03-SuperDB 폴더를 보면 name_icon.png와 secret_

icon.png가 있을 것이다. 이 파일들은 두 개의 탭에서 사용할 것이다. 두 이미지 파일을 프로젝트의

Resources 폴더에 추가하자. 그러고 나서 HeroListViewController.xib 파일을 더블클릭해서 인터페

이스 빌더를 실행한다.

HeroListViewController 인터페이스를 디자인하자

nb 파일이 열리면 View 윈도우가 보일 것이다. View 윈도우가 보이지 않으면 메인 윈도우의 View 아

이콘을 더블클릭하자. 탭바와 테이블 뷰를 추가하고 아웃렛과 연결할 것이다.

우선 탭바를 추가하자. 라이브러리에서 탭바를 찾는다(그림 3-16). 사용자 인터페이스 아이템만 필

요하므로 탭바 컨트롤러가 아니라 탭바를 선택해야 한다.

그림 3-16 | 라이브러리에 있는 탭바

그림 3-17처럼 라이브러리에서 View 윈도우에 탭바를 드래그하여 윈도우 하단에 놓는다.

Page 96: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

74 l More! 아이폰 3 프로그래밍

그림 3-17 | 화면 하단에 아늑하게 위치한 탭바

기본적으로 탭바에는 탭이 두 개 포함돼 있다. 아이콘과 레이블을 각각 바꿔보자. 탭바가 선택된 상

태에서 Favorites라고 쓰인 위쪽에 별을 클릭하고 1을 눌러 속성 인스펙터를 불러온다.

탭바 아이템을 정확히 선택하면 인스펙터에서 Tab Bar Item Attributes와 Favorites로 설정된

Identi�er 콤보박스를 볼 수 있을 것이다. 속성 인스펙터에서 Title을 By Name이라 설정하고 Image

는 name_icon.png라고 설정한다(그림 3-18). 이제 탭바의 오른쪽에 있는 More 탭을 More라는 이름

위의 점 세 개의 아이콘을 클릭해 선택한다. 인스펙터를 이용해 Title을 By Secret Identity라고 설정

하고 Image는 secret_icon.png라고 설정한다.

그림 3-18 | 왼쪽 탭의 속성 설정하기

라이브러리로 돌아와서 테이블 뷰를 찾자(그림 3-19). TableViewController가 아닌 Table VIew를

선택해야 한다. 테이블 뷰를 드래그해서 탭바 위에 놓는다. 크기가 자동으로 조절될 것이다. 테이블 뷰

를 드래그앤드롭해보면 그림 3-20처럼 보일 것이다.

Page 97: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 75

그림 3-19 | 라이브러리에 있는 테이블 뷰

그림 3-20 | 테이블을 드롭한 후의 HeroListViewController 인터페이스

테이블을 위치시키는 것으로 HeroListViewController 인터페이스 구성이 끝났다. 이제 아웃렛과

델리게이트, 데이터 소스를 연결하는 일만 남았다. File’s Owner에서 테이블 뷰로 컨트롤-드래그해서

tableView 아웃렛을 선택하고, 다시 File’s Owner에서 탭바로 컨트롤-드래그하여 tabBar 아웃렛과 연

결한다. 델리게이트와 데이터 소스 연결을 보자.

테이블 뷰에서 File’s Owner로 컨트롤-드래그를 하여 dataSource 아웃렛에 연결하고 다시 컨트

롤-드래그해서 delegate 아웃렛에 연결한다. 그런 다음 탭바에서 File’s Owner로 컨트롤-드래그해서

delegate 아웃렛에 연결한다. 이제 컨트롤러의 아웃렛들이 연결됐고 컨트롤러는 탭바와 테이블 뷰를

위한 델리게이트가 됐으며, 테이블 뷰를 위한 데이터 소스가 됐다. 여기서 할 일은 다 끝났다. nib를 저

장하고 Xcode로 돌아오자.

Page 98: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

76 l More! 아이폰 3 프로그래밍

영웅 뷰 컨트롤러 구현하기

HeroListViewController의 구현은 상속받는 클래스는 다르지만, 이전 장의 RootViewController의

구현과 비슷하다. HeroListViewController.m 파일을 아래 코드로 바꿔주자.

#import "HeroListViewController.h"

#import "SuperDBAppDelegate.h"

@implementation HeroListViewController

#pragma mark Properties

@synthesize tableView;

@synthesize tabBar;

@synthesize fetchedResultsController = _fetchedResultsController;

#pragma mark -

- (void)addHero {

NSManagedObjectContext *context =

[self.fetchedResultsController managedObjectContext];

NSEntityDescription *entity =

[[self.fetchedResultsController fetchRequest] entity];

NSManagedObject *newManagedObject = [NSEntityDescription

insertNewObjectForEntityForName:[entity name]

inManagedObjectContext:context];

NSError *error;

if (![context save:&error])

NSLog(@"Error saving entity: %@", [error localizedDescription]);

// TODO: Instantiate detail editing controller and push onto stack

}

- (IBAction)toggleEdit {

BOOL editing = !self.tableView.editing;

self.navigationItem.rightBarButtonItem.enabled = !editing;

self.navigationItem.leftBarButtonItem.title = (editing) ?

NSLocalizedString(@"Done", @"Done") : NSLocalizedString(@"Edit", @"Edit");

[self.tableView setEditing:editing animated:YES];

}

- (void)viewDidLoad {

[super viewDidLoad];

NSError *error = nil;

Page 99: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 77

if (![[self fetchedResultsController] performFetch:&error]) {

UIAlertView *alert = [[UIAlertView alloc]

initWithTitle:NSLocalizedString(@"Error loading data", @"Error loading data")

message:[NSString stringWithFormat:NSLocalizedString(@"Error was: %@, quitting.",

@"Error was: %@, quitting."),

[error localizedDescription]]

delegate:self

cancelButtonTitle:NSLocalizedString(@"Aw, Nuts", @"Aw, Nuts")

otherButtonTitles:nil];

[alert show];

}

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSInteger selectedTab = [defaults integerForKey:kSelectedTabDefaultsKey];

UITabBarItem *item = [tabBar.items objectAtIndex:selectedTab];

[tabBar setSelectedItem:item];

}

- (void)viewDidAppear:(BOOL)animated {

UIBarButtonItem *editButton = self.editButtonItem;

[editButton setTarget:self];

[editButton setAction:@selector(toggleEdit)];

self.navigationItem.leftBarButtonItem = editButton;

UIBarButtonItem *addButton = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd

target:self

action:@selector(addHero)];

self.navigationItem.rightBarButtonItem = addButton;

[addButton release];

}

- (void)viewDidUnload {

// Release any retained subviews of the main view.

// e.g. self.myOutlet = nil;

self.tableView = nil;

self.tabBar = nil;

}

- (void)dealloc {

[tableView release];

[tabBar release];

[_fetchedResultsController release];

Page 100: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

78 l More! 아이폰 3 프로그래밍

[super dealloc];

}

#pragma mark -

#pragma mark Table View Methods

- (NSInteger)numberOfSectionsInTableView:(UITableView *)theTableView {

NSUInteger count = [[self.fetchedResultsController sections] count];

if (count == 0) {

count = 1;

}

return count;

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

NSArray *sections = [self.fetchedResultsController sections];

NSUInteger count = 0;

if ([sections count]) {

id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];

count = [sectionInfo numberOfObjects];

}

return count;

}

- (UITableViewCell *)tableView:(UITableView *)theTableView

cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *HeroTableViewCell = @"HeroTableViewCell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:HeroTableViewCell];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle

reuseIdentifier:HeroTableViewCell] autorelease];

}

NSManagedObject *oneHero = [self.fetchedResultsController objectAtIndexPath:indexPath];

NSInteger tab = [tabBar.items indexOfObject:tabBar.selectedItem];

switch (tab) {

case kByName:

cell.textLabel.text = [oneHero valueForKey:@"name"];

cell.detailTextLabel.text = [oneHero valueForKey:@"secretIdentity"];

break;

case kBySecretIdentity:

cell.detailTextLabel.text = [oneHero valueForKey:@"name"];

Page 101: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 79

cell.textLabel.text = [oneHero valueForKey:@"secretIdentity"];

default:

break;

}

return cell;

}

- (void)tableView:(UITableView *)theTableView

didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

// TODO: Instantiate detail editing view controller and push onto stack

}

- (void)tableView:(UITableView *)tableView

commitEditingStyle:(UITableViewCellEditingStyle)editingStyle

forRowAtIndexPath:(NSIndexPath *)indexPath {

if (editingStyle == UITableViewCellEditingStyleDelete) {

NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext];

[context deleteObject:[self.fetchedResultsController objectAtIndexPath:indexPath]];

NSError *error;

if (![context save:&error]) {

NSLog(@"Unresolved error %@, %@", error, [error userInfo]);

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:

NSLocalizedString(@"Error saving after delete",

@"Error saving after delete.")

message:[NSString stringWithFormat:NSLocalizedString

@"Error was: %@, quitting.",@"Error was: %@, quitting."),

[error localizedDescription]]

delegate:self

cancelButtonTitle:NSLocalizedString(@"Aw, Nuts", @"Aw, Nuts")

otherButtonTitles:nil];

[alert show];

exit(-1);

}

}

}

#pragma mark -

#pragma mark Fetched results controller

- (NSFetchedResultsController *)fetchedResultsController {

if (_fetchedResultsController != nil) {

Page 102: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

80 l More! 아이폰 3 프로그래밍

return _fetchedResultsController;

}

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

SuperDBAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];

NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Hero"

inManagedObjectContext:managedObjectContext];

NSUInteger tab = [tabBar.items indexOfObject:tabBar.selectedItem];

if (tab == NSNotFound) {

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

tab = [defaults integerForKey:kSelectedTabDefaultsKey];

}

NSString *sectionKey = nil;

switch (tab) {

case kByName: {

NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc]

initWithKey:@"name" ascending:YES];

NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc]

initWithKey:@"secretIdentity" ascending:YES];

NSArray *sortDescriptors = [[NSArray alloc]

initWithObjects:sortDescriptor1, sortDescriptor2, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

[sortDescriptor1 release];

[sortDescriptor2 release];

[sortDescriptors release];

sectionKey = @"name";

break;

}

case kBySecretIdentity:{

NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc]

initWithKey:@"secretIdentity" ascending:YES];

NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc]

initWithKey:@"name" ascending:YES];

NSArray *sortDescriptors = [[NSArray alloc]

initWithObjects:sortDescriptor1, sortDescriptor2, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

[sortDescriptor1 release];

[sortDescriptor2 release];

Page 103: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 81

[sortDescriptors release];

sectionKey = @"secretIdentity";

break;

}

default:

break;

}

[fetchRequest setEntity:entity];

[fetchRequest setFetchBatchSize:20];

NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]

initWithFetchRequest:fetchRequest

managedObjectContext:managedObjectContext

sectionNameKeyPath:sectionKey

cacheName:@"Hero"];

frc.delegate = self;

_fetchedResultsController = frc;

[fetchRequest release];

return _fetchedResultsController;

}

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {

sectionInsertCount = 0;

[self.tableView beginUpdates];

}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {

[self.tableView endUpdates];

}

- (void)controller:(NSFetchedResultsController *)controller

didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath

forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)

newIndexPath {

switch(type) {

case NSFetchedResultsChangeInsert:

[self.tableView insertRowsAtIndexPaths:[NSArray

arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeDelete:

[self.tableView deleteRowsAtIndexPaths:[NSArray

Page 104: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

82 l More! 아이폰 3 프로그래밍

arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

break;

case NSFetchedResultsChangeUpdate: {

NSString *sectionKeyPath = [controller sectionNameKeyPath];

if (sectionKeyPath == nil)

break;

NSManagedObject *changedObject = [controller objectAtIndexPath:indexPath];

NSArray *keyParts = [sectionKeyPath componentsSeparatedByString:@"."];

id currentKeyValue = [changedObject valueForKeyPath:sectionKeyPath];

for (int i = 0; i < [keyParts count] - 1; i++) {

NSString *onePart = [keyParts objectAtIndex:i];

changedObject = [changedObject valueForKey:onePart];

}

sectionKeyPath = [keyParts lastObject];

NSDictionary *committedValues = [changedObject committedValuesForKeys:nil];

if ([[committedValues valueForKeyPath:sectionKeyPath] isEqual:currentKeyValue])

break;

NSUInteger tableSectionCount = [self.tableView numberOfSections];

NSUInteger frcSectionCount = [[controller sections] count];

if (tableSectionCount + sectionInsertCount != frcSectionCount) {

// Need to insert a section

NSArray *sections = controller.sections;

NSInteger newSectionLocation = -1;

for (id oneSection in sections) {

NSString *sectionName = [oneSection name];

if ([currentKeyValue isEqual:sectionName]) {

newSectionLocation = [sections indexOfObject:oneSection];

break;

}

}

if (newSectionLocation == -1)

return; // uh oh

if (!((newSectionLocation == 0) && (tableSectionCount == 1) &&

([self.tableView numberOfRowsInSection:0] == 0))) {

[self.tableView insertSections:[NSIndexSet

indexSetWithIndex:newSectionLocation]

withRowAnimation:UITableViewRowAnimationFade];

sectionInsertCount++;

}

NSUInteger indices[2] = {newSectionLocation, 0};

newIndexPath = [[[NSIndexPath alloc] initWithIndexes:indices

Page 105: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 83

length:2] autorelease];

}

}

case NSFetchedResultsChangeMove:

if (newIndexPath != nil) {

NSUInteger tableSectionCount = [self.tableView numberOfSections];

NSUInteger frcSectionCount = [[controller sections] count];

if (frcSectionCount != tableSectionCount + sectionInsertCount) {

[self.tableView insertSections:[NSIndexSet

indexSetWithIndex:[newIndexPath section]]

withRowAnimation:UITableViewRowAnimationNone];

sectionInsertCount++;

}

[self.tableView deleteRowsAtIndexPaths:[NSArray

arrayWithObject:indexPath]

withRowAnimation:UITableViewRowAnimationFade];

[self.tableView insertRowsAtIndexPaths: [NSArray

arrayWithObject:newIndexPath]

withRowAnimation: UITableViewRowAnimationRight];

}

else {

[self.tableView reloadSections:[NSIndexSet

indexSetWithIndex:[indexPath section]]

withRowAnimation:UITableViewRowAnimationFade];

}

break;

default:

break;

}

}

- (void)controller:(NSFetchedResultsController *)controller

didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo

atIndex:(NSUInteger)sectionIndex

forChangeType:(NSFetchedResultsChangeType)type {

switch(type) {

case NSFetchedResultsChangeInsert:

if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1))) {

[self.tableView insertSections:[NSIndexSet

Page 106: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

84 l More! 아이폰 3 프로그래밍

indexSetWithIndex:sectionIndex]

withRowAnimation:UITableViewRowAnimationFade];

sectionInsertCount++;

}

break;

case NSFetchedResultsChangeDelete:

if (!((sectionIndex == 0) && ([self.tableView numberOfSections] == 1) )) {

[self.tableView deleteSections:[NSIndexSet

indexSetWithIndex:sectionIndex]

withRowAnimation:UITableViewRowAnimationFade];

sectionInsertCount--;

}

break;

case NSFetchedResultsChangeMove:

break;

case NSFetchedResultsChangeUpdate:

break;

default:

break;

}

}

#pragma mark -

#pragma mark UIAlertView Delegate

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {

exit(-1);

}

#pragma mark -

#pragma mark Tab Bar Delegate

- (void)tabBar:(UITabBar *)theTabBar didSelectItem:(UITabBarItem *)item {

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSUInteger tabIndex = [tabBar.items indexOfObject:item];

[defaults setInteger:tabIndex forKey:kSelectedTabDefaultsKey];

_fetchedResultsController.delegate = nil;

[_fetchedResultsController release];

_fetchedResultsController = nil;

NSError *error;

if (![self.fetchedResultsController performFetch:&error])

NSLog(@"Error performing fetch: %@", [error localizedDescription]);

Page 107: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 85

[self.tableView reloadData];

}

@end

코드의 양이 상당하다. 코드를 직접 입력하다 보면 좀 더 친근해질 것이다. 코드의 위쪽부터 차근차

근 살펴보자.

처음 나오는 몇 줄의 코드는 별로 어렵지 않다. 헤더 파일을 임포트했고 몇몇 메서드에서 애플리케

이션 델리게이트를 사용할 것이기 때문에 애플리케이션 델리게이트의 헤더 파일을 임포트했다.

#import "HeroListViewController.h"

#import "SuperDBAppDelegate.h"

@implementation HeroListViewController

그러고 나서 fetchedResultsController에서 쓸 인스턴스 변수가 다른 이름을 갖고 있기 때문에 해

당 인스턴스 변수를 식별할 수 있게 프로퍼티를 합성했다.

@synthesize tableView;

@synthesize tabBar;

@synthesize fetchedResultsController = _fetchedResultsController;

그런 다음 영웅을 추가하기 위한 메서드를 처음으로 구현한다. 이 메서드는 이전 장의 insertNew

Object: 메서드와 동일하다. save: 메서드에서 오류가 발생하면 NO를 반환할 것이고 콘솔에 오류가

전달될 것이다.

#pragma mark -

- (void)addHero {

NSManagedObjectContext *context =

[self.fetchedResultsController managedObjectContext];

NSEntityDescription *entity =

[[self.fetchedResultsController fetchRequest] entity];

NSManagedObject *newManagedObject = [NSEntityDescription

insertNewObjectForEntityForName:[entity name]

inManagedObjectContext:context];

NSError *error;

if (![context save:&error])

NSLog(@"Error saving entity: %@", [error localizedDescription]);

// TODO: Instantiate detail editing controller and push onto stack

}

Page 108: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

86 l More! 아이폰 3 프로그래밍

참고

코드를 컴파일하면 newManagedObject가 사용되지 않았다는 경고가 나타날 것이다. 하지만 이 코드는 실

제로 필요한 코드로, 새로운 관리 객체를 생성하고 삽입하는 역할을 한다. 이 포인터 변수는 사용되지 않으며

이로 인해 경고가 발생한다. 보통 반환되는 값을 저장하지 않았지만 4장에서 애플리케이션을 확장할 때 이

포인터를 사용할 것이다. 일단 경고를 그대로 두자. 다음 장에서 newManagedObject 활용법에 대해 알아

보겠다.

이 메서드의 마지막 줄에 포함된 주석을 봤는가? 일부 주석은 Xcode에서 특별한 의미를 갖는 문자

열로 시작된다. // TODO:로 시작되는 주석은 함수 팝업 메뉴에 포함될 것이다(그림 3-21).

이 주석은 나중에 해야 할 일을 떠올리게 해준다. 이 경우 다음 장에서 볼 세부사항 수정 페인을 인

스턴스화해야 한다는 것을 떠올리게 해준다.

// TODO: 외에도 함수 팝업 메뉴에서 볼 수 있는 특별한 주석이 있다. // FIXME:는 수정해야 할 문제를 표

시할 때 쓸 수 있다. 궁금한 사항이나 혼란스러운 코드를 나타낼 때 사용하는 // ???:나 코드에서 긴급하거

나 놀라운 부분을 나타내는 // !!!:로 시작되는 주석도 함수 팝업 메뉴에 보인다. 또한 // MARK:를 이용하면

#pragma로 시작되는 주석처럼 함수 메뉴에서 콜론 뒤의 주석을 보여줄 것이다.

그림 3-21 | 코드에 추가한 TODO 주석과 같은 몇몇 주석이 함수 팝업 메뉴에 보일 것이다.

Page 109: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 87

다음으로 살펴볼 것은 테이블 뷰의 에디트 모드를 켜고 끄는 것을 설정하는 액션 메서드인 toggle

Edit이다. 테이블 뷰의 에디트 모드를 단순히 토글하는 것과 더불어 사용자 인터페이스는 사용자가

원하는 대로 작동해야 한다. UITableViewController 대신 UIViewController의 하위 클래스를 사용

했기 때문에 Edit 버튼의 레이블을 직접 설정해줘야 한다. 테이블이 에디트 모드인지 아닌지에 따라

에디트 버튼의 레이블을 Edit나 Done으로 바꿔줘야 한다. 또한 에디트 모드의 활성화 여부에 따라 오

른쪽 상단에 있는 더하기 버튼도 제거해야 한다. 에디트 모드에서는 사용자가 새로운 행을 추가할 수

없을 것이다.

- (IBAction)toggleEdit {

BOOL editing = !self.tableView.editing;

self.navigationItem.rightBarButtonItem.enabled = !editing;

self.navigationItem.leftBarButtonItem.title = (editing) ?

NSLocalizedString(@"Done", @"Done") : NSLocalizedString(@"Edit", @"Edit");

[self.tableView setEditing:editing animated:YES];

}

얼핏 보기에 viewDidLoad는 템플릿과 비슷해 보인다. 여기서는 super에 있는 같은 메서드를 호출

하는 것으로 시작해서 페치 결과 컨트롤러를 얻고 performFetch:를 호출한다.

- (void)viewDidLoad {

[super viewDidLoad];

NSError *error = nil;

if (![[self fetchedResultsController] performFetch:&error]) {

오류가 발생하면 단순히 로그만 남기고 종료하게 된다. 대신 사용자에게는 경고창을 띄워 오류에

관한 정보를 보여준다. 더 자세한 정보는 콘솔에 로그로 기록되고 종료되겠지만 최소한 사용자에게

지금 이 애플리케이션은 종료되고 있으며, 종료되기 전에 왜 종료되는지를 알려준다. 실제로 종료하는

명령은 사용자가 경고창을 없앨 때 실행되는 alertView:didDismissButtonWithIndex라는 경고 뷰

델리게이트 메서드에 있다.

UIAlertView *alert = [[UIAlertView alloc]

initWithTitle:NSLocalizedString(@"Error loading data",

@"Error loading data")

message:[NSString stringWithFormat:@"Error was: %@, quitting.",

[error localizedDescription]]

delegate:self

cancelButtonTitle:NSLocalizedString(@"Aw, Nuts", @"Aw, Nuts")

otherButtonTitles:nil];

[alert show];

Page 110: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

88 l More! 아이폰 3 프로그래밍

}

}

viewDidAppear: 메서드는 이전 장에 나왔던 것과 거의 같다. 이 메서드는 더하기 버튼과 에디트 버

튼이 내비게이션 바에 위치하게 해준다.

- (void)viewDidAppear:(BOOL)animated {

self.navigationItem.leftBarButtonItem = self.editButtonItem;

UIBarButtonItem *addButton = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemAdd

target:self

action:@selector(addHero)];

self.navigationItem.rightBarButtonItem = addButton;

[addButton release];

}

에디트 버튼이 탭되거나 사용자가 행을 스와이프(swipe)했을 때 호출되는 메서드인 setEditing:

animated를 상속받았다.

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {

self.navigationItem.rightBarButtonItem.enabled = !editing;

self.navigationItem.leftBarButtonItem.title = (editing) ?

NSLocalizedString(@"Done", @"Done") :

NSLocalizedString(@"Edit", @"Edit");

[self.tableView setEditing:editing animated:animated];

}

테이블 뷰 델리게이트와 데이터 소스 메서드는 상당히 직관적이기 때문에 fetchedResultsController

로 넘어가겠다. 페치 결과 컨트롤러는 이전 장과 거의 비슷하게 구성돼 있다.

- (NSFetchedResultsController *)fetchedResultsController {

if (_fetchedResultsController != nil) {

return _fetchedResultsController;

}

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

SuperDBAppDelegate *appDelegate = (SuperDBAppDelegate *)[[UIApplication

sharedApplication] delegate];

NSManagedObjectContext *managedObjectContext = appDelegate.managedObjectContext;

NSEntityDescription *entity = [NSEntityDescription entityForName:@"Hero"

inManagedObjectContext:managedObjectContext];

Page 111: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 89

정렬방식은 현재 선택된 탭에 따라 달라지기 때문에 현재 어떤 탭이 선택됐는지 알아야 한다. nib가

로드되기 전에 이 메서드가 호출되는 것처럼 아무 탭도 선택되지 않았다면 환경설정에서 마지막으로

선택된 값을 가져온다.

NSUInteger tab = [tabBar.items indexOfObject:tabBar.selectedItem];

if (tab == NSNotFound) {

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

tab = [defaults integerForKey:kSelectedTabDefaultsKey];

}

다음으로 현재 선택된 탭을 기반으로 정렬 기술자(sort descriptor)를 변경하기 위해 정렬 기술자와

이전 장에서 템플릿으로 썼던 페치 리퀘스트를 생성한다. 여기서는 템플릿에서 쓰이지 않았던 페치

결과 컨트롤러의 다른 부분도 사용할 것이다. 페치 결과 컨트롤러를 생성할 때 섹션 이름 키패스를 설

정하면 페치 결과 컨트롤러는 결과집합을 자동으로 섹션으로 나눌 것이다. 가장 흔한 시나리오는 단

순히 첫 번째 정렬 기술자와 같은 키를 넘겨주는 것이다. 그래서 이름으로 정렬하고 섹션 이름 키패스

로 @“name”을 넘겨주면 섹션은 영웅 이름의 첫 글자를 기준으로 자동으로 생성될 것이다. 다음 장에

서 영웅을 수정하는 기능을 넣기 전까지는 이 기능을 액션에서 볼 수 없을 것이다.

여기서는 현재 선택된 탭에 기반하여 정렬 기술자와 섹션 이름 키패스를 설정했다.

NSString *sectionKey = nil;

switch (tab) {

case kByName: {

NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc]

initWithKey:@"name" ascending:YES];

NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc]

initWithKey:@"secretIdentity" ascending:YES];

NSArray *sortDescriptors = [[NSArray alloc]

initWithObjects:sortDescriptor1, sortDescriptor2, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

[sortDescriptor1 release];

[sortDescriptor2 release];

[sortDescriptors release];

sectionKey = @"name";

break;

}

case kBySecretIdentity:{

NSSortDescriptor *sortDescriptor1 = [[NSSortDescriptor alloc]

initWithKey:@"secretIdentity" ascending:YES];

NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc]

Page 112: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

90 l More! 아이폰 3 프로그래밍

initWithKey:@"name" ascending:YES];

NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1,

sortDescriptor2, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

[sortDescriptor1 release];

[sortDescriptor2 release];

[sortDescriptors release];

sectionKey = @"secretIdentity";

break;

}

default:

break;

}

fetchRequest setEntity:entity];

[fetchRequest setFetchBatchSize:20];

NSFetchedResultsController *frc = [[NSFetchedResultsController alloc]

initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext

sectionNameKeyPath:sectionKey

cacheName:@"Hero"];

그런 다음 페치 결과 컨트롤러의 델리게이트를 self로 설정해서 변경 사항이 있을 때 알 수 있

게 했다. 새로운 페치 결과 컨트롤러를 비공개(private) 인스턴스 변수에 할당해 주고 인스턴스 변

수를 반환했다. frc를 일부러 릴리즈하지 않았음에 주목하자. 인스턴스 변수에 직접적으로 컨

트롤러를 할당하기 때문에 자동적으로 계속 유지되지 않는다. 이 말은 즉, 우리가 원하는 대로

_fetchedResultsController의 리테인 카운트의 값이 1이라는 의미다.

frc.delegate = self;

_fetchedResultsController = frc;

return _fetchedResultsController;

}

다음은 4개의 페치 결과 컨트롤러 델리게이트 메서드다. 여기서의 구현은 이전 장에서 다룬 내용과

일치하기 때문에 4개의 메서드가 어떤 역할을 하는지 잘 모르겠다면 2장으로 돌아가 페치 결과 컨트

롤러 부분을 다시 읽어보자.

사용자가 경고 뷰를 없앨 때 호출되는 경고 뷰 델리게이트 메서드는 애플리케이션을 종료하는 것

이외의 동작은 하지 않는다. 이 컨트롤러에서 경고 뷰를 사용하는 이유는 단지 심각한 오류가 발생했

을 때 사용자에게 알려주기 위해서다.

Page 113: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 91

#pragma mark -

#pragma mark UIAlertView Delegate

- (void)alertView:(UIAlertView *)alertView

didDismissWithButtonIndex:(NSInteger)buttonIndex {

exit(-1);

}

마지막으로 사용자가 탭바에서 선택된 탭을 바꿀 때 호출되는 tabBar:didSelectItem: 탭바 델리게

이트 메서드를 보자. 이 메서드는 기본 설정값으로 사용자가 선택한 탭의 인덱스를 저장하는 것으로

시작한다. 비록 탭바 컨트롤러가 어느 탭이 선택됐는지를 알기 위해 탭 인덱스를 사용하더라도 탭바

스스로가 인덱스를 사용할 수는 없다. 대신 선택된 탭바 아이템을 실제로 전달해주며 이 탭의 인덱스

를 결정해 줘야 한다. UITabBar는 items라는 아이템의 배열을 갖고 있다. 배열 안에서 탭바 아이템의

인덱스는 탭 인덱스이기 때문에 NSArray의 indexOfObject: 메서드로 이것을 결정할 수 있다.

#pragma mark -

#pragma mark Tab Bar Delegate

- (void)tabBar:(UITabBar *)theTabBar didSelectItem:(UITabBarItem *)item {

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];

NSUInteger tabIndex = [tabBar.items indexOfObject:item];

[defaults setInteger:tabIndex forKey:kSelectedTabDefaultsKey];

}

다음으로 해야 할 일은 페치 결과 컨트롤러를 nil로 설정해주는 것이다. 이렇게 해서 다음 번에 페치

결과 컨트롤러 접근 메서드가 호출되면 새로운 탭 선택을 기준으로 하여 영구 저장소에서 결과집합을

다시 불러온다. 페치 결과 컨트롤러를 nil로 설정하기 전에 델리게이트 프로퍼티를 nil로 설정해 줘야

한다. 컨트롤러는 페치 결과 컨트롤러의 델리게이트였지만 델리게이트 프로퍼티를 nil로 설정해서 더

는 델리게이트 역할을 하지 않게 된다.

참고

작업을 완료하면 델리게이트 프로퍼티를 nil로 설정하는 것이 좋다. 이렇게 하면 실패하더라도 어떠한 문제도

발생하지 않는다. 몇 가지 예외가 있긴 하지만 일반적으로 객체는 해당 객체의 델리게이트를 유지하지 않으므

로 델리게이트를 nil로 설정하는 데 실패하더라도 객체의 리테인 카운트가 0이 되는 것을 방해하지 않는다.

_fetchedResultsController.delegate = nil;

[_fetchedResultsController release];

_fetchedResultsController = nil;

Page 114: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

92 l More! 아이폰 3 프로그래밍

페치 결과 컨트롤러를 nil로 설정한 후 새로운 기준으로 데이터를 즉시 다시 불러오도록 viewDid

Load:에서처럼 performFetch:를 호출한다. 페치 결과 컨트롤러를 사용할 때 reloadData를 호출하는

것은 중요하다. 페치 결과 컨트롤러를 릴리즈하고 새로운 페치 결과 컨트롤러를 생성했기 때문에 페치

결과 컨트롤러 델리게이트에 의존해서 테이블을 업데이트할 수는 없다.

NSError *error;

if (![self.fetchedResultsController performFetch:&error])

NSLog(@"Error performing fetch: %@", [error localizedDescription]);

[self.tableView reloadData];

}

실행해보자

뭘 망설이는가? 이미 많은 내용을 접했고 이제는 익힌 내용을 시험해 볼만하다. 모든 파일이 저장됐는

지 확인하고 Xcode의 Build 메뉴에서 Build and Run을 선택해 실행해보자.

애플리케이션이 처음 실행될 때 모든 것이 정상적이라면 빈 테이블과 화면 상단의 내비게이션 바와

화면 하단의 탭바를 보고 흐뭇할 것이다(그림 3-22). 내비게이션 바의 오른쪽 버튼을 누르면 이름 없

는 슈퍼 영웅이 데이터베이스에 추가될 것이다. Edit 버튼을 누르면 영웅을 삭제할 수 있다.

참고

앱이 실행되지 않고 충돌을 일으킨다면 두 가지를 확인해봐야 한다. 우선 프로젝트를 실행하기 전에 모든 소

스코드 파일과 nib 파일을 저장했는지 확인하자. 또한 데이터 모델 에디터에서 영웅의 이름과 비밀 신원에 기

본값을 줬는지 확인하자. 그런데도 계속 충돌을 일으킨다면 시뮬레이터를 다시 설정해보자. 시뮬레이터를 다

시 설정하기 위해 시뮬레이터를 불러오고 iPhone Simulator 메뉴에서 Reset Contents and Settings... 을

선택하자. 5장에서는 데이터 모델이 바뀌어도 문제가 일어나지 않게 하는 방법을 알아볼 것이다.

두 개의 탭이 생성됐는지 확인하고 각 탭을 선택할 때마다 화면이 바뀌는지 확인해보자. By Name

탭을 선택하면 그림 3-1과 같은 모습이 보일 것이고, By Secret Identity 탭을 누르면 그림 3-23과 같은

모습이 보일 것이다.

Page 115: More 아이폰3 프로그래밍 : iPhone SDK 3 집중 분석

03 슈퍼 스타트: 데이터 추가, 표현, 삭제 l 93

다 됐지만 아직 끝난 건 아니다

이 장에서는 많은 양의 작업을 했다. 탭바를 사용하는 내비게이션 기반의 애플리케이션을 구성하는

방법을 비롯해서 엔터티를 생성하고 몇몇 속성을 주는 것으로 기본적인 코어 데이터 모델을 디자인하

는 방법을 배웠다.

이 애플리케이션은 아직 완료된 게 아니지만 어느 정도 기반은 다졌다. 준비가 되면 페이지를 넘겨

슈퍼 영웅들의 정보를 사용자가 수정할 수 있게 만들어보자.

그림 3-22 | SuperDB

애플리케이션이 실행된 모습

그림 3-23 | Secret Identity를

선택해도 아직 행의 순서가 바뀌진

않지만 행 안에서 어느 값이 먼저

표시될지는 바뀐다.