Skip to main content

5 posts tagged with "아이폰-앱-개발"

View All Tags

· 6 min read

Chapter 5는 chapter 4에서 크래쉬된 application을 디버깅해서 오류를 수정하는 내용과 사용성 측면의 보완, 그리고 모달뷰를 작성하고 칵테일 추가기능을 넣게 되는데 뷰를 상속해서 사용하는 방법 등을 다루고 있습니다. 다루는 내용도 많고 기네요. :)

viewWillAppear

View가 나타나는 시점에 대한 method 들을 여럿 볼 수 있습니다. viewDidLoad / viewDidUnload 도 있었고 viewWillAppear, viewWillDisappear 등도 있는데 이름만 보면 대충 view가 나타나는 시점에 따라 여러개의 메소드가 제공됨을 추측할 수 있습니다. viewWillAppear는 실제로 view가 추가되기 전에 호출되는 것으로 문서에는 명시되어 있네요. parameter로 BOOL type의 animated를 받고 있는데, YES를 넣으면 view가 나타날 때 animation 효과를 줄 수 있습니다.

문자열 상수의 이용

이전 chapter에 이미 설명되어 있는 내용입니다. 리팩토링을 하자면서 뜬금없이 코드 안에서 사용되는 문자열 들을 별도로 뽑아서 헤더파일 안에 상수로 정의해 두고 문자열 상수로 대체하는 부분이 있었는데요. 이번 chapter에서도 고스란히 그 상수들을 이용하고 있습니다. 여러번 사용할 문자열들을 간단하게 재사용 할 수 있다는 측면에서도 리팩토링의 효과를 충분히 얻을 수 있는 예입니다. 하지만 더 중요한 부분은 단순히 문자열을 개발자가 입력해 사용할 경우 오자가 있어도 컴파일 결과는 정상일 가능성이 높기 때문에 나중에 문제가 생겨도 디버깅하기가 상당히 까다로워질 수 있는데 반해 문자열 상수를 이용할 경우 문자열 상수를 잘못 사용할 경우 컴파일러가 미리 잡아주기 때문에 디버깅과 개발에 들어가는 시간과 노력을 조금이나마 줄일 수 있습니다.

Disclosure Indicator

사용성에 대한 문제가 언급되면서 HIG (Human Interface Guidelines) 라는 용어가 등장합니다. 애플에서 UI의 일관성과 application의 사용성을 높이기 위해 작성해 둔 규칙 같은건데, 꼭 지키라고 강조하고 있네요. 이런 애플의 강한 규제들을 싫어하는 분들도 있지만 저는 어느정도 찬성입니다. 개발자가 궁극적으로 원하는건 자신의 산출물을 통해 사용자들이 멋진 경험을 하게 만드는 것이라고 생각하는데 개발자가 자유도를 조금 빼앗기더라도 사용자에게 일관된, 그리고 멋진 경험을 선사할 가능성을 조금 더 높일 수 있는 방법이라고 생각하기 때문입니다. 그래서 이번 장에서 칵테일 리스트를 누르면 어떤 내용이 더 나타날 것이라는 예상을 할 수 있도록 disclosure indicator 라는 것을 사용합니다. 보통의 리스트가 있는 iphone application을 보면 오른쪽 끝에 화살표가 삽입된 경우가 있는데 이것이 disclosure indicator 입니다. 이것 이외에도 몇가지 type을 더 제공하는 듯 한데, 언젠가는 다른 type 들도 사용할 기회가 있을 겁니다. 이것을 넣고 싶으면 단순히 리스트의 cell의 accessoryType에 사용하고 싶은 타입의 indicator 상수를 넣어주면 됩니다. 이렇게요. 매우 간단합니다. :)

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

Selector

Chapter 5에서 Save와 Cancel 버튼을 만들게 됩니다. 이 때 버튼을 초기화 하면서 action이라는 이름의 파라미터를 넣게 되어있는데 @selector(save:) 이런 식으로 입력을 하고 있습니다. @ 마크는 NSString type 이란걸 알려주기 위해서만 사용한다고 생각했는데, 참 혼란스럽습니다. 찾아보니 @selector 자체는 SEL type을 가리키기 위한 키워드 정도로 그냥 생각하면 될 듯 합니다. C, C++에서 함수 포인터를 이용해 특정한 상황에서 해당 함수를 호출하는 방식과 유사하다고 볼 수 있습니다. 주의해야 할 사항은 method의 콜론까지 포함해서 입력해주어야 한다는 사실입니다. Selector에 대해서는 그냥 감만 잡고 나중에 또 등장하면 자세히 살펴봐야겠습니다.

· 5 min read

Ch.4 부터는 독특한 Objective C 문법이 나오질 않네요. 객체에 메세지 보내는 방법, property, protocol 등만 익숙하면 크게 어려울게 없는 듯 해서 중요해 보이는 부분이나 이상했던 부분 위주로 정리해볼까 합니다.

UITableViewController

이전 chapter 들에서 picker 라는게 나오면서 DataSource와 Delegate를 protocol로 지정해 사용했었는데, Chapter 4에 사용되는 UITableViewController는 DataSource와 Delegate를 이미 protocol로 사용하고 있기 때문에 개발자가 별도로 선언할 필요가 없다고 합니다. 실제로 UITableViewController의 정의 부분을 찾아보니 아래처럼 protocol로 선언되어 있네요.

그래서인지 이미 UITableViewController가 구현해야만 하는 method 들이 이미 만들어져 있습니다.

NSMutableArray

NSMutableArray를 사용하는 부분이 있는데 초기화를 개발자가 원하는 문자열을 사용해 초기화 하는 것으로 예제들이 되어 있습니다. 물론 다른 방법(method)을 사용해도 무방하겠지만요. 재밌다고 생각한건 초기화 할 때 일련의 문자열 마지막에 nil이 들어있다는 사실입니다. 이전 chapter 들에서도 비슷한 부분들이 있었는데, C / C++ 과 비슷하게 문자열 마지막의 nil은 문자열의 마지막임을 알려주는 용도로 사용된다고 합니다. 물론 배열 안에 추가되는 것은 아니구요.

NSBundle

이번 chapter에서 독특한 클래스가 등장합니다. NSBundle 이라는. Property list를 DB 대신 이용하면서 property list의 경로를 찾기 위해 사용하고 있는데, 문서들을 찾아보니 프로그램에서 사용되는 resource나 code 들을 그룹핑한 파일시스템에서의 위치를 표현한다고 하네요. Cocoa에서 몇개의 파일들과 디렉토리들을 묶음으로 관리하는데 그 묶음을 bundle이라고 한다고 합니다. 자세한 내용은 더 찾아봐야 하겠지만 일단은 우리가 작성하는 application에 존재하는 파일이나 resource 들을 읽어올 때 사용해야 한다고 생각하면 편할 듯 합니다. 나중엔 음원이나 영상들을 얻어와서 읽어들일 때도 사용가능할 듯 싶네요.

UIViewController subclass 생성시 유의사항

이번에 사용되는 예제에서 detail view를 위해 UIViewController subclass를 생성해서 작성하게 됩니다. 이 때 책에 표시가 되어있는데 저는 깜빡하고 넘어갔던 부분이 "with XIB for user interface" 옵션인데요. 이걸 체크하지 않으면 h와 m 파일만 생성됩니다. 전 xib 파일을 따로 생성해서 작업을 했었는데, 어찌된 일인지 나중에 run 해보면 빌드는 정상인데 run time error가 발생하더군요. xib 파일을 따로 인식하지 못하는 모양이었습니다. 분명 별도로 생성한 xib 파일을 나중에 엮어주는 방법이 있을텐데 한참이나 고생하다가 옵션 체크해서 새로 작성했던 기억이 있습니다. 저처럼 고생하는 일이 없기를... 그래도 이런 실수들 한거 전 좋습니다. 알아낸게 있으니까요. :)

Debugger와 Console 단축키

단축키는 여러모로 편리합니다. 머릿속에 각인시켜 두는게 힘들긴 하지만요. 이번 chapter에서 debugging에 대한 맛보기가 등장하는데 따로 debugger 윈도우나 console이 올라오지 않으면 아래의 단축키를 사용하세요.

Debugger : Command + Shift + Y

Console : Command + Shift + R

· 7 min read

이번주에 연구회에서 책 Ch.3에 대한 내용을 발표하게 되어서 Ch.2 부터 정리했습니다. 원래 지난주에 Ch.3까지 다 정리하긴 했는데 그게 머릿속으로만 정리한거라 ㅎㅎ. Ch.2와 3이 거의 연결되는 내용이라 따로 분리할 필요는 없을거 같긴 한데 그래도...

Protocol

@interface InstaTwitViewController : UIViewController
<UIPickerViewDataSource, UIPickerViewDelegate> {
...
}

위의 예문에서 보면 특이하게 < > 기호로 감싼 부분이 있는데, UIPickerViewDataSource와 UIPickerViewDelegate 라는 protocol을 사용하겠다는 의미입니다. Protocol이란 개념이 Objective C 2.0 부터 포함되어 있는거 같은데, 다른 언어들만 접해본 입장에서는 생소한 개념입니다. 근데 뭐 간단히 생각해보면 일반적인 protocol을 보통은 '어떤 시스템에서 미리 약속해 놓은 규약' 정도의 의미로 사용하고 있으니 비슷하게 이해하고 넘어가면 되지 않을까 싶네요. Objective C의 특징과 연결해서 생각해보면 Objective C가 다중상속을 금지하고 있기 때문에 보완하고자 만들어진 개념이 아닐까 생각됩니다. 전 Java에서 Interface와 유사한 녀석이라 생각하기로 했습니다.

alloc & init

activities = [[NSArray alloc] initWithObjects : @"abc", @"def", nil];

Objective C에서는 new 대신 alloc이란 method (message)로 새로운 객체 생성을 하는 듯 합니다. 보통 class 들의 멤버들을 열어보면 보통 초기화 method들이 제공되는 듯 한데 초기화도 방법에 따라 다양한 녀석들이 있습니다. 위의 경우엔 (initWithObjects) 다른 객체들로 초기화하는 내용을 표현하고 있습니다. 위에서는 NSString type의 객체들이네요. 그리고 마지막은 nil (=null)을 넣어두고 있습니다. Objective C에서 제공하는 method 들은 대부분 이름만 봐도 대충 의미가 파악된다는게 참 좋은 점이라고 생각합니다. 근데 쓰면서 보니 마지막엔 꼭 nil을 넣어주어야 하는 것인지 갑자기 궁금해지네요. 이 내용은 확인해서 추가해야겠어요. 참 alloc으로 새로 할당한 녀석들은 꼭 나중에 release 해야 합니다.

참으로 헷갈렸던 method 이름

- (NSInteger)pickerView : (UIPickerView *)pickerView numberOfRowsInComponent : (NSInteger)component {
....
}

이거 전체가 method의 prototype입니다. 참 이상하고 헷갈리고, 책을 당장이라도 덮고 싶게 만드는 것들 중 하나입니다. 근데 한 번 이해하니 오히려 편한 부분도 있습니다. 그냥 보면 굳이 parameter로 무엇을 전달해야 하는지 reference를 찾아볼 필요가 없어요. :) 그래서 제 나름대로 이해하기 쉽게 배열하기로 했습니다. 그럼 더 편하거든요. 위의 method의 정확한 이름은 이렇게 표현할 수 있습니다.

(NSInteger)pickerView : numberOfRowsInComponent

pickerView만 이름인게 아니라 numberOfRowsInComponent까지 이름이 되는거죠. 이렇게 이해하면 됩니다. 원래 pickerView라는 녀석인데 numberOfRowsInComponent에 필요한 parameter를 하나 더 받을거다 라고요. 그래서 나름 쉽게 풀어서 다시 쓰면 아래와 같습니다.

(NSInteger)pickerView : (UIPickerView *)pickerView
numberOfRowsInComponent : (NSInteger)component {
....
}

다시 쉽게 해석해보면 첫번째 parameter로 UIPickerView의 pointer type인 pickerView를 가져야 하고, numberOfRowsInComponent라는 parameter가 필요한데, NSInteger type의 component란 이름으로 parameter를 받을거란 얘기입니다. 책에선 외부 parameter, 내부 parameter란 이름으로 얘기하고 있는데 공식적으론 numberOfRowsInComponent란 parameter가 필요하고 실제로 component란 이름의 parameter를 넘긴다고 이해하면 됩니다. 그러므로 method 내부에선 component를 가지고 처리하면 되겠습니다. numberOfRowsInComponent를 보면서 가만히 생각해보면 이게 어떤 component 안에 있는 row의 갯수를 어떻게 하겠다는 얘기니 필요한 component를 parameter로 받겠다는 걸 알 수 있습니다. 특이하게 보통 이런 경우 number of rows를 return하게 되더군요. 실제로 이 method는 특정 component를 parameter로 받아서 그 component의 row 갯수를 NSInteger type으로 return하도록 작성되어 있습니다. 설명이 좀 복잡해진 듯 하지만, 3개 이상의 parameter 들도 이런 식으로 작성되기 때문에 이런 형태에 대해 익숙해져야 할 듯 합니다.

3개의 parameter를 사용하는 예

- (NSString *)pickerView : (UIPickerView *)pickerView
titleForRow : (NSInteger) row
forComponent : (NSInteger)component {
....
}

위에서 설명한 것과 비슷한 방법으로 해석해보면 제일 끝에서부터, component를 받을거고 row도 필요하고 NSString * type의 값을 return 할거라는 걸 알 수 있고, 더 나아가면 특정 component를 받아서 component 별로 row를 입력받을 생각이고 결국은 해당 row의 title을 return 하겠다는 걸 추측해볼 수 있습니다.

· 5 min read

Ch.1, 2를 보면서 의문을 가지고 있었던 모든 것을 Ch.3에서 해결할 수 있습니다. Head First 시리즈라서 그런지 책 구성이 무조건 시작하고 따라하면서 복잡한 설명은 다음에 한다는 식으로 표현되어 있었는데, 진짜 약속대로 Ch.3에서 설명하고 있더군요.

Property

이게 뭔지 정말 궁금했었습니다. 궁금증을 이기지 못해 가지고 있던 Objective C 책을 뒤져서 Ch.2 공부하던 시점에 대강 알아두긴 했는데 Ch.3에서 친절하게 설명했더군요. 예문은 아래와 같습니다.

@interface InstaTwitViewController : UIViewController
<UIPickerViewDataSource, UIPickerViewDelegate> {
IBOutlet UITextField *notesField;
....
}

@property (nonatomic, retain) UITextField *notesField;
....
@end

보통 OOP를 공부하다보면, 멤버변수에 직접 접근하지 말고 getter와 setter를 만들어 간접적으로(indirection의 의미) 접근하는 방법을 사용하라고 권유하는 걸 많이 보게 됩니다. 근데 사실 getter와 setter 라는게 별개 없습니다. 남이 작성한 코드를 열어봐도 getter와 setter는 거의 같은 모습을 하고 있지요. 단지 사용되는 멤버변수들의 이름만 다를 뿐입니다. 그래서인지 Objective C에서는 이걸 자동으로 만들어주는 기능을 제공하고 있습니다. 그 기능이 바로 그동안 괴롭혀왔던 property 입니다. 위의 예문에서는 notesField라는 이름을 가진 UITextField 객체를 사용하고 있는데 읽거나 변경하는 작업을 하게 될 것 같습니다. 그래서 property로 만들어서 별도로 getter와 setter를 작성할 필요없이 자유롭게 사용할 수 있게 만들어 두었습니다. 유의할 사항은 property를 사용한다면 class 구현부분에 반드시 @synthesize로 명시해주어야 한다는 점입니다.

Property 속성 키워드

Property에 대해서 대강 이해가 되었지만 예문에 있는 nonatomic과 retain은 무엇이냐는 의문이 또 남게 됩니다. Objective C에서는 property로 getter와 setter를 만들어줄 때 속성을 지정할 수 있게 해 두었다고 합니다. nonatomic과 retain은 그 속성 중 일부인거죠. 책에서 설명하고 있는 속성들은 아래와 같습니다.

readonly : property가 변경되지 않을 때 사용 . 컴파일러가 setter 생성하지 않음.

retain : 객체를 다룰 때 사용. 객체를 계속 사용하겠다는 의미이며, 내부적으로 reference count를 증가시킴

readwrite : property를 변경할 필요가 있을 때 사용(default)

copy : property를 복사해 사용하겠다는 의미이며 전달된 원래의 값이 변경되지 않도록 할 때 사용됨

assign : int, float 등의 기본형을 다룰 때 사용. 단순히 할당하고자 할 때만 이용함(default)

nonatomic : 기본적으로는 property 값을 변경할 때 mutex를 사용하도록 되어있는데(atomic), multi-thread가 필요없을 경우 mutex 사용이 낭비이므로 불필요한 처리를 막을 때 사용

기타

NSString 객체들은 값 변경이 되지 않습니다. 만약 이미 할당한 문자열을 바꿀 일이 있다면 NSMutableString을 사용하면 문자열 변경이 가능합니다.

· 9 min read

Indirection에 이어 OOP에 대한 부분을 공부했다. Objective C 자체가 객체지향 때문에 C에서 small talk를 차용했다고 알려져 있기 때문에 얼마나 효율적인가가 궁금하긴 했다. 일단 C++ 과 비교했을 때 비슷하다는 느낌도 들었지만 가독성 면에서는 더 낫다는 생각이다. 또 java와 비슷하게 interface와 implementation 부분을 구분하기 좋다는 것도 장점이지 않을까 싶다. 내용 정리하면서 사용했던 예제는 보통 C++이나 java에서 OOP 설명에 많이 사용하는 도형 예제이다. (Circle, Rectangle, Triangle 을 처음엔 구조체로 작성했다가 클래스로 바꾸고, 나중엔 상속 개념을 도입하면서 추상화 클래스인 Shape 라는 걸 만들면서 refactoring 하는 그런...)

1. 용어

다른 언어에서는 볼 수 없었던 조금은 생소한 용어를 아래에 따로 정리했다. 사실 메세지의 경우엔 Objectvie C 코드에서 가장 특이하다고 생각했던 문법에 관련된 내용으로 이 부분만 책에서 봤는데도 Objective C 예제 코드들을 이해하기가 쉬웠다.

Message : Objective C에서 객체가 수행하는 액션.

Method dispatcher : Objective C의 코드에서 메세지가 보내지면 method dispatcher 가 메소드를 찾는다. 가장 먼저 해당 클래스에서 찾고, 없으면 상위 클래스로 올라가면서 찾는 방식을 취하며 찾아도 없으면 오류를 내보낸다.

2. Message

아래의 그림을 살펴보면,

이상한 부분이 하나 눈에 띈다.

[shape draw];

이게 뭔지 도대체 알 수 없었다. 배열도 아니고...Objective C 샘플들을 보면 대괄호로 묶인 저런 문장들이 자주 보인다. 메세지를 보낸다는 의미로 해석하면 된다고 하는데, 책 내용 그대로 보면 shape 라는 객체에 draw라는 메세지를 보낸다는 의미라고 한다. 근데 왠지 뭔가 뒤죽박죽 이상해서 내 나름대로 편하게 해석하기로 했다. shape에게 draw 라는 명령을 내리는 거라고 생각하면 훨씬 편한 듯 하다. 뭐 그게 그거일 수도 있다...ㅎㅎ

다시 정리하면 shape라는 객체가 존재하는데 그 객체는 멤버함수로 draw를 가지고 있어야 하고 그 draw를 호출하는 내용이라고 볼 수 있다. 그런데 그건 그렇다고 하더라도 또 이상한게 보인다. "id". 객체를 가리키는 포인터로 이해하면 쉽다. 난잡하게 생긴 포인터 대신 깔끔하게 id 라는 타입을 사용하는 듯 하다. 그러니까 [shape draw]의 shape가 id 타입의 변수이지만 어차피 객체를 가리키기 때문에 shape가 가리키고 있는 객체에게 draw 를 지시하는 내용.

헷갈릴 수 있으니 또 다른 예문을 보자.

첫번째 예제와 같은 프로젝트에 사용한 main 함수의 내용이다. 첫번째 예제와 비슷한 부분이 몇 군데 보인다. 일단 재미있는 부분은 shapes[0] = [Circle new]; 문장인데 처음에 다루었던 내용을 보면 같다. 단지 Circle 이라는 클래스에 new라는 메세지를 보내고 있을 뿐이다. 한마디로 Circle 객체를 새로 만들라는 의미.

두번째로, 비슷하지만 좀 이상한 부분이 보인다.

[shape[0] setBounds: rect0];

shape[0]는 객체를 가리키는 id 배열 중 하나이니 어차피 객체를 가리키는 거라 생각하면 되지만 뒷 부분의 콜론 좌우는 좀 어색하게 느껴진다. 알고보니 간단했는데, setBounds는 메세지. 그러니까 멤버함수인거고 콜론 이후의 rect0는 setBounds에 전달하고 싶은 parameter. 끝. 이상한거 없음.

3. Interface / Implementation

첫 부분에 언급했던 것처럼 Objective C에서는 interface 와 implementation이 구분하기 좋게 문법적으로 구성되어 있다. 아래는 상속개념까지 적용해서 refactoring 까지 마친 상태의 도형의 최상위 클래스 Shape이다.

클래스를 interface와 implementation으로 구분해 놓은 내용이고, @interface로 시작해 @end로 끝맺음을 하는 형태를 갖추어야 한다. Shape 옆의 콜론은 상속을 받는 의미로 해석하면 편할 듯 하고 extends 정도로 해석하면 되지 않을까? (나만의 생각) 그러니까 Shape는 NSObject (C++에서 Object 클래스 같은 최상위 클래스)를 상속받고 그 아래의 중괄호로 싼 내용은 멤버변수라고 생각하면 된다. 특이한 부분은 '-' 로 시작하는 3줄. 직관적으로 멤버함수를 의미한다는게 느껴진다. (void) 는 리턴타입을 의미하며, 위에 있는 main 함수 예문을 참고해서 보면 setFillColor 라는 method는 ShapeColor 타입의 fillColor를 인자로 받는 method 라는 걸 명시해주고 있다.

구현부인 implementation도 @로 시작해 @end로 끝맺음한다. Interface 부분과 동일한 형태로 method를 나열해 중괄호로 묶어 구현내용을 넣어두었다. 마지막에 있는 draw는 Shape를 상속받을 Circle, Rectangle 등의 sub class에서 overriding 해서 쓸 거라 빈 내용으로 두었다.

4. Inheritance

상속이다. OOP에서 가장 중요한 개념 중 하나. 역시나 예제 위주로 보는게 편하다. 아래의 그림.

Shape를 상속받는 Circle과 Rectangle 클래스의 interface와 implementation 부분이다. 대충보면 대부분 이해가 되는데 하나 이상한게 interface 안에 내용이 없다는 사실. 두 클래스 모두 Shape의 멤버변수와 멤버함수 이외에 추가할게 없는 경우엔 저렇게 빈 공간으로 두어도 무방하다. implementation 부분을 보면 Shape의 draw를 overriding 해서 쓰려고 재정의해 두었다. 두 클래스의 draw의 차이점은 NSLog에 들어가는 circle과 rect 문자열의 차이. ㅎㅎ

초반에 용어정리해 두었던 method dispatcher를 생각해보면, method dispatcher는 내가 사용한 예제에서 draw를 호출할 때 각 클래스(Circle, Rectangle)에 있는 draw를 먼저 호출하려 할거고 만약 재정의되어 있지 않다면 상위의 Shape로 올라가 draw를 찾아서 있다면 Shape의 draw를 호출하게 만든다.