Xcode를 많이 사용해 보지도 않았고, 간단한 샘플 몇가지만 만들어 본 초보 사용자라 아직 Xcode가 어떻다는 평가를 하기는 어렵습니다. 하지만 주관적인 취향이지만 Xcode 코코아의 GUI 개발 방식이 저에게는 편하게 느껴집니다.

저는 위자드에서 소스코드를 생성해 주는 방식을 선호하지 않습니다. 당장의 편리함과 속도면에서는 손해를 볼 수도 있지만 내가 사용하는 변수와 함수를 직접 타이핑함으로써 존재와 용도를 확실히 인지하고 있는 것이 소스를 이해하고 관리해 나가는데 더 많이 도움이 되기 때문입니다.

그런면에서 Xcode는 최소한의 코드를 생성해 내고 인터페이스빌더에서는 소스코드 자체에는 영향을 주지 않는 것(class 파일을 생성해 주는 일은 합니다)이 좋습니다. 이에 반해 MS의 VC++에서는 Xcode의 코코아에서 보다 훨씬 많은 파일과 코드를 자동으로 생성합니다.

또한 다이얼로그 ID, 컨트롤-변수 연결 정보, 메시지-함수 연결 정보등이 헤더나 소스파일에 기록됩니다. (MS의 개발툴중 VC만 이에 해당되는 것 같습니다) 하지만 Xcode에서는 IBAction, IBOutlet으로 변수와 함수들이 Nib의 객체들과 관련이 있는 것만 나타냅니다.

이는 OS X의 Nib 파일이 리소스, 사용자 인터페이스와 더불어 class, instance, 바인딩과 같은 객체와 연결에 대한 다양한 정보를 가지고 있지만 VC의 .rc파일에서는 객체와 연결정보 등을 가지고 있지 않고 이에 관련된 부분을 소스코드에서 관리하기 때문인 것 같습니다.

또한 코코아에서 생성하는 실행파일은 많은 부분을 컴파일하는 시점 보다 실행될 때 연결되는 동적 바인딩을 사용하고 있기 때문에 소스파일에 존재하여 같이 컴파일 되지 않아도 되기 때문인 것 같습니다.

반대로 VC++에선 rc에 있는 UI, 기타 리소스에 대한 정보와 코드의 연관관계를 소스에서 명확하게 확인할 수 있어 좋습니다. Xcode에서는 그런 정보들이 Nib파일에 들어 가고 인터페이스 빌더에서 관리하기 때문에 소스코드만 보아서는 어떤 객체와 어떻게 연결되었는지 정확히 알 수 없습니다. (하지만 잘된 명명규칙을 따랐다면 쉽게 짐작할 수는 있을 것입니다)

이전 포스팅에서 Xcode에서 소스코드에 아무런 변경없이 간단한 GUI 어플리케이션을 만드는 작업을 해보았습니다. VC++였으면 모든 연관 관계의 작업이 코드에 추가되었을 것입니다. 소스코드는 복잡해 지지만 소스코드만 보면 어떻게 연관되어 있는지 어떤 동작을 하는지 짐작할 수 있을 것 입니다. Xcode에서는 인터페이스 빌더에서 확인해야만 명확하게 알 수 있습니다. 대신 소스코드는 매우 간결합니다.

저는 소스코드가 간단하고 툴이 많은 부분 소스에 관여하는 것 보다는 소스코드가 제 취향과 입맛대로 직접 입력한 내용으로 구성되어 있다는 면에서 Xcode의 방식이 더 편하게 느껴집니다. 이 부분이 Xcode의  본래 설계의도인지 애플이 인력이 부족해 아직 신경을 쓰지 못하는지는 모르겠습니다.

실용주의 프로그래머란 책에선 '자신이 이해하지 못하는, 마법사가 만들어 준 코드는 사용하지 말라'고 합니다. 이유에 있어서 많은 부분 동의 하지만 VC++ 6.0의 클래스 위자드를 사용하지 않거나 기타 다른 툴에서 위자드나 속성창을 이용하지 않고 타이핑에 의한 코딩만 해야 한다면 그리 좋은 방법 같지는 않습니다. 일단 위자드로 생성해 놓고 올바른 위치로 재배열 하는 것이 좋지 않나 생각됩니다. 책에서도 이해하고 사용해라 정도의 의미인 것 같습니다.

툴 자체로만 놓고 본다면 VC++이 분명히 Xcode보다 편리성과 다양한 기능을 가진 더 잘 만들어진 툴입니다. 하지만 꼭 다양한 기능을 가지고 많은 부분을 자동화 해주는 툴이 모든 사람들이 개발하기에 좋다고는 할 수 없을 것 같습니다.

만약 툴이 배려하지 않은 작업을 해야 할 때나 자동으로 처리되는 부분에 있어 변경이 필요할 시에는 더 복잡한 작업을 해야하며 쓸데 없는 내용을 알아야 할 때가 있기 때문입니다. 또한 아무리 도움을 준대도 툴이 지나치게 참견하는 것을 싫어하고 제 손으로 해야만 직성이 풀리는 저같이 깝깝하게 막힌 사람도 있으니까요.

인터페이스 빌더에서 마우스로 드래그 하여 오브젝트들을 연결하는 모습과 오브젝트와 정보들을 Nib란 파일에 순간 냉동포장하여 저장하는 방식은 상당히 재밌습니다. 아마 일반적인 GUI 개발툴에서 자주 보던 방법이 아닌 다소 독특한 Xcode의 개발방법이 익숙치 않고 신선하기 때문에 단점을 묻어 두고 좋은 평가를 내리는지도 모르겠습니다. 익숙해지면 또 어떤 생각이 들지 모르겠네요.

'개발 툴' 카테고리의 다른 글

실버라이트2 둘러보기  (10) 2008.12.16
프로젝트 관리 도구 OpenProj  (2) 2008.03.21
적당히 참견하는 Xcode  (4) 2008.02.25
OS X의 파이썬  (0) 2008.02.20
Java 교육용 프로그램 Greenfoot  (0) 2007.12.23
Xcode에서 Flex - Hello World 작성  (0) 2007.12.12

이번에는 Core Data를 이용해 간단한 할일(Todo) 어플리케이션을 만들어 보겠습니다. Core Data는 객체의 연결, 저장, 불러오기등의 복잡한 데이터 관련 작업을 쉽게 관리할 수 있도록 해줍니다. 많은 부분이 자동화 되어 있어 개발자가 해야할 작업은 매우 적습니다. 실제 이번 예제에서도 모델링 툴과 인터페이스 빌더를 이용하여 객체를 생성하고 연결하는 작업만 하고 소스코드에서는 작업을 하지 않을 것입니다.

이 예제는 ADC에서 제공하는 Building a Sample Core Data Application 동영상 강좌를 참고하였습니다. 동영상으로 되어 있기 때문에 이 포스팅 보다 따라 해보기가 매우 쉬우실 것입니다. Core Data에 관한 상세한 내용은 Core Data Programming Guide 문서를 참조하시기 바랍니다.

1. 프로젝트 생성
New Project에서 Core Data Document-based Application을 선택합니다. Core Data Document-based Application은 Document와 연결된 datamodel을 생성해주고, 파일 Open/Save와 MDI를 지원합니다.

Project Name을  'MyToDoList'로 입력하고 [Finish] 버튼을 클릭합니다.

사용자 삽입 이미지
이제 Xcode에서 좌측과 같이  Models 밑에MyDocument.xcdatamodel이 생성되어 있음을 확인할 수 있습니다.


2. 데이터 모델링 툴
작업을 위해 MyDocument.xdatamodel을 더블클릭하면, 아래와 같은 데이터 모델링 툴이 오픈됩니다.
사용자 삽입 이미지

일반적인 모델링 툴과 사용방법이 유사합니다. 상단 좌측에서 객체를 생성하고 우측의 창에서 객체들의 각각의 속성을 관리합니다. 우측에 현재 'No Selection'로 표시된 영역에서 선택된 값들의 세부 속성을 설정합니다. 하단에는 생성된 객체들이 그래픽한 다이어그램으로 표시되어 쉽게 속성과 연결을 확인할 수 있습니다.

3. 오브젝트 생성
Todo 어플리케이션은 작업들의 목록과 각 작업들 아래 세부 작업들을 관리할 수 있도록 만들려고 합니다. 이를 위해 작업(Work)와 세부작업(Todo)의 두개의 객체(NSManagedObject)를 생성하겠습니다. 좌측 상단의 Entity필드가 있는 목록 하단에 위치한 +버튼을 두번 클릭하여 2개의 객체를 생성 합니다.

사용자 삽입 이미지
좌측과 같이 Entity를 Work와 Todo로 변경합니다.


4. 항목 설정
사용자 삽입 이미지
이제 각각의 객체에 항목을 추가하겠습니다. Work 객체를 선택한 상태에서 우측에 있는 Property 목록 하단의 + 버튼을 클릭합니다. 나오는 메뉴중에서 좌측과 같이 Add Attribute를 클릭합니다.

생성된 항목의 속성 창에서 Name을 'title'로 Default Value를 '새작업'으로 입력합니다.
사용자 삽입 이미지

이제 Todo를 선택하고 위와 같은 방법으로 세개의 항목을 추가하고 아래와 같이 속성을 설정합니다.
사용자 삽입 이미지

5. 연결(RelationShip) 설정

사용자 삽입 이미지
Work가 선택된 상태에서 좌측과 같이 + 버튼의 Add RelationShip을 클릭하여 새로운 연결을 생성합니다.



아래와 같이 속성에서 이름을 todo(소문자로 시작해야 합니다.)로 설정하고 Destination에서 연결될 객체를 Todo를 선택합니다.
사용자 삽입 이미지

이제 Todo를 선택하고 위와 같은 방법으로 RelationShip을 추가합니다. Destination은 Work 객체로 Inverse를 todo로 선택하여 상호 참조할 수 있도록 해줍니다.
사용자 삽입 이미지

이제 다시 Work 객체의 todo에서 아래와 같이 설정합니다. 하나의 Work에 여러개의 Todo가 연결될 수 있도록 To-Many Relationship에 체크합니다.
사용자 삽입 이미지


6. 확인
사용자 삽입 이미지
하단을 보시면 좌측과 같이 지금까지 작업한 내용을 쉽게 확인할 수 있습니다.

Work와 Todo는 연결되어 있고 Todo쪽의 화살표가 이중으로 표시되어 있습니다. 이는 Work와 Todo가 1:N으로 연결(To-Many Relationship)되어 있슴을 나타냅니다. 하나의 Work에 대하여 여러개의 Todo가 올수 있습니다.

7. ArrayController 생성

이제부터 인터페이스 빌더에서 작업을 작업을 해보겠습니다. MyDocument.nib를 더블클릭하여 인터페이스 빌더를 오픈합니다.

사용자 삽입 이미지
좌측과 같이 팔레트 윈도우에서 >> 버튼을 클릭하고 나오는 메뉴에서 Controllers를 클릭합니다.

좌측 하단의 NSArrayController를 드래그하여 인스턴스 창에 가져다 놓습니다. 한번 더 반복합니다.


사용자 삽입 이미지
가져온 컨트롤러의 이름을 위와 같이 WorkController와 TodoController로 변경합니다.

1) WorkController 설정
사용자 삽입 이미지
WorkController 속성중 Attribute에서 좌측과 같이 Mode를 Entity로 체크하고 이름을 Work로 입력합니다.
Automatically prepares content에 체크합니다.



사용자 삽입 이미지
Binding 항목 하단에 있는 Parameters의 ManagedObjectContext를 좌측과 같이 설정합니다.





2) TodoController 설정
사용자 삽입 이미지
TodoController도 EntityName만 Todo로 입력하고 위와 동일하게 설정합니다.

그외에 Todo가 Work에 종속성을 가지도록 한가지 작업을 더 합니다. Binding 항목의 Controller Content 밑의 ContentSet의 항목을 좌측과 같이 설정합니다.




8. 사용자 인터페이스
윈도우에 NSTableView 2개와 NSTextField, NSDataPicker, NSSearchField, NSButton들을 아래와 같이 배치합니다.

각각의 테이블뷰 Inspector의 Attribute에서 Columns를 1과 3으로 설정하고 각 컬럼을 더블클릭하여 '작업', '완료', '할일', '예정일'로 이름을 변경합니다.
사용자 삽입 이미지

9. Work, Todo 테이블컬럼 설정
1) Work - 작업 컬럼(NSTableColumn)
작업 컬럼을 더블클릭하여 아래와 같이 설정합니다.
사용자 삽입 이미지

2) Todo - 완료 컬럼(NSTableColumn)
Todo 컬럼들은 직접입력을 막기 위해 모두 Attribute에서 Editable의 체크를 해제합니다.  Binding에서 각각의 컬럼들을 아래와 같이 설정합니다.
사용자 삽입 이미지

3) Todo - 할일 컬럼(NSTableColumn)
사용자 삽입 이미지

4) Todo - 예정일 컬럼(NSTableColumn)
사용자 삽입 이미지

5) 작업:(NSTextField)
사용자 삽입 이미지

10. 기타 컨트롤 설정
1) 완료 예정일 :(NSDatePicker)
사용자 삽입 이미지

2) 완료:(NSButton)
사용자 삽입 이미지

3) 검색(NSSearchField)
Binding에서 Search/predicate에서 아래와 같이 설정합니다. Predicate Format:에서  key가 'memo'로 변경되었습니다.
사용자 삽입 이미지

4) 동작 버튼
이제 각각의 버튼을 컨트롤키와 함께 드래그해서 Controller에 연결해서 아래와 같이 설정합니다. 좌측의 두 버튼은 WorkController에 우측의 두 버튼은 TodoController에 연결하고, '+' 버튼은 add:에 '-' 버튼은 remove:에 연결합니다.
사용자 삽입 이미지

11. 완료
이제 모든 작업이 완료되었습니다. 빌드를하고 테스트를 해봅니다. 좌측에서 작업을 먼저 입력하시고 작업별로 할일을 등록하시면 됩니다.

사용자 삽입 이미지

사용자 삽입 이미지

마우스 클릭과 속성 설정만으로 등록, 변경, 검색, 저장, 불러오기등이 동작하는 간단한 ToDo 어플리케이션을 만들어 보았습니다. 프로젝트 파일은 아래의 아이콘을 클릭하여 다운로드 받으실 수 있습니다.

이전 포스트에 이어서 프로그램을 완성해 보겠습니다. 이전의 소스에서 아래의 청색으로 된 부분을 추가합니다.

1. AppController.h 수정

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    NSMutableString *curString;
    NSString* dataArray[3][2];
   
    IBOutlet NSTextField* displayString;
    IBOutlet NSTableView* wordList;
    IBOutlet NSMatrix* subjectMatrix;
    IBOutlet NSComboBox* adverbSelecter;
    IBOutlet NSColorWell* colorWell;
    IBOutlet NSSlider* alignSlider;
    IBOutlet NSButton* notChecker;
    IBOutlet NSProgressIndicator* progressBar;   
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn
            row:(int)row;

- (IBAction)setDisplayText:(id)sender;
- (IBAction)adverbSelecterChanged:(id)sender;
- (IBAction)alignSliderChanged:(id)sender;
- (IBAction)notCheckerChanged:(id)sender;
- (IBAction)subjectMatrixChanged:(id)sender;
- (IBAction)colorWellChanged:(id)sender;
- (IBAction)wordListChanged:(id)sender;
@end


2. 인터페이스 빌더에서 수정

변경한 소스를 저장한 후에 변경된 사항을 인터페이스 빌더에 적용하기 위해서 다시 AppController.h 파일아이콘을 드래그 하여 인터페이스 빌더의 MainMenu.nib에 놓습니다.

사용자 삽입 이미지
좌측과 같이 윈도우의 TableView중에 목적어 컬럼을 선택합니다. 속성창을 열어 Identifier 항목을 1로 설정합니다.

이와 동일하게 동사 컬럼의 Identifier는 2로 설정합니다.




사용자 삽입 이미지
AppController에서 새로 추가한 메소드인 wordListChanged르 TableView에 연결합니다.

TableView를 선택하고 Control키를 클릭한 채 마우스로 드래그 하여 AppController 인스턴스에 가져다 놓습니다. 좌측의 속성 화면에서 wordListChanged를 선택하고 연결합니다.

(연결이 어려우신 분들은 이전 포스트를 참조하세요.)


3. AppController.m 수정

#import "AppController.h"

@implementation AppController

- (id)init
{
    self = [super init];
   
    curString = [[NSMutableString alloc] init];

    /* 목적어 데이터 설정 */

    dataArray[0][0] = [[NSString alloc] initWithUTF8String:"철수를"];
    dataArray[1][0] = [[NSString alloc] initWithUTF8String:"영희를"];
    dataArray[2][0] = [[NSString alloc] initWithUTF8String:"바둑이를"];
   
    /* 동사 데이터 설정 */
    dataArray[0][1] = [[NSString alloc] initWithUTF8String:"사랑했다"];

    dataArray[1][1] = [[NSString alloc] initWithUTF8String:"싫어했다"];
    dataArray[2][1] = [[NSString alloc] initWithUTF8String:"때렸다"];
   
    return self;
}

- (void)awakeFromNib
{
}

- (void)dealloc
{
    int i, j;
   
    /* 할당된 오브젝트 해제 */
    for(i = 0; i < 3; i++)
    {
        for(j = 0; j < 2; j++)
        {
            [dataArray[i][j] dealloc];
        }
    }
   
    [curString dealloc];
    [super dealloc];
}

- (IBAction)setDisplayText:(id)sender
{
    int pos = [wordList selectedRow]; // 테이블뷰에서 현재 선택된 열
    if(pos == -1) // 없으면 실행하지 않는다
        return;
   
    /** 조건에 따른 문자열 설정 */
    if([[subjectMatrix selectedCell] tag] == 0)
        [curString setString:[NSString stringWithUTF8String:"나는 "]];
    else
        [curString setString:[NSString stringWithUTF8String:"너는 "]];
   
    [curString appendString:dataArray[pos][0]];
    [curString appendString:@" "];
    [curString appendString: [adverbSelecter stringValue]];
       
    if([notChecker state] == NSOnState)
        [curString appendString:[NSString stringWithUTF8String:" 안 "]];
   
    [curString appendString:dataArray[pos][1]];
   
    /* 색상 설정 */
    [displayString setTextColor:[colorWell color]];
   
    /* 정렬 설정 */
    int align = [alignSlider intValue];

    if(align == 0)
        [displayString setAlignment:NSLeftTextAlignment];
    else if(align == 50)
        [displayString setAlignment:NSCenterTextAlignment];
    else
        [displayString setAlignment:NSRightTextAlignment];
   
    /* 텍스트 출력 */
    [displayString setStringValue:curString];
   
    /* 진행 상태바 에니메이션 중지 */
    [progressBar stopAnimation:self];

}

- (IBAction)adverbSelecterChanged:(id)sender
{
    /* 변경시 진행 상태바 에니메이션 시작 */
    [progressBar startAnimation:self];
}

- (IBAction)alignSliderChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (IBAction)notCheckerChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (IBAction)subjectMatrixChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (IBAction)colorWellChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (IBAction)wordListChanged:(id)sender
{
    [progressBar startAnimation:self];
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
    /* 테이블 열 갯수 3 반환 */
    return 3;

}

- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    int pos;

    /** 현재 설정되어야 할 TableView의 컬럼을 반환한다. */
    if([[tableColumn identifier] characterAtIndex:0] == '1')
        pos = 0;
    else
        pos = 1;
   
    return dataArray[row][pos];
}

@end


사용자 삽입 이미지
이제 빌드를 하고 실행 하시면 좌측과 같이 어플리케이션이 실행됩니다. 각각의 옵션을 선택하고 [적용] 버튼을 클릭하면 선택된 옵션에 맞추어 상단에 텍스트가 출력됩니다.

옵션에 변동이 일어나면 하단의 바가 에니메이션되며 [적용] 버튼을 클릭하여 적용이 완료되면 진행 상태바의 에니메이션이 중지 됩니다.

전체 프로젝트 소스파일은 아래를 클릭해서 다운로드 받이스면 됩니다.








사용자 삽입 이미지
흔히 사용되는 컨트롤들의 간단한 사용법을 알아 보겠습니다.
만들려는 예제의 모습은 좌측과 같습니다. 각 컨트롤들의 설정에 따라서 상단 텍스트 필드의 내용을 변경하여 보여주는 샘플을 작성해 보겠습니다.

이 예제를 따라 해보기 위해서는 인터페이스 빌더의 사용법과  인스턴스 생성, 인스턴스와의 연결 등에 관한 기본 내용을 알고 있어야 합니다. 잘 이해가지 않는 분들은 이전 포스트를 확인해 보신 후에 다시 보시기 바랍니다.



1. AppController 생성

XCode에서 Cocoa Application으로 새로운 프로젝트를 생성합니다. 다시 New File에서 Objective-C Class를 선택한 후에 AppController란 이름으로 새로운 클래스를 생성합니다.

1) AppController.h 변경
이번 장에서 사용할 컨트롤들은 아래와 같습니다. 아래의 컨트롤들을 인터페이스 빌더에서 연결하기 위해 IBOutlet 변수들을 생성합니다.
NSTextField, NSTableView, NSMatrix, NSComboBox, NSColorWell, NSSlider, NSButton, NSProgressIndicator  

사용자의 조작으로 컨트롤의 상태가 변하는 알림 메시지를 받을 메소드를 생성합니다. 각 메소드는 아래와 같습니다.

- (IBAction)setDisplayText:(id)sender;
- (IBAction)adverbSelecterChanged:(id)sender;
- (IBAction)alignSliderChanged:(id)sender;
- (IBAction)notCheckerChanged:(id)sender;
- (IBAction)subjectMatrixChanged:(id)sender;
- (IBAction)colorWellChanged:(id)sender;

AppController.h
#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    IBOutlet NSTextField* displayString;
    IBOutlet NSTableView* wordList;
    IBOutlet NSMatrix* subjectMatrix;
    IBOutlet NSComboBox* adverbSelecter;
    IBOutlet NSColorWell* colorWell;
    IBOutlet NSSlider* alignSlider;
    IBOutlet NSButton* notChecker;
    IBOutlet NSProgressIndicator* progressBar;   
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView;
- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn
            row:(int)row;

- (IBAction)setDisplayText:(id)sender;
- (IBAction)adverbSelecterChanged:(id)sender;
- (IBAction)alignSliderChanged:(id)sender;
- (IBAction)notCheckerChanged:(id)sender;
- (IBAction)subjectMatrixChanged:(id)sender;
- (IBAction)colorWellChanged:(id)sender;

@end


2) AppController.m 변경

사용자 삽입 이미지
TableView는 좌측과 같이 테이블 형식으로 목록을 보여주는 컨트롤입니다. 각 셀에 데이터를 입력하기 위해서는 데이터를 등록하여 주는 dataSource를 지정해야 합니다.

여기서는  나중에 인터페이스 빌더에서 AppController를 dataSource로 등록하겠습니다. tableView의 dataSource가 되면 데이터를 요구하는 메시지에 응답하기 위해서 아래와 같은 두개의 메소드를 구현해야 합니다.

- (int)numberOfRowsInTableView:(NStableView *)tableView;
목록의 갯수를 반환합니다.

- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row

각 셀의 데이터를 반환합니다. 이 반환된 값들로 각 셀들이 채워집니다. 셀에 데이터를 등록하는 것은 다음 포스팅에서 구현해 보겠습니다.

메시지를 처리하는 각 메소드들은 실제 기능을 구현하기 전에 정확히 동작하는지 확인하기 위해서 NSLog를 추가합니다. 소스파일 전체는 아래와 같습니다.

AppController.m
#import "AppController.h"

@implementation AppController

- (void)awakeFromNib
{
}

- (void)dealloc
{
    [super dealloc];
}

- (IBAction)setDisplayText:(id)sender
{
    NSLog(@"Button colicked");
}

- (IBAction)adverbSelecterChanged:(id)sender
{
    NSLog(@"adverbSelecter changed");
}

- (IBAction)alignSliderChanged:(id)sender
{
    NSLog(@"alignSlider Changed");
}

- (IBAction)notCheckerChanged:(id)sender
{
    NSLog(@"notChecker changed");
}

- (IBAction)subjectMatrixChanged:(id)sender
{
    NSLog(@"subject matix");
}

- (IBAction)colorWellChanged:(id)sender
{
    NSLog(@"colorWell changed");
}

- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
    NSLog(@"tableView getRowCount");
    return 1;
}

- (id)tableView:(NSTableView *)tableView
    objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row
{
    NSLog(@"tableView getColumnValue");
    return nil;
}

@end


2. 인터페이스 빌더에서 작업
 
1) 컨트롤 배치

윈도우에 각 컨트롤들이 배치되어야 할 모습은 아래와 같습니다.

사용자 삽입 이미지

사용자 삽입 이미지
팔레트에서 마우스 포인터를 해당 컨트롤 위에 놓으면 좌측의 빨간 화살표가 가르키는 것과 같이 컨트롤의 종류를 확인할 수 있습니다.

위의 이미지를 참조하여 팔레트에서 각각의 콘트롤들을 드래그로 가지고 와서 배치 합니다.





2) 속성 설정
각각의 콘트롤들의 속성을 변경합니다.

NSTextField
사용자 삽입 이미지
텍스트필드의 옵션을 좌측과 같이 설정합니다. 사용자의 입력은 받지 않기 때문에 Editable, Enabled를 해제합니다.


NSTableView
사용자 삽입 이미지
Attributes에서 Columns를 2로 설정합니다.
NSTableView를 더블클릭하면 좌측과 같이 푸른 테두리가  나타납니다. 이 상태에서 컬럼을 더블클릭하여 입력 모드로 변경되면 이름을 변경할 수 있습니다.

각각 목적어, 동사로 변경합니다.

NSMatrix
Rows는 1 Cols는 2로 설정하고 Mode가 'Radio'로 되어 있음을 확인합니다.

NSComboBox
사용자 삽입 이미지
좌측과 같이 Visible Items를 4로 입력하고 4개의 항목을 추가합니다.

추가는 하단의 입력필드에서 이름을 입력 후에 [+] 버튼을 클릭합니다. 삭제는 아이템을 선택한 후에 [-]버튼을 클릭합니다.



NSColorWell
변경없이 기본값을 유지합니다.

NSSlider
사용자 삽입 이미지
좌측과 같이 최소값을 0.0, 최대값을 100.0, 기본값을 50.0으로 설정합니다. 세단계만 선택되게 하기 위해 Number Of Markers를 3으로  Stop on ticks marks only를 체크 합니다.


NSButton
Type이  'CheckBox'로 되어 있음을 확인하고 Title에 '부정어로 만듭니다.'로 입력합니다.

NSProgressIndicator  
MinumRange는 0.0, MaximumRange는 100.0으로 설정하고 Indeterminate를 체크 합니다.

2) AppControll 인스턴스 생성
AppController.h를 인터페이스 빌더의 MainMenu.nib 윈도우로 드래그 해 놓습니다. Classes 항목에서 AppController를 우클릭하여 Instantiate AppController를 선택하여 인스턴스를 생성합니다.

이제 아래의 연결된 모습을 참조하여 윈도우의 각 항목들을 AppController의 IBOutlet 변수와 IBAction 메소드로 연결합니다.
사용자 삽입 이미지

각 컨트롤들은 아래의 AppController의 IBAction 메소드와 연결됩니다.
  • adverbSelecterChanged <- NSComboBox
  • alignSliderChanged <- NSSlider
  • notCheckerChanged <-NSButton (Check Box)
  • subjectMatrixChanged <- NSMatrix
  • colorWellChanged <- NSColorWell
  • setDisplayText <- NSButton (적용 Button)
 
사용자 삽입 이미지
마지막으로 위에 설명한 대로 좌측과 같이  TableView의 dataSource를 AppControll로 설정합니다.








이제 컴파일 하고 실행한 후에 각각의 콘트롤의 값과 상태를 변경하여 봅니다. 그리고 RunLog 창에서 아래와 같이 메시지를 보내고 받는지 확인합니다.

잘 동작하지 않는 분들은 아래의 샘플 파일을 다운로드 받으신 후에 비교해 보시기 바랍니다.
사용자 삽입 이미지


1. 프로젝트 및 관련 파일 생성

Xcode를 실행하고 New Project를 클릭합니다. 이전 포스트에서 추가한 Flash >  FlexApplication을 선택하고 "Hello"란 이름으로 프로젝트를 생성합니다.

사용자 삽입 이미지
Hello 프로젝트를 우클릭하고 Add/NewFile 메뉴에서 액션스크립트를 위한 소스 파일인 Hello.as와 테스트를 위한 Test.html을 생성합니다.

좌측과 같이 생성되어 있음을 확인합니다.



2. Hello.mxml 편집

하나의 버튼을 추가하고 과 Hello.as 파일을 인클루드 하기위해 아래의 내용을 추가합니다.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*" layout="absolute">
    <mx:Script source="Hello.as"/>
   
    <mx:Box backgroundColor="#efefef" width="200" height="100" horizontalAlign="center" verticalAlign="middle">
    <mx:Button label="Click" click="buttonClicked()"/>
    </mx:Box>   
</mx:Application>


3. Test.html 편집

생성된 Hello.swf를 테스트 하기 위한 html 파일을 편집합니다.

<html lang="ko">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Flex Hello</title>
</head>
<body>
<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
            id="flexTest" width="200" height="100"
            codebase= "http://fpdownload.macromedia.com/get/flashplayer/current/swflash.cab">
<param name="movie" value="bin/Hello.swf" />
<param name="quality" value="high" />
<embed src="bin/Hello.swf" quality="high" bgcolor="#efefef"
                width="200" height="100" name="flex" align="middle"
                play="true"
                loop="false"
                quality="high"
                type="application/x-shockwave-flash"
                pluginspage="http://www.adobe.com/go/getflashplayer">
            </embed>
    </object>
</body>
</html>


3. Hello.as 편집

버튼이 클릭되었을 경우 호출되는 "buttonClicked" 메소드를 작성합니다. 간단히 Hello World 창을 오픈합니다.

function buttonClicked()
{
    mx.controls.Alert.show('Hello World!', 'Flex');
}     


4. 확인

사용자 삽입 이미지
파일들을 저작하고 빌드를 실행합니다. 빌드가 오류없이 완료되면, Test.html 파일을 우클릭하여 Open With Finder를 클릭합니다.

Test.html이 브라우져에서 실행됩니다.


아래와 같이 사파리에서 Test.html이 오픈됩니다.. "Click" 버튼을 누르면 우측과 같이 Hello 메시지 창이 뜨는 것을 확인할 수 있습니다.
사용자 삽입 이미지사용자 삽입 이미지

몇일전 즉흥적으로 Flex에 대한 포스트를 올렸다가, 댓글을 주신 Jason님의 블로그에서 흥미있는 내용을 발견하였습니다. Xcode에서 Flex를 개발하는 방법에 관한 링크였습니다. 이번 내용들은 그 링크가 된 사이트인 joshbuhler.com에 서 보고 따라 해 본 것입니다.


방법 1

1. Flex SDK 설치

아도비 사이트에서 free Flex SDK를 다운로드 받습니다. 다운로드 받은 폴더를 flex로 변경하고 /Developer/SDKs/ 아래로 옮겨 놓습니다.(원작자 분이 거기다 놓는게 좋답니다. 나중에 보니 이해가 가더군요.) 쓰기 권한이 없기 때문에 복사를 할 수가 없어, 터미널에서 root 계정으로 옮겨 놓았습니다. 파인더에서 아래와 같이 flex가 복사 되어 있음을 확인합니다.
(저는 flex2로 했는데 이는 나중에 두번째 방법에서 번거로움이 있으니 flex로 하시기 바랍니다.)

사용자 삽입 이미지


2. Xcode 설정

1) Project 생성

사용자 삽입 이미지
이제 Xcode를 실행하고 New Project에서 새로운 프로젝트를 생성합니다. Empty Project를 선택합니다.

New Project를 처음 본 것은 아닌데 맨 위의 Empty Project는 처음 보는 항목이었습니다.





2)  사용자빌드 환경 설정

Xcode 좌측의 Groups & Files 바로 아래에서 프로젝트명을 더블클릭하여 Info창을 열고 Genral 항목을 선택합니다. 하단에서 Cross-Devleop Using Target SDK 항목을 Other로 변경합니다. 변경 후에 확인하는 경고창이 뜨는데 Change를 클릭합니다. 그리고 아래의 Choose 버튼을 클릭하여 이전 단계에서 flex를 설치한 디렉토리를 선택합니다.

사용자 삽입 이미지


사용자 삽입 이미지
좌측과 같이 타겟을 우클릭(or control+클릭)하여 Add/New Target을 선택합니다. 선택창에서 Special Targets > External Target을 선택하고 Next를 클릭합니다.

Taget Name을 mxmlc로 하고 종료 합니다.


Targets 밑에 mxmlc가 생성된 것을 확인하시고 mxmlc를 더블클릭하여 아래와 같은 정보 창을 오픈합니다.
사용자 삽입 이미지

위와 같이 Build Tool에 /usr/bin/java 를 입력하시고, Arguments에는 -jar $(SDKROOT)/lib/mxmlc.jar -flexlib $(SDKROOT)/frameworks -verbose=true -file-specs $(PROJECT_NAME).as 와 같이 입력합니다.


3) 소스 파일 생성

눈여겨 보실점은 $(PROJECT_NAME).as로 되어 있으니, 후에 생성할 소스파일명이 프로젝트와 동일해야 합니다. 저는  testFlex로 하였으니 testFlex.as로 생성하였습니다. 소스 파일명을 변경하실려면 이 부분을 변경하셔야 할 것 같습니다.

사용자 삽입 이미지
이제 좌측과 같이 New File...을 클릭합니다. 첫번째 항목인 Empty File in Project를 선택하고 프로젝트명과 동일하게 파일명을 testFlex.as로 하고 종료 합니다.



아래와 같이 소스 파일과 타겟이 생성된 것을 확인합니다.
사용자 삽입 이미지

열심히 해 보았지만 위의 -flexlib 옵션 때문에 컴파일 시 오류가 납니다. 다시 잘 살펴 보니 글 쓴 시점이 2005년 10월 21일이고 아래와 같은 내용을 보았습니다.
사용자 삽입 이미지

빌드 오류는 그 사이 Flex SDK가 업그레이드 되면서 변경사항이 있었는 것 같습니다. 터미널 쉘상에서 해봐도 마찬가지였습니다. 일단 Xcode의 새로운 사용법을 알았다는데 만족하고 새로운 링크로 다시 갑니다.


방법 2

방법1에서와 같이 flex SDK를 다운 받고 /Developer/SDKs/flex에 flexSDK에 있어야 합니다.

1. Flex 프로젝트 템플릿 설치

원 저작자가 만드신 것 같은데 프로젝트 템플릿 파일을 다운 받습니다. 압축을 풀고 생성된 FlexApplication 디렉토리를 /Library/Application Support/Apple/Developer Tools/Project Templates /Flash로 복사합니다. 마지막에 /Flash 디렉토리는 만드셔야 합니다.

위의 폴더를 보시면  Xcode에서 프로젝트 생성 시 나오는 템플릿들과 같은 내용을 보실 수 있습니다.


2. 프로젝트 생성

사용자 삽입 이미지
이제 Xcode를 다시 실행하고 새로운 프로젝트를 만드시면 좌측과 같이 Flash 밑에 FlexApplication이라는 새로운 항목이 생긴 것을 보실 수 있습니다.

이 항목을 선택하고 새로운 프로젝트를 생성합니다. 위에서 수동으로 작업하였던 내용이 자동으로 설정되어 있으며, [프로젝트명].mxml이 생성되어 있음을 확인하실 수 있습니다.




Custom Build Command를 확인하시면 방법1의 java를 이용한 것과는 달리 다른 명령어와 인자로 되어 있습니다. java에 의존하던 것을 네이티브한 실행파일로 변경한 것인지는 잘 모르겠습니다.

사용자 삽입 이미지

위 1의 방법에서 아래의 내용으로 변경하고 mxml파일을 프로젝트에 추가해주면 컴파일이 가능할 것 같습니다. Build Tool에 보면 경로이 Flex로 자동으로 설정되어 있는데, 저는 괜히 처음에 flex2로 만들어 놓아 flex2로 변경하였습니다. 빈 프로젝트지만 컴파일은 이상이 없었습니다. 간단한 Flex 사용기는 다음으로 미룰려고 합니다.

쉽게 Xcode에서 Flex를 사용할 수 있는 방법을 알려주신 Jason님에게  감사드립니다. 사실 Josh님께도 감사를 드려야 하는데 "hi, thanks ^^"만 쓰고 튈수도 없고 해서 그냥 마음으로만 드립니다.

이전 어쩔 수없이 짧은 지식으로 플래쉬 작업을 한 적이 있습니다. 타임라인이니 레이어 같은 알수 없는 환경에 좌절하고 오로지 액션스크립트로만 작업을 하고 넘겼는데, 플래쉬를 하시는 분이 소스를 보셨으면 무슨 생각이 드셨을지 모르겠네요. 아마 회사에 엄청 불만있는 사람으로 생각했을 것 같습니다.
 
아직도 모르겠지만 플래쉬에 비해 개발자 친화적인 환경과 공짜라는 점에 무척 관심이 갑니다. 코코아는 물만 끓여 놓고  자꾸 딴데만 두리번 거리고 있네요.

방명록에 질문 해주신 분이 계셔서 일단 컴파일만 해보고 성공된 내용을 올립니다. 급하게 컴파일만 해보고 하는 방법이니 틀린 내용일 수도 있습니다. 제가 파이어폭스 플러그인에 대한 지식이 전무해서요.

저도 처음보는 환경이라 우선 모질라 개발자 센터로 가서 흩어 보았습니다. 윈도우즈와 리눅스에서 컴파일을 하는 방법을 읽어 보니 플러그인을 .dll .so등 동적 라이브러리로 만들어야 되는 것 같습니다.

C++ 예제가 있는 것 같아 맥의 Xcode에서는 New Project에서 Dynamic Library의 C++ Standard Dynamic Library를 선택하여 프로젝트를 만들었습니다.

사용자 삽입 이미지

위와 같이 gecko-sdk를 찾을 수 있도록 include/lib 패스를 지정해 줍니다. 저는 바탕화면에 있어서 위와같이 세팅했습니다.

그리고 샘플 중 weblock1.cpp 내용을 복사해 와서, 제 프로젝트의 cp(c++ 소스파일) 파일에 복사해서 넣었습니다. 컴파일은 오류없이 성공했습니다.

유닉스 makefile예제를 보니 별다른 변환없이 동적 라이브러리면 가능한 것 같은데, 저도 이부분은 잘 모르겠씁니다. 또 하나 확장자가 예제의 .so가 아니라 .dylib입니다. 자세한 것은 모르지만 아마 설치스크립트를 작성해야 하는 것 같은데, 이 부분에서 변경하면 별 문제 없지 않을까 생각됩니다.

일단 빌드가 급하신 것 같아 대충 올려 봅니다. 정확한 내용은 시간이 있을 때 찬찬히 해보고 올리겠습니다.

파이프(pipe)란 그 이름과 같이 한 프로세스의 표준 출력을 다른 프로세스의 표준입력으로 연결하여 주는 프로세스간의 통신을 위한 방법입니다. 파이프는 아래와 같이 많이 사용됩니다.

사용자 삽입 이미지

ps로 현재 프로세스를 출력하는데 결과가 파이프(|)를 통해 grep의 입력으로 전달됩니다. 그래서 출력 내은 grep으로 전달되고, 현재 프로세스중 iTerm의 문자열이 있는 프로세스만 출력해줍니다.

이번장에선 파이프를 이용하여 command line, GUI의 두 어플리케이션간에 통신을 하고, 프로젝트를 또 다른 프로젝트에 임포트 시키는 프로그램을 간단히 만들어 보겠습니다.


1. getDouble 커멘트라인 툴

먼저 커메드라인 툴을 만들어 보겠습니다.

Xcode의 New Project 메뉴를 클릭하시고, 프로젝트 타입에서 Command Line Utility/Starndard Tool 을 선택합니다. 전 getDouble이란 이름으로 프로젝트를 생성하였습니다. main.c 소스파일을 열고 아래의 내용을 추가합니다.

#include <stdio.h>

int main (int argc, const char * argv[]) {
    // insert code here...
    int num;
   
    scanf("%d", &num);
    printf("%d", num * 2);
   
    return 0;
}

보시는 바와 같이 사용자로 부터 한 수를 입력 받아 2를 곱해서 출력해주는 간단한 프로그램입니다. 테스트를 위해 빌드 후에 실행 합니다.
사용자 삽입 이미지
위와 같이 Run Log 창에서 3[return]을 입력하고, 결과는 두배인 6이 출력되었습니다. 이제 프로젝트를 닫습니다.


2. testPipe GUI 어플리케이션

다시 New Project를 클릭하고 Application/cocoa application를 선택합니다. 저는 이름을 위와 같이 testPipe로 하였습니다.

New File에서 Objective-C class를 선택하시고 AppController란 이름으로 새로운 클래스를 생성합니다. 아래의 내용을 추가합니다.

1) AppController.h 파일

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    NSTask* task;
   
    /*
사용자로 부터 숫자를 입력받는 텍스트필드 입니다. */
    IBOutlet NSTextField* inputText;
   
    /*
입력받은 수를 처리한 결과를 보여 줄 텍스트필드 입니다. */
    IBOutlet NSTextField* outputText;
}

/* 버튼이 클릭되었을 경우에 입력 받은 값을 getDouble 을 실행시켜 넘겨 줍니다. */
- (IBAction) buttonClicked:(id) sender;

/*
getDouble로 부터 받은 결과값을 outputText에 출력합니다. */
- (void) setData: (NSNotification *) noti;

@end


2) AppController.m 파일

#import "AppController.h"

@implementation AppController

- (IBAction) buttonClicked:(id) sender
{
     /* getDouble을 실행하기 위해 NSTask를 사용합니다. NSTask는 한 어플리케이션에서 다른 어플리케이션을 서브프로세스로 실행시키고 관리할 수 있습니다. 리소스 폴더에 있는 getDouble을 실행합니다.

    */
    task = [[NSTask alloc] init];
    NSBundle* aabundle = [NSBundle mainBundle];
    NSString* appPath = [aabundle pathForResource: @"getDouble" ofType: @""];
   
    [task setLaunchPath: appPath];
   
    /*  입출력 파이프를 준비합니다. 유닉스에서는 파이프도 파일로 취급하기 때문에 NSFileHandle 오브젝트로 관리합니다.
    */
    NSPipe* inPipe = [[NSPipe alloc] init];
    NSPipe* outPipe = [[NSPipe alloc] init];
   
    NSFileHandle* inputFile = [inPipe fileHandleForWriting];
    NSFileHandle* outputFile = [outPipe fileHandleForReading];
   
    [task setStandardInput:inPipe];
    [task setStandardOutput:outPipe];
   
    [inPipe release];
    [outPipe release];
   
    /* getDouble로 부터의 출력이 종료되었을 때 호출된 함수(setData)를 설정하도록 옵저버로 등록합니다.
    */

    [[NSNotificationCenter defaultCenter]
        addObserver: self
           selector: @selector(setData:)
               name: NSFileHandleReadToEndOfFileCompletionNotification
             object: outputFile];
    
    /* 백그라운드로 파일을 읽고 종료시에 
읽은 데이터를 NSDicionary 타입의 userInfo에 NSFileHandleNotificationDataItem 키로 읽은 데이터를 넘겨줍니다.
     */

    [outputFile readToEndOfFileInBackgroundAndNotify];
   
    /* getDouble을 실행합니다. */
    [task launch];
   
    /* 사용자가 입력한 값을 파이프(inputFile)를 통해 getDouble 프로세스에 넘겨 줍니다. */
    NSString* str = [inputText stringValue];
    NSData* data = [str dataUsingEncoding:NSASCIIStringEncoding];
    [inputFile writeData: data];
   
    [inputFile closeFile];

}

- (void) setData: (NSNotification *) noti
{
    /* getDouble에서 printf로 출력한 값을 텍스트필드에 출력합니다. */
    NSDictionary* dic = [noti userInfo];
    NSData* data = [dic objectForKey: NSFileHandleNotificationDataItem];
   
    NSString* str = [[NSString alloc] initWithData: data encoding:NSASCIIStringEncoding];

    [outputText setStringValue: str];
   
    [str release];
    [task release];
}

@end


3) 인터페이스 빌더에서 작업

아래의 작업이 이해가 가시지 않는 분은 이전 포스트를 참조해주세요.

사용자 삽입 이미지
위의 소스파일을 저장하고 인터페이스빌더를 실행하고 Window에 텍스트필드 2개와 버튼하나를 좌측과 같이 위치시킵니다.




Xcode의 Groups & Files에서 AppController.h을 드래그하여 열려진 MainMenu.nib의 Instances 판넬에 놓습니다. 그리고 Classes에서 AppController를 우클릭하여 Instantiate AppController를 실행하여 인스턴스를 생성합니다.

AppController를 컨트롤키와 함께 드래그하여 좌측의 텍스트 필드를 inputText 아울렛에 우측의 텍스트 필드를 outputText 아울렛에 연결합니다. 반대로 버튼 컨트롤을 컨트롤키와 함께 AppConrtoller 인스턴스로 드래그하여  buttonClicked 액션에 연결합니다.

각각의 인스펙터에서 아래와 같이 연결되었음을 확인합니다.
사용자 삽입 이미지

 
3. getDouble 프로젝트 추가

실행 또는 배포 시에는 번들의 리소스 디렉토리에 getDouble 실행 파일을복사해 놓으시면 되지만 여기서는 getDouble 프로젝트를 testPipe 프로젝트로 임포트 시켜보겠습니다.

사용자 삽입 이미지

위와 같이 getDouble 프로젝트의 디렉토리에서 getDouble.xcodeproj 파일을 드래그 하여 Xcode(testPipe) 좌측의 Groups & Files에 Resources  하위에 놓습니다.

이제 getDouble이 변경되었을 때에도 testPipe에서 반영될 수 있도록 Dependencies에 추가합니다.
사용자 삽입 이미지
Groups & Files의 Targets 서브의 testPipe를 더블클릭하여 좌측의 info 창을 오픈합니다. 좌측 하단의 [+] 버튼을 클릭하여 getDouble을 선택하시고 좌측과 같이 추가된 것은 확인합니다.



이제 모든 작업이 완료되었습니다. getDouble이 정확히 포함되었는지 아래 이미지와 같이 세개의 붉은색 화살표 부분을 확인합니다.

사용자 삽입 이미지

testPipe를 빌드 후에 터미널에서 아래와 같이 리소스 디렉토리에 getDouble 실행파일을 확인합니다.
사용자 삽입 이미지


4. 실행 및 확인

사용자 삽입 이미지

빌드 후에 testPipe를 실행합니다. 위와 같이 3을 입력한 후에 [X 2] 버튼을 클릭하면 getDouble을 실행하고 파이프를  통해 3을 전달합니다. getDouble은 scanf를 통하여 파이프로 들어온 값에 2를 곱하고 출력합니다. 이 출력값은 다시 설정된 파이프를 통해 testPipe에서 받은 후 출력합니다.

이번 장에선 간단히 파이프에 대해서 알아 보았습니다. 파이프는 소켓처럼 네트워크를 통해서 프로세스간에 통신을 할 수는 없지만 로컬에서 사용할 수 있는 간단한 방법입니다. 직접 구현을 하지 않고도 존재하는 실행파일 또는 프로세스에서 간편한게 정보를 가져오거나 가공할 수 있는 편리한 방법을 제공합니다.

유닉스에서 간단하고 직관적으로 pipe를 사용하던 방법과 비교해 보면 cocoa의 특징을 잘 보여주는 것 같습니다.

코아에서 MySQL C Library를 이용하는 예입니다. wrapper 클래스를 작성하여 MySQL
서버에 접속해서 데이터를 가져오는 간단한 샘플 코드를 작성해 보겠습니다.

OS X에서 MySQL 설치 및 설정은 이전 포스트를 참고해 하세요.


1. MySQL에서 작업

1) MySQL 서버 확인
테스트를 위하여 테이블을 생성하고 데이터를 넣어 보겠습니다. 우선 시스템 환경설정의 MySQL 항목에서 아래와 같이 서버가 기동중인지 확인 합니다. 서버가 실행되지 않고 있으면 Start 버튼을 클릭하여 아래와 같이 실행 상태로 만듭니다.
사용자 삽입 이미지

2) 테이블 생성 및 데이터 입력
그 다음 아래와 같이 터미널에서  MySQL에 로그인 후에 member 테이블을 생성하고 데이터를 넣습니다. 저는 이전에 test/1111로 계정을 만들고 cocoadev란 데이터베이스를 만들어 두었기에 아래와 같이 접속하였습니다.
사용자 삽입 이미지

id, name 두개의 필드를 가진 member란 테이블을 생성하고, 테스트를 위해 1, cocoa 값으로 데이터를 입력합니다.

2. 소스코드 작성
Xcode를 실행하고 프로젝트를 생성합니다. 저는 최대한 간단하게 만들기 위해 프로젝트 타입을 Command Line Utility의 Foundation Tool로 선택하고 프로젝트를 생성하였습니다.

Wrapper 클래스 작성을 위하여 새 파일에서 Objective C class 타입으로 MySqlDB 클랙스를 생성합니다. 이제 MySqlDB.h 파일과 MySqlDB.m 파일을 작성합니다.

테스트를 위하여 연결 및 쿼리등 필요한 메소드만 작성해 보겠습니다. mysql.h 파일을 참고 하시면 MySQL 라이브러리에서 제공하는 많은 함수들을 확인하실 수 있습니다.

1) MySqlDB.h 편집
#import <Cocoa/Cocoa.h>
#import "mysql.h"

@interface MySqlDB : NSObject {

    bool isConnected;
    int rowCount;
   
    MYSQL mySQL;
    MYSQL *pMySQL;
   
    MYSQL_RES* pRes;
    MYSQL_ROW Rows;
}

-(id) initWithServer: (const char*) host
             loginID: (const char*) userid
         loginPasswd: (const char*) passwd
               setDB: (const char*) database;

-(bool) connect : (const char*) host
         loginID: (const char*) userid
     loginPasswd: (const char*) passwd
           setDB: (const char*) database;

-(bool) query :(const char*) str;
-(bool) getRows;
-(bool) getStringData: (int)idx toBuffer: (char *) buff;

-(bool) isConnected;
-(int) rowCount;

@end

2) MySqlDB.m 파일 편집
#import "MySqlDB.h"


@implementation MySqlDB

-(id) initWithServer: (const char*) host
             loginID: (const char*) userid
         loginPasswd: (const char*) passwd
               setDB: (const char*) database
{   
    self = [super init];
   
    [self connect: host
          loginID: userid
      loginPasswd: passwd
            setDB: database];
               
    return self;
}

- (void)dealloc
{
    if(pMySQL)
        mysql_close(pMySQL);
   
    [super dealloc];
}

-(bool) connect : (const char*) host
         loginID: (const char*) userid
     loginPasswd: (const char*) passwd
           setDB: (const char*) database
{
    isConnected = FALSE;
   
    pMySQL = mysql_init(&mySQL);
    if(pMySQL == NULL)
    {
        return FALSE;
    }
   
    pMySQL = mysql_real_connect(&mySQL, host, userid, passwd, database, 0, 0, 0);
    if(pMySQL == NULL)
    {
        return FALSE;
    }
   
    isConnected = TRUE;
   
    return TRUE;
}

-(bool) query: (const char*) str
{
    if(!pMySQL)
        return FALSE;
   
    int ret = mysql_query(pMySQL, str);
    if(ret != 0)
    {
        return FALSE;
    }
   
    pRes = mysql_store_result(pMySQL);
    rowCount = mysql_num_rows(pRes);
   
    NSLog(@"rows:%d", rowCount);
    return TRUE;
}

-(bool) getStringData: (int)idx toBuffer: (char *) buff;
{
    if(Rows[idx] == NULL)
        return FALSE;
   
    strcpy(buff, Rows[idx]);
    return TRUE;
}

-(bool) getRows
{
    Rows = mysql_fetch_row(pRes);
   
    if(Rows == NULL)
        return FALSE;

    return TRUE;
}

-(bool) isConnected
{
    return isConnected;
}

-(int) rowCount
{
    return rowCount;
}

@end

3) MyTest.m 편집
이제 테스트를 위하여 위에 작성된 클래스를 사용하여 MySQL서버에 접속하여 데이터를 가져오도록  MyTest.m 파일에 소스를 추가해 보겠습니다. 연결시 로그인 정보와 데이터 베이스, 쿼리 내용은 자신의 환경에 맞게 변경해 줍니다.

#import <Foundation/Foundation.h>
#import "MySqlDB.h"

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    // insert code here...
    MySqlDB* myDB = [[MySqlDB alloc] initWithServer:"localhost"
                                            loginID:"test"
                                        loginPasswd:"1111"
                                              setDB:"cocoadev"];
   
    if([myDB isConnected] == true)
    {
        if([myDB query :"SELECT * FROM member"])
        {
            char buff1[128];
            char buff2[128];
           
            [myDB getRows];
            [myDB getStringData: 0 toBuffer: buff1];
            [myDB getStringData: 1 toBuffer: buff2];
           
            NSLog(@"DATA: id=%s, name=%s", buff1, buff2);
        }
        else
            NSLog(@"Fail to query");
    }
    else
        NSLog(@"Fail to connect.");
   
    [myDB release]
    [pool release];
    return 0;
}


3. 빌드옵션 설정 및 라이브러리 등록

MySQL 라이브러리를 사용하기 위해서는 링크시에 라이브러리를 추가하도록 등록하고, 헤더파일을 인크루드하기 위해 위치를 지정해 주어야 합니다.

GCC 옵션에서 -I(인클루드 패스 지정), -L(라이브러리 패스 지정), -l(라이브러리 추가) 옵션을 생각하시면 됩니다.

1) include 패스 설정
빌드시 헤더파일을 찾을 수 있도록 링크를 설정합니다. 프로젝트 정보창을 오픈합니다. Build 항목에서 "User Header Search Path"에 mysql의 include 패스를 입력합니다. 대부분 /usr/local/mysql에 설치되는데 다른 곳에 설치하신 분들은 그 곳의 패스를 입력합니다.
사용자 삽입 이미지

2. libmysqlclient.a 라이브러리 링크
사용자 삽입 이미지
파인더에서 해당 디렉토리에 접근하기 힘들기 때문에 터미널에서
 > open /usr/local/mysql/lib 로 파인더를 오픈합니다.

libmysqlclient.a 파일을 드래그 하여 좌측과 같이 Xcode의 Frameworks 그룹으로 가지고 옵니다.




이제 모든 준비가 완료되었습니다. 빌드 후 실행하여 아래와 같은 결과를 확인합니다.
사용자 삽입 이미지

코코아에서 MySQL 라이브러리를 사용하여 MySQL에 연결하는 방법을 간단히 알아 보았습니다. PostgreSQL이나 다른  C 라이브러리들도 위와 같은 방법으로 코코아에서 사용하실 수 있습니다.

티스토리 블로그 알리미를 작성한 적이 있는데, 설치형 블로그들은 위와 같이 DB에 직접 쿼리하는 방법으로 더욱 쉽고, 다용하고, 정확한 정보를 가져오는 툴을 작성할 수 있을 것 같습니다.

'Xcode 2 > Tip' 카테고리의 다른 글

Xcode에서 파이어폭스 플러그인 컴파일 하기  (0) 2007.12.04
pipe를 이용한 간단한 프로세스간의 통신  (6) 2007.11.28
Cocoa에서 MySQL 라이브러리 사용하기  (6) 2007.11.13
Xcode에 Subversion 적용하기  (6) 2007.11.10
Xcode 단축키  (2) 2007.10.26
#pragma mark  (2) 2007.08.04

프로젝트 진행 시에 다수 작업, 디버깅, 백업의 의미로 버젼관리는 매우 중요합니다. Xcode에서는  CVS, Subversion, Perforce, 3종류의 SCM(Software Configuration Management) 시스템을 지원합니다.

CVS
오픈소스로 유닉스/리눅스 계열에서 가장 오랫동안 많이 사용하는 버젼관리 시스템입니다. OS X에는 기본으로 설치가 되어있어 바로 사용이 가능합니다.

Subversion
CVS를 대체하기 위해서 나온 버젼관리 시스템으로, CVS에 비해 많은 장점을 가지고 있습니다. 여기서는 Subversion을 이용해 보겠습니다.

Perforce
Perforce Software에서 제공하는 상용 버젼관리 툴입니다.

SVS와 Subversion에 관한 자세한 사항은 아래의 KLDP Wiki를 확인하시고, 여기서는 Xcode에서 Subversion을 사용하는 방법에 대해서 간단히 알아 보겠습니다.


1. Subversion 다운로드/설치

사용자 삽입 이미지
이곳
을 클릭하시고 맥 OS X용 Subversion 설치파일(dmg)을 다운로드 받습니다.

현재 버젼은 1.4.4로 설치 프로그램을 실행하시면 간단히 설치하실 수 있습니다.

(소스코드설치를 원하시면 Subversion 사이트에서 다운로드 받으실 수 있습니다.)

설치가 완료되면 /usr/local/bin/에 실행파일들이 위치합니다. .bash_profile의 PATH에 /usr/local/bin을 추가하시면 터미널에서 사용이 편리합니다.


2. 프로젝트에 Subversion 적용

저는 Test란 프로젝트에 적용해 보겠습니다. 프로젝트명, 위치등은 각자 환경에 맞게 변경하여 작업하시면 됩니다. 우선 아래와 같이 저장소 디렉토리 svn_test를 생성합니다.

>/usr/local/bin/svnadmin create ~/svn_test


프로젝트를 가져오기 위하여 아래와 같이 tmp라는 임시폴더와 그 아래 trunk, branches, tags 디렉토리를 각각 생성합니다. 그 후에 cp 명령으로 적용시킬 프로젝트를 임시 디렉토리로 복사합니다.  [프로젝트 디렉토리]는 가져 올 프로젝트가 위치한 경로 입니다.

> mkdir tmp
> mkdir tmp/Test
> cd tmp/Test
> mkdir trunk
> mkdir branches
> mkdir tags
> cp -r [프로젝트 디렉토리] ~/tmp/Test/trunk


이제 svn_test 저장소로 프로젝트를 저장합니다. [계정아이디]는 현재 계정 아이디를 입력하시면 됩니다. 아래의 이미지를 클릭하시면 확대하여 확인하실 수 있습니다.

>/usr/local/bin/svn import ~/tmp/Test file:///Users/[계정아이디]/svn_test --message 'Start...'

사용자 삽입 이미지


그 후에 작업할 디렉토리로 프로젝트를 가지고 옵니다. [프로젝트 디렉토리]는 Test 프로젝트를 저장소로 부터 가져 올 디렉토리이며 전 ~/Projects란 디렉토리로 설정하였습니다. 위와 같이 아래의 이미지를 클릭하여 보시면 이해가 빠르실 겁니다.

> cd [프로젝트 디렉토리]
> /usr/local/bin/svn checkout file:///Users/[계정아이디]/svn_test Test

사용자 삽입 이미지

임시 폴더를 아래와 같이 삭제하고,  확인을 위하여 Xcode를 실행합니다.

> cd ~/tmp/
> rm -fR Test/


3. Xcode 설정 및 확인

사용자 삽입 이미지

이제 Xcode의 Open메뉴에서 터미널에서 설정하였던 디렉토리의 trunk/[프로젝트명]에서 프로젝트 파일을 가지고 옵니다. 저는 Projects/Test/trunk/Test로 되어 있습니다.

프로젝트 정보창을 열어 Genral  메뉴 하단의 SCM 설정을 아래와 같이 Subversion으로 선택하고 Enable SCM을 체크합니다.
사용자 삽입 이미지

이제 소스코드를 수정하여 봅니다. 확인을 위하여 아래와 같이 기존 소스에 "NSLog..." 라인을 추가하였습니다.
사용자 삽입 이미지

이제 Xcode의 SCM 메뉴에서 Compare With Revision... 을 실행합니다. 비교할 버젼을 선택한 후 [Compare] 버튼을 클릭하시면 이전 버젼과 현재 파일의 차이점을 아래와 같이 확인하실 수 있습니다.
사용자 삽입 이미지

이제 SCM 메뉴의 Commit Changes... 을 클릭하여 현재 상태를 새로 저장합니다. 변경에 대한 간단한 메시지를 작성한 후 [commit] 버튼을 클릭합니다. Get SCM Info를 클릭하시면 아래와 같이 Test.m 파일에 관한 버젼 히스토리를 확인할 수 있습니다.
사용자 삽입 이미지

'Xcode 2 > Tip' 카테고리의 다른 글

Xcode에서 파이어폭스 플러그인 컴파일 하기  (0) 2007.12.04
pipe를 이용한 간단한 프로세스간의 통신  (6) 2007.11.28
Cocoa에서 MySQL 라이브러리 사용하기  (6) 2007.11.13
Xcode에 Subversion 적용하기  (6) 2007.11.10
Xcode 단축키  (2) 2007.10.26
#pragma mark  (2) 2007.08.04

Xcode 2/Tip 2007.10.26 15:28
1. Xcode

1. 프로젝트 & 파일

* 새 프로젝트 [shift][command] N
새로운 프로젝트를 생성한다.
* 프로젝트 닫기 [control][command] W
현재 프로젝트를 종료한다.
* 프로젝트 추가 [option][command] A
프로젝트에 파일을 추가 한다.
* 새 파일 [command] N
새로운 파일을 작성한다.
* 파일열기 [command] O
파일을 오픈한다.
* 파일 저장 [command] S
현재 파일을 저장한다.
* 모든 파일 저장 [option][command] S
프로젝트의 모든 변경된 파일을 저장한다.


2. Xcode

* 새 그룹 생성 [option][command]  N
좌측의 Groups & Fiels에서 새로운 그룹을 생성한다.
* 파일그룹 지정 [option][command] G
선택된 파일들을 새로운 그룹으로 만든다.
* 파일그룹 지정 [option][command][shift]G
그룹을 해제한다.
* 에디터 전체 보기 [shift] [command] E
Group & Files 윈도우를 제외하고 에디터를 전체 보기로 만든다.
* 에디터 전체 보기 [shift] [option] [command] E
Group & Files 윈도우도 감추고 에디터를 전체 보기로 만든다.
* 클래스 브라우져 [shift] [command] C
프레임워크 또는 사용자 정의 클래스의 메소드, 소스를 보여 준다.
* target 속성창 [option][command] E
빌드 및 어플리케이션 옵션을 설정한다.
* 실행 속성창 [option][command] X
실행파일 패스, 인자, 디버깅 정보를 설정한다.
* 핼프 [shift] [command] /
Xcode 핼프 윈도우를 연다.


3. 빌드

* 빌드 [command] B
프로젝트를 빌드 한다.
* 빌드 후 실행 [command] R
빌드를 완료한 후, 프로그램을 실행한다.
* 빌드 후 디버그 [command] Y
빌드를 완료한 후, 디버그를 실행한다.
* 빌드 결과 보기 [shift][command] B
빌드 결과창을 보여준다.
* clean [shift][command] K
소스 파일의 날짜를 현재 시간으로 변경하여, 다시 컴파일 될 수 있도록 한다.


4. 디버깅

* 다음 경고/오류 [command] =
소스파일에서 경고나 오류가 발생한 다음 위치로이동한다.
* 이전 경고/오류 [shift][command] =
소스파일에서 경고나 오류가 발생한 이전 위치로 이동한다.
* 디버거 [shift][command] Y
디버거 윈도우로 이동한다.
* 디버그 실행 [option][command] Y
디버그를 시작한다.
* 브레이크포인트 보기 [option][command] B
브레이크포인트가 설정된 위치를 모두 보여 준다.
* 브레이크포인트 [command] \
현재 위치에 브레이크포인트를 설정/해제 한다.
* step into [shift][command] I
한라인씩 실행하며 함수일 경우 내부로 진입한다.
* step over [shift][command] O
한라인씩 실행하며 함수일 경우 건너 뛴다.
* step out [shift][command] T
함수를 나가 호출한 위치 다음으로 이동한다.
* 로그창 [shift][command] R
실행로그창을 보여 둔다.


5. 에디터

* 잘라내기 [command] X
현재 선택된 영역을 잘라낸다.
* 복사 [command] C
현재 선택된 영역을 복사낸다.
* 붙여넣기 [command] V
현재 클립보드에 있는 내용을 붙여 넣는다.
* undo [command] Z
실행을 취소한다.
* redo [shift] [command] Z
undo를 취소한다.
* 전체선택 [command] A
파일 전체를 선택한다.
* 자동완성 보기 [esc]
자동으로 입력될 내용(함수, 변수)들을 보여 준다.
* 다음 제안 [control] .
자동완성 목록중에 다음 목록을 보여 준다.
* 이전/다음 파일 [option] [command] 왼쪽 방향키
여러 파일이 열려있을 경우에 다음 소스 파일을 보여 준다.
* 이전/다음 파일 [option] [command] 오른쪽 방향키
여러 파일이 열려있을 경우에 이전 소스 파일을 보여 준다.
* 헤더/소스 파일 보기 [option] [command] 위쪽 방향키
소스파일일 경우에는 헤더 파일을 헤더파일일 경우에는 소스 파일을 보여 준다.
* 우측으로 들여 쓰기 [command] ]
현재 또는 선택된 영역의 내용을 오른쪽으로 한칸 들여 쓴다.
* 좌측으로 들여 쓰기 [command] [
현재 또는 선택된 영역의 내용을 왼쪽으로 한칸 들여 쓴다.
* 찾기 [command] F
현재 에디터에서 문자를 찾거나 대치한다.
* 전체 찾기 [shift][command] F
모든 프로젝트 또는 열린 파일에서 문자를 찾거나 대치한다.
* 다음 문자 [command] G
다음 문자를 찾는다.
* 이전 문자 [shift][command] G
이전 문자를 찾는다.
* 찾기 문자열에 복사 [command] E
현재 선택된 문자를 찾을 문자열 에디터에 복사한다.
* 대치 문자열에 복사 [command] J
현재 선택된 문자를 대치할 문자열 에디터에 복사한다.
* 라인 가기 [command] L
지정한 라인으로 바로 간다.
* 북마크에 추가 [command] D
현재 라인을 북마크에 추가한다.


2. 인터페이스 빌더

1) Window

* 인스펙터 [shift][command] I
오브젝트의 속성창을 연다.
* attributes [command] 1
인스펙터의 attributes 항목을 연다.
* connections [command] 2
인스펙터의 connections 항목을 연다.
* size [command] 3
인스펙터의 size 항목을 연다.
* bindings [command] 4
인스펙터의 bindings 항목을 연다.
* 팔레트 [command] /
팔레트 윈도우를 보여준다.
* 테스트 [command] R
윈도우를 실행한다.
* 테스트 종료 [command] Q
윈도우를 종료하고 인터페이스 빌더로 돌아 온다.
* 정렬 판넬 [shift][command] A
윈도우 상의 오브젝트들을 정렬할 수 있는 판넬을 연다.
* 레이아웃 사각형 [command] L
오브젝트들의 레이아웃을 나타내는 사각형을 보여주거나 감춘다.
* 레이아웃 검사 [shift][command] V
 레이아웃 검사창은 보여준다. 오브젝트들의 겹침, 잘림등을 체크해준다.


2)  Classes

* subclass [option][command] S
Classes 판넬에서 현재 선택된 클래스의 서브클래스를 생성한다.
* Action 추가 [option][command] A
Classes 판넬에서 현재 선택된 클래스에 Action을 추가한다.
* Outlets 추가 [option][command] O
Classes 판넬에서 현재 선택된 클래스에 Outlet을 추가한다.
* Outlets 추가 [option][command] F
Classes 판넬에서 현재 선택된 클래스의 소스파일을 생성한다.

'Xcode 2 > Tip' 카테고리의 다른 글

Xcode에서 파이어폭스 플러그인 컴파일 하기  (0) 2007.12.04
pipe를 이용한 간단한 프로세스간의 통신  (6) 2007.11.28
Cocoa에서 MySQL 라이브러리 사용하기  (6) 2007.11.13
Xcode에 Subversion 적용하기  (6) 2007.11.10
Xcode 단축키  (2) 2007.10.26
#pragma mark  (2) 2007.08.04

사용자 삽입 이미지
이번 장에서는 적기들을 출력하고 동작하게 하는 작업을 해보겠습니다.

적기는 좌측과 같이 세 종류로 상단에 위치해 있으며 인베이더 게임과 같이 좌우로 움직이며 아래로 내려 옵니다.

마지막 적기가 화면에서 사라지면 다음 스테이지로 넘어가며, 스테이지가 진행될 수록 적기의 움직임이 빨라 집니다.




1.8.5 소스 파일 추가

1) global.h

게임에서 사용되는 여러 속성들의 공유를 위해 global.h 헤더파일을 작성합니다. XCode의 메뉴에서 File/New File을 클릭한 후, BSD/Header File을 선택하고 [Next] 버튼을 클릭합니다. 파일명을 global.h로 입력하고  [Finish] 버튼을 클릭합니다.

이제 에디터에서 global.h를 열어서 아래와 같이 내용을 입력합니다.
#define SCREEN_WIDTH        300        /* 화면 너비 */
#define SCREEN_HEIGHT       320        /* 화면 길이 */

#define HERO_SPEED          3       /* 우주선 속도 */
#define MISSILE_SPEED       4       /* 총알발사 속도 */

#define MAX_ENEMY1            5        /* 적기1 갯수 */
#define MAX_ENEMY2            3        /* 적기2 갯수 */
#define MAX_ENEMY3            1        /* 적기3 갯수 */

#define ENEMY_DOWN            20        /* 적기 하강 픽셀 */

이전 StageView.m에 있던 SCREEN_WIDTH및 위와 중복되는 define된 부분 (검은색)을StageView.m에서 삭제 합니다.  그리고 StageView.m 상단에 아래와 같이 global.h를 임포트 합니다.
 
#import "global.h"
#import "StageView.h"


2) Enemy 오브젝트 추가

최대한 간단하게 만들기 위해 class 추가 없이 StageView.m에서 다 해결 할라고 했는데, 적기 때문에 너무 복잡해 질 것 같아 Enemy 클래스를 추가하기로 하였습니다.
 
XCode의 메뉴에서 File/New File을 클릭한 후, Cocoa/Objective-C class를 선택하고 [Next] 버튼을 클릭합니다. 파일명을 Enemy.m으로 입력하고  [Finish] 버튼을 클릭합니다.

설명은 소스에 간단한 주석으로 대치 합니다.

Enemy.h
#import <Cocoa/Cocoa.h>

@interface Enemy : NSObject {
    int type;
    int energy;
    int state;
   
    NSImage* image;
    NSPoint position;
}

- (id)initWithImage:(NSImage*)img
               type:(int)t;

- (int)state;
- (void)setState:(int)s;
- (void)setPosition:(NSPoint)pos;

- (void)down;
- (BOOL)moveAndDisplay:(int)dir;

@end
type과 energy는 아직 사용하지 않습니다.

Enemy.m
#import "global.h"
#import "Enemy.h"

@implementation Enemy

/** 적기 타입과 이미지를 설정 */
- (id)initWithImage:(NSImage*)img
           type:(int)t
{
    [super init];
   
    type = t;
    image = img;

    return self;
}

- (void)setPosition:(NSPoint)pos
{
    position = pos;
}

- (int)state
{
    return state;
}

- (void)setState:(int)s
{
    state = s;
}

/** 적기를 한단계 아래로 옮기고, 화면에 안 나올 경우에는 상태를 0으로 변경한다. */
- (void)down
{
    position.y -= ENEMY_DOWN;
    if(position.y + [image size].height < 0)
        state = 0;
}

- (BOOL)moveAndDisplay:(int)dir
{
    BOOL isChange = false;
    NSRect drawRect;
    NSRect imgRect;
       
    drawRect.size = [image size];
   
    /** 적기를 이동하고 좌우 화면의 경계를 넘었을 경우에는 아래로 이동하도록 isChange를 1로 세팅 해서 반환. */
    position.x += dir;
    if(position.x < 0)
    {
        isChange = true;
    }   
    if(position.x >= SCREEN_WIDTH - drawRect.size.width)
    {
        isChange = true;
    }
   
    /** 적기를 출력한다. */
    drawRect.origin.x = position.x;
    drawRect.origin.y = position.y;
   
    imgRect.origin = NSZeroPoint;
    imgRect.size = [image size];
   
    [image drawInRect:drawRect
                       fromRect:imgRect
                      operation:NSCompositeSourceOver
                       fraction:1.0];   
   
    return isChange;
}

@end


1.8.6 StageView 변경
다음은 스테이지를 설정하고, 적기를 출력 하기 위해 Stageview 클래스를 변경해 보겠습니다. 스테이지가 증가할 수록 적기의 이동속도가 빨라 집니다.

StageView.h
/* StageView */

#import <Cocoa/Cocoa.h>

@class Enemy;

@interface StageView : NSView
{
    NSTimer *timer;
    NSImage *backgroundImage;
   
    NSRect heroRect;
    NSImage *heroImage;

    BOOL isFire;
    NSRect missileRect;
    NSImage *missileImage;
   
    int curStage;
    int enemyDir;

    NSImage *enemyImage1;
    NSImage *enemyImage2;
    NSImage *enemyImage3;
   
    Enemy *Enemy1[MAX_ENEMY1];
    Enemy *Enemy2[MAX_ENEMY2];
    Enemy *Enemy3[MAX_ENEMY3];
}

- (void)setStage;
- (void)processGame;
- (void)fireMissile;
- (void)keyDown:(NSEvent *)event;

@end

StageView.m
#import "global.h"
#import "StageView.h"
#import "Enemy.h"

@implementation StageView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        /* 배경 이미지 로드 */
        NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
        backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 우주선 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"hero" ofType:@"png"];
        heroImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        heroRect.size = [heroImage size];
        heroRect.origin = NSMakePoint((SCREEN_WIDTH - heroRect.size.width)/2, 0);
       
        /* 미사일 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"missile" ofType:@"png"];
        missileImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        missileRect.size = [missileImage size];
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
       
        /* 적기1 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy1" ofType:@"png"];
        enemyImage1 = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 적기2 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy2" ofType:@"png"];
        enemyImage2 = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 적기3 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy3" ofType:@"png"];
        enemyImage3 = [[NSImage alloc] initWithContentsOfFile:imageName];
               
        int i;
       
        /** 적기 오브젝트 생성 */
        for(i = 0; i < MAX_ENEMY1; i++)
        {
            Enemy1[i] = [[Enemy alloc] initWithImage:enemyImage1
                                                type:1];
        }
        for(i = 0; i < MAX_ENEMY2; i++)
        {
            Enemy2[i] = [[Enemy alloc] initWithImage:enemyImage2
                                                type:2];
        }
        for(i = 0; i < MAX_ENEMY3; i++)
        {
            Enemy3[i] = [[Enemy alloc] initWithImage:enemyImage3
                                                type:3];
        }
       
        /** 1 스테이지 설정 */
        curStage = 0;
        [self setStage];
       
        /* 30프레임으로 타이머 설정 */
        timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];       
    }
    return self;
}

/** 할당된 메모리 반환 */
- (void) dealloc
{
    int i;
   
    [backgroundImage release];
    [heroImage release];
    [missileImage release];
   
    [enemyImage1 release];
    [enemyImage2 release];
    [enemyImage3 release];

    for(i = 0; i < MAX_ENEMY1; i++)
    {
        [Enemy1[i] release];
    }
   
    for(i = 0; i < MAX_ENEMY2; i++)
    {
        [Enemy2[i] release];
    }
   
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        [Enemy3[i] release];
    }
   
    [super dealloc];
}

- (void) setStage
{
    int i;
    NSPoint pos;
   
    /* 현재 스테이지를 1 증가 시킨다. */
    curStage++;
       
    /* 적기의 좌/우 움직이는 속도와 방향 설정. */
    enemyDir = curStage;

    isFire = NO;
   
    /** 적기들의 초기 위치와 상태를 설정한다. */
    pos.x = 45;
    pos.y = 190;
    for(i = 0; i < MAX_ENEMY1; i++)
    {
        [Enemy1[i] setPosition:pos];
        [Enemy1[i] setState:1];
   
        pos.x += 45;
    }

    pos.x = 78;
    pos.y = 225;
    for(i = 0; i < MAX_ENEMY2; i++)
    {
        [Enemy2[i] setPosition:pos];
        [Enemy2[i] setState:1];
   
        pos.x += 55;
    }

    pos.x = 122;
    pos.y = 265;
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        [Enemy3[i] setPosition:pos];
        [Enemy3[i] setState:1];
   
        pos.x += 80;
    }
}

/** 키입력을 받기 위한 설정 */
- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)keyDown:(NSEvent *)event
{
    int keyCode;
   
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];
       
    /* 좌측으로 이동 */
    if(keyCode == 123)
    {
        heroRect.origin.x -= HERO_SPEED;
        if(heroRect.origin.x < 0)
            heroRect.origin.x = 0;
    }
   
    /* 우측으로 이동 */
    if(keyCode == 124)
    {   
        heroRect.origin.x += HERO_SPEED;
       
        if(heroRect.origin.x >= SCREEN_WIDTH - heroRect.size.width)
            heroRect.origin.x = SCREEN_WIDTH - heroRect.size.width;
    }
   
    /* 발사(스페이스) */
    if(keyCode == 49)
    {
        [self fireMissile];
    }
}

- (void)fireMissile
{
    if(isFire == NO)
    {
        /* 미사일이 처음 발사되었을 경우에 초기위치를 우주선 좌표로 설정 */
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
    }
    isFire = YES;
}

- (void)processGame
{
    if(isFire == YES)
    {
        /* 미사일이 발사중이면 y좌표를 이동 */
        missileRect.origin.y += MISSILE_SPEED;
        if(missileRect.origin.y >= SCREEN_HEIGHT)
        {
            /* 화면상단에 위치했을 경우에는 미사일을 출력하지 않는다 */
            isFire = NO;
        }
    }
   
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)rect
{
    NSRect imgRect;
    NSRect drawRect;
   
    /* 배경 이미지 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [backgroundImage size];
   
    drawRect = [self bounds];
   
    [backgroundImage drawInRect:drawRect
                       fromRect:imgRect
                      operation:NSCompositeSourceOver
                       fraction:1.0];
   
    /* 우주선 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [heroImage size];
   
    [heroImage drawInRect:heroRect
                 fromRect:imgRect
                operation:NSCompositeSourceOver
                 fraction:1.0];   
   
   
    int i;
    int isChange = 0;
    int enemyCount = 0;
   
    /* 적기들을 출력하고 좌우 경계선을 넘어간 적기가 있을 경우를 체크한다. */
    for(i = 0; i < MAX_ENEMY1; i++)
    {
        if([Enemy1[i] state])
        {
            enemyCount++;
            isChange |= [Enemy1[i] moveAndDisplay:enemyDir];
        }   
    }

    for(i = 0; i < MAX_ENEMY2; i++)
    {
        if([Enemy2[i] state])
        {
            enemyCount++;
            isChange |= [Enemy2[i] moveAndDisplay:enemyDir];
        }   
    }
   
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        if([Enemy3[i] state])
        {
            enemyCount++;
            isChange |= [Enemy3[i] moveAndDisplay:enemyDir];
        }
    }
   
    /** 남은 적기가 없을 경우에는 다음 스테이지로 넘어 간다. */
    if(enemyCount == 0)
    {
        [self setStage];
        return;   
    }
       
    if(isFire == YES)
    {
        /* 미사일 출력 */
        imgRect.origin = NSZeroPoint;
        imgRect.size = [missileImage size];
       
        [missileImage drawInRect:missileRect
                        fromRect:imgRect
                       operation:NSCompositeSourceOver
                        fraction:1.0];   
    }
   
    /** 적기가 좌우측의 경계를 넘었을 경우에는 아래로 이동한다. */
    if(isChange)
    {
        enemyDir *= -1;
        for(i = 0; i < MAX_ENEMY1; i++)
        {
            if([Enemy1[i] state])
                [Enemy1[i] down];
        }
       
        for(i = 0; i < MAX_ENEMY2; i++)
        {
            if([Enemy2[i] state])
                [Enemy2[i] down];
        }
       
        for(i = 0; i < MAX_ENEMY3; i++)
        {
            if([Enemy3[i] state])
                [Enemy3[i] down];
        }
    }
}
@end

이번장에서 사용된 이미지와 전체 프로젝트 파일은 아래에서 다운 받으실 수 있습니다.

다음장에서는 충돌 검사, 스테이지/점수/에너지 표시등을 넣고 몇 가지 사항을 수정하여 초 간단한 게임을  완료해 보겠습니다.


Xcode 2 2007.10.01 11:52
그동안 바쁘다는 핑계로 포스트를 뜸하게 했지만, 10월 부터는 다시 초심의 마음으로 열심히 할려고 합니다. 우선 이전부터 생각했던 것인데 XCode를 처음 사용할 때 만나는 다소 생소한 용어들을 정리해 볼려고 합니다.

일단 간단히 정리만 해놓고 계속 업데이트를 할려고 합니다. 티스토리에 위키 기능이 있었으면 좋겠다는 생각이 드네요.

자료는 ADC의 문서와  Cocoa Programming for MAC OS X, Step into Xcode 서적을 참고하였습니다.

Action (액션)
버튼 등의 UI 컨트롤이 변경될 때 호출되는 메소드로 인터페이스 빌더에서 연결된다.

awakeFromNib [메소드]
어플리케이션이 실행되어 nib파일에서 읽어들인 정보로, 객체를 할당하고 초기화(init 메시지 발송)를 한 후에 nib에 있는 모든 오브젝트들에게 보내지는 메지지. init은 아울렛이 설정되기 전에 불려 지므로, 다른 객체에 메시지를 보낼 수 없다. 이럴 때는 awakeFromNib파일을 이용한다.

Bundle (번들)
패키지된 실행가능한 파일들과 이미지, 사운드, nib파일등의 리소스들을 가지고 있는 디렉토리이다. 응용 프로그램과 프레임워크는 일반적인 번들의 예이다.

FirstResponder
기존의 클래스에 추가하는 메소드들의 집합. 이벤트를 처리하는 첫번째 NSResponder 객체이며 First Responder가 받은 이벤트를 처리하지 않는 다면, 핸들러를 찾을 때까지 Responder 체인을 찾는다.

Framework (프레임워크)
헤더파일, 동적으로 로드되는 라이브러리와 단일 디렉토리의 하부 디렉토리에 있는 리소스들을 포함하고 있는 번들이다. Framworks는  디렉토리이지만 .framework의 확장자를 가진다.

Method (메소드)
class 내부 함수. 메시지를 보내서 호출한다.

MVC (Model-View-Controller)
디자인 패턴, 클래스는 MVC중 하나로 분류될 수 있어야 한다.
Model
데이터를 의미하면, 데이터를 변경하는 연산을 지원한다.

View
사용자에게 Model을 보여주는 역활을 한다. 하나의 Model에 관하여 출력 방식이 다른 다양한 View가 존재할 수 있다.

Controller
사용자의 입력과 로직을 처리하여 Model을 변경한다.

Nan (난)
Not a number의 약자로 숫자가 와야 할 곳에 오류 또는 숫자가 아닌 다른 값일  경우를 나타내는 상태다. 숫자값이 NaN이 된 상태에서는 다른 연산도 모두 NaN이 된다.

Nib 파일
NeXT Interface Builder의 약자이며, 오브젝트의 속성, 연결 등의 정보를 가지고 있는 패키지이다. 인터페이스 빌더에서 생성되며 해당 어플리케이션이 실행시에 로드된다.

nil (닐)
객체의 포인터가 NULL일 경우에는 nil로 사용한다.

Outlet (아울렛)
객체를 가르키는  인스턴스 변수. 인터페이스 빌더에서 다른 객체를 지정하는 포인터로 사용된다.

Package (패키지)
OS X의 파인더가 실제로는 디렉토리이며 내부에는 하부 디렉토리를 가지고 있다고 하더라도, 사용자에게는 하나의 파일 형태로 보여주는 디렉토리이다. 응용프로그램과 RTFDs와 같은 복합적인 문서들이 일반적인 예이다.

Protocol (프로토콜)
Objective-C에서 클래스가 구현하기로 약속한 method의 한벌(suit). 만약 클랙스가 프로토콜의 모든 메시지를 구현하지 않으면 컴파일러는 오류를 내보낸다. id <ProtocolName>의 오브젝트 포인터 타입은 구현된 어떠한 ProtocolName 프로토콜의 타입을 정의한다.

메소드선언의 목록이며, 프로토콜을 구현한 클래스를 생성하면 클래스는 선언한 모든 메소드를 구현하여야 한다.

ZeroRink (제로링크)
Xcode에서 빌드 속도를 높이기 위해 어플리케이션이 실행되기 전 링크를 생략한다. 어플리케이션을 실행하기 위한 모듈만 링크되며, 다른 모듈들은 호출될 때만 링크가 된다.

ZeroLink된 어플리케이션은 현재 개발중인 컴퓨터에 종속되며, 다른 컴퓨터에서는 적용 되지 않는다.Deployment로 빌드하면 어플리케이션에서 디버깅 심벌을 제가하고 실제로 링크 작업을 한다.

'Xcode 2' 카테고리의 다른 글

XCode 용어집  (4) 2007.10.01

1.8.0 프로젝트 개요
이번 장에서는 NSView와 NSImage를 이용해서 간단한 슈팅게임을 만들어 보겠습니다.

사용자 삽입 이미지
좌측과 같이 배경을 출력하고, 우주선을 좌우로 조종하고, 미사일을 발사하는  작업까지 하겠습니다.

키입력에서 다중으로 입력 받을 수가 없어 이동시에 미사일을 발사하면 이동이 중지 됩니다. 이문제는 다음 장에서 해결하도록 하겠습니다.





1.8.1 프로젝트 생성
Xcode를 실행하고 아래와 같이 SimpleShot 코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. project Name에 SimpleShot를 입력하고 finish 버튼을 클릭합니다.

1.8.2 인터페이스 빌더에서 작업
1) 서브 클래스 생성

사용자 삽입 이미지
Nib 파일을 클릭하여 인터페이스빌더를 열고 NSView 클래스의 서브클래스를 만들고(아래의 창에서 NSView에 마우스 우클릭 합니다. 그 다음 Subclass NSView 클릭) 이름을 StageView로 변경합니다


사용자 삽입 이미지
StageView를 우클릭 하고 좌측과 같이 Create Files for StageView를 클릭하여 소스파일을 생성합니다.





2) NSView 생성 및 속성 설정

윈도우를 열고 팔레트의 Containers 항목에서  CustomeView를 드래그하여 아래와 같이 윈도우에 배치 합니다. View의 크기를 300X320으로 변경하고,  Custom Class 항목에서 StageView로 선택합니다.
사용자 삽입 이미지사용자 삽입 이미지
(클릭하면 이미지가 확대됩니다.)

1.8.3 이미지 파일

1) 이미지 생성

이제 배경, 우주선, 미사일에 쓰일 이미지를 준비합니다. 배경은 상관없지만 우주선과 미사일은 윤곽선이외에는 투명색으로 보여야 하기 때문에 png를 사용했습니다.

사용자 삽입 이미지
저는 도스의 디럭스페인트 이후로는 그래픽 작업이 불가능하므로 초등학생인 아들에게 이미지를 부탁하였습니다. 그래픽툴들을 다루실 수 있는 분들은 새로 만드셔도 되고, 아니면 첨부파일에서 다운로드 받으셔서 사용하시면 됩니다.

좌측은 배경화면 이미지입니다. StageView와 같은 300X320의 크기를 가지고 있습니다.



사용자 삽입 이미지
우주선 이미지 입니다. 하얗게 보이는 부분을 투명하게 처리해야 하단의 배경화면에서 올바르게 보입니다. 이를 위해 png파일 포맷을 사용했습니다.

사용자 삽입 이미지
미사일 이미지 입니다. 위와 같이 png파일 포맷을 사용했습니다.

2) 이미지 등록

사용자 삽입 이미지
준비된 이미지 파일을 드래그하여 Xcode 좌측의  Group & Files 목록의 Resources에 가져다 놓습니다.



드래그 시 나오는 판넬에서 아래와 같이 Copy items... 항목의 체크를 확인합니다.
사용자 삽입 이미지

1.8.4 소스코드 작성

1) StageView.h 수정

#import <Cocoa/Cocoa.h>

@interface StageView : NSView
{
    NSTimer *timer;
    NSImage *backgroundImage;
   
    NSRect heroRect;
    NSImage *heroImage;

    BOOL isFire;
    NSRect missileRect;
    NSImage *missileImage;
}

- (void)processGame;
- (void)fireMissile;
- (void)keyDown:(NSEvent *)event;

@end


2) StageView.m 수정

#import "StageView.h"

#define SCREEN_WIDTH        300
#define SCREEN_HEIGHT        320

#define HERO_SPEED            3       /* 우주선 속도 */
#define MISSILE_SPEED        4       /* 총알발사 속도 */

@implementation StageView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
       
        isFire = NO;
       
        /* 배경 이미지 로드 */
        NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
        backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];

        /* 우주선 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"hero" ofType:@"png"];
        heroImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        heroRect.size = [heroImage size];
        heroRect.origin = NSMakePoint((SCREEN_WIDTH - heroRect.size.width)/2, 0);
       
        /* 미사일 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"missile" ofType:@"png"];
        missileImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        missileRect.size = [missileImage size];
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
       
        /* 30프레임으로 타이머 설정 */
        timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];
    }
    return self;
}

- (void) dealloc
{
    [backgroundImage release];
    [heroImage release];
    [missileImage release];
       
    [super dealloc];
}

/* 키입력을 받기 위한 설정 */
- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)keyDown:(NSEvent *)event
{
    int keyCode;
    
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];
   
    /* 좌측으로 이동 */
    if(keyCode == 123)
    {
        heroRect.origin.x -= HERO_SPEED;
        if(heroRect.origin.x < 0)
            heroRect.origin.x = 0;
    }

    /* 우측으로 이동 */
    if(keyCode == 124)
    {   
        heroRect.origin.x += HERO_SPEED;

        if(heroRect.origin.x >= SCREEN_WIDTH - heroRect.size.width)
            heroRect.origin.x = SCREEN_WIDTH - heroRect.size.width;
    }
   
    /* 발사(스페이스) */
    if(keyCode == 49)
    {
        [self fireMissile];
    }
}       

- (void)fireMissile
{
    if(isFire == NO)
    {
        /* 미사일이 처음 발사되었을 경우에 초기위치를 우주선 좌표로 설정 */
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
    }
    isFire = YES;
}

- (void)processGame
{
    if(isFire == YES)
    {
        /* 미사일이 발사중이면 y좌표를 이동 */
        missileRect.origin.y += MISSILE_SPEED;
        if(missileRect.origin.y >= SCREEN_HEIGHT)
        {
            /* 화면상단에 위치했을 경우에는 미사일을 출력하지 않는다 */
            isFire = NO;
        }
    }
   
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)rect
{
    NSRect imgRect;
    NSRect drawRect;
   
    /* 배경 이미지 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [backgroundImage size];
   
    drawRect = [self bounds];
   
    [backgroundImage drawInRect:drawRect
                     fromRect:imgRect
                    operation:NSCompositeSourceOver
                     fraction:1.0];
   
    /* 우주선 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [heroImage size];
   
    [heroImage drawInRect:heroRect
               fromRect:imgRect
              operation:NSCompositeSourceOver
               fraction:1.0];   
   
    if(isFire == YES)
    {
        /* 미사일 출력 */
        imgRect.origin = NSZeroPoint;
        imgRect.size = [missileImage size];
       
        [missileImage drawInRect:missileRect
                     fromRect:imgRect
                    operation:NSCompositeSourceOver
                     fraction:1.0];   
    }   
}

@end

#define HERO_SPEED            3       /* 우주선 속도 */
#define MISSILE_SPEED        4       /* 미사일발사 속도 */

한 프레임에서 우주선과 총알의 움직이는 속도를 설정합니다. 좌/우 방향키를 누르면 각각의 방향으로 우주선이 한 프레임당 3픽셀을 움직입니다. 미사일은 4픽셀씩 상단으로 이동합니다. 숫자를 바꾸어 주면 해당 오브젝트의 속도를 변경할 수 있습니다.

StageView가 생성될 때 자동으로 호출되는 - (id)initWithFrame:(NSRect)frameRect 메소드에서 이미지 파일을 로드하고 타이머를 세팅하는 작업을 합니다.

NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];

어플리케이션의 리소스 디렉토리에서 background.png 이미지 파일을 로드하고 NSImage를 생성 합니다. 우주선과 미사일 이미지를 차례로 로드합니다.

timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];

1/30초 마다 processGame 함수를 호출하도록 타이머를 설정합니다. processGame 함수에서는 [self setNeedsDisplay:YES];로 설정하여 DrawRect가 호출되어 이미지를 출력하게 합니다.
이제 컴파일을하고 실행을 합니다. 좌우 방향키로 우주선을 움직이며 스페이스키로 미사일을 발사할 수 있습니다. 미사일은 현재 한발씩만 발사 할 수 있습니다. 다음 장에서는 적기들을 만들고 움직여 보도록 하겠습니다.

시작하면서...
처음엔 잘 모르시는 분들도 쉽게 따라 해 볼 수 있도록 튜토리얼을 만들어 볼려고 했습니다. 그런데  포스트를 계속 올리다 보니, C에 관해 질문해 오시는 분들도 많고 주위에서도 따라 해보기는 하는데 이해를 전혀 할 수 없다는 이야기를 들었습니다.

이런 이유로 프로그래밍이나 C언어에 관한 지식이 없으면 용어나 소스코드 작성 시 이해가 힘들기 때문에, 간단하게 이곳의 튜토리얼을 이해할 수 있는 정도의 C언어 강좌를 시작해 볼려고 합니다.

대상은 처음 C언어를 공부하시는 분들이며, 최대한 쉽게 설명해 보겠습니다.
 
1.1 C 언어의 역사와 특징
C는 1972년 Unix를 만들기 위해 Dennis Ritchie에 의해 만들어 졌습니다. 나온지 오래된 언어로 이 후 나온 언어들에 비해 불편한 점과 객체지향 프로그래밍을 구현하는데 어려움이 있지만, 아직까지 많이 사용되는 언어입니다.  

C는 오래된 역사와 시스템 프로그래밍과 같은 로우레벨 작업이 가능하고, 컴파일 되어 빠른 실행 파일을 작성할 수 있기 때문에 많은 플랫폼에서 사용되고 있습니다. 또한 게임 부터 각종 어플리케이션, 하드웨어 제어등 다양한 분야에서 사용됩니다.

현재 많이 쓰이는 C++, Objective-C, PHP등의 언어들이 C를 기본으로 하고 있고, C 문법은 거의 FM이라고 할수 있어 C를 기본으로 익히면 필요에 따른 프로그램 언어들을 배우기가 매우 용이 합니다.

이런 이유로 C는 처음 프로그램을 배우는 입문자에게 아직도 많이 권해 지고 있는 언어 입니다. 보다 자세한 C의 역사와 특징은 많은 곳에 언급되어 있으니 이곳에서는 이정도로 생략하겠습니다.


1.2 C 언어 공부하기
이곳에서는 간단하게 튜토리얼을 이해할 수 있을 정도의 기본적인 C 강좌를 할 것입니다. 체계적으로 C를 배우기 위해서는 관련서적을 구입하여 보실 것을 권장합니다.

1) 추천 서적

C와 관련하여 많은 서적이 있으니, 서점에서 직접 확인하여 보기 편하고 각자에게 맞는 서적을 구입하시면 됩니다. 일반적으로 많이 보는 C 관련 서적을 2개와 프로그래밍에 필요한 시스템에 관련된 기본 지식을 얻을 수 있는 책을 추천 합니다.

사용자 삽입 이미지
열혈강의 C 프로그래밍
윤성우 지음 / 프리렉

후배가 C를 배우고 싶다고 하여, 몇 년전 친구에게 요새 초보자가 보기에 괜찮은 C 서적이 뭐가 있는지 물어 보니 이 책을 추천해 주었습니다. 쉽게 설명되어 있어 C언어 공부를 시작하는 사람들이 보기에 가장 적당하다고 합니다.


사용자 삽입 이미지
C언어 프로그래밍
Brian W. Kernighan, Dennis M. Ritchie/대영사

1978년 나온 C의 바이블이라고 불리우는 The C Programming Language의 번역본 입니다. C를 배우기 위해 거의 필수적으로 봐야 할 서적이라고 할 수 있습니다. 하지만 바이블인 만큼 내용이 너무 교과서적(?)이라 위의 서적과 같이 병행하면서 보시면 좋습니다.


사용자 삽입 이미지
성공과 실패를 결정하는 1%의 프로그래밍 원리
HISAO YAZAWA(번역:예승철)/성안당

이전에 Inside the IBM PC라는 컴퓨터 시스템에 관련된 책이 있었습니다. 절판되지 않았다면 그 책을 추천했을 것이지만, 그와 비슷하고 심도는 조금 낮아진 것 같은 이 책도 적극 추천 합니다. C를 공부하기 전, 또는 공부하면서 이런 류의 책을 보시면 훨씬 이해가 쉽습니다.

위의 도서 이미지는 kangcom.com 에서 가지고 왔으며, 링크는 kangcom.com의 해당 도서로 링크되어 있습니다.


2) 보는 방법

책을 보실 때는 아래의 사항을 지키시면서 보시는게 좋습니다.

1. 백견불여일타
소스 코드는 백번 보는 것보다 직접 쳐보는 것이 더욱 효과적입니다. 책에 나오는 예제들은 무조건 직접 쳐보는 것이 좋습니다.

머리로 이해 하는 것도 중요하지만, 기본 문법과 자주 쓰이는 함수들이 습관화 되고 손에 익을 때 까지 가능한 한 많이 쳐보는 것이 좋습니다. 직접 타이핑을 하다 보면 오타가 나오고 컴파일을 위해 오류를 수정해야 하는데, 이 작업을 반복하면 반복할 수록 컴파일 전에 오류를 내는 습관을 줄일 수 있습니다.
 
2. 최소 2번이상 읽기
바이블로 여겨지는 책은 반드시 3번 정도 다시 읽어 보아야 합니다. 아는만큼 보인다고, 처음에 대충 이해를 하거나 잘 못 이해하고 넘어 갔던 부분들도 몇 번 다시 읽게 되면 작자가 설명하고자 하는 정확한 의미를 알 수 있습니다.

3. 꼼꼼하게 읽기
모든 책이 마찬가지 겠지만 대충 아는 것 같다고 그냥 넘겨 보면  안됩니다. 한 줄이지만 중요한 내용이 나올 수도 있고, 아는 내용이더라도 복습하는 의미로 꼼꼼하게 책을 읽는 것이 좋습니다. 빨리 책을 끝내는 것 보다 최대한 이해하도록 하고, 소스 코드 같은 경우에는 변경해 가면서 응용해 보는 것이 좋습니다. 


1.3 C 컴파일 환경 만들기
C 뿐만 아니라 다른 언어도 책만 보는 것 보다, 직접 타이핑을 해보고 컴파일을 하는 것이 더욱 이해가 용이합니다. C 코드를 테스트 해 볼수 있는 환경을 만들기 위하여, 맥/리눅스/윈도우에서 필요한 툴들을 알아보겠습니다.

1. Mac OS X

맥 OSX에서는 GCC, VI를 이용하여 C 소스코드를 작성하고 컴파일 할 수 있습니다. 간편하게 XCode를 이용할 수도 있습니다. Xcode는 이곳의 튜토리얼 1.1 Xcode 구하기에서 다운로드 및 설치에 대해 참고하실 수 있습니다.

Xcode를 이용하는 방법은 다음과 같습니다. 메뉴에서 File/New Project를 클릭합니다. 아래와 같이 Command Line Utility 항목에서 Standard Tool을 선택 하고 Next를 클릭한 후, 적당한 프로젝트명을 입력합니다.
사용자 삽입 이미지

위의 작업이 완료되면 Xcode에서 main.c를 열고 아래와 같이 책이나 이곳에 나오는 C 코드를 입력하고 테스트 해 보실 수 있습니다.
사용자 삽입 이미지

Xcode이외에 직접 gcc를 이용할 수 있습니다. 아래 2. Linux의 내용은 맥 OS X에서도 똑같이 적용이 가능합니다.


2. Linux

리눅스에는 GCC라는 C/C++ 컴파일러가 있습니다. 어떻게 보면 IDE (프로그램 통합 개발 환경)없이 리눅스 프롬프트 모드에서 직접 컴파일러나 링커를 실행 시키고 make를 이용하는 가장 원초적인 환경에서 작업 하는 것이 기본적인 이해에 좋습니다.

Linux(unix)는 C와 밀접한 관계에 있으므로, 대부분 C 컴파일러와 개발툴들이 포함되어 있습니다. 만약 사용하는 리눅스에 gcc가 설치되어 있지 않을 경우에는, 사용하는 리눅스 패키지에 있는 설치 툴들을 이용해 gcc 또는 developement, build 등으로 되어 있는 패키지를 인스톨 하시거나, 직접 다운로드 받아 설치하셔야 합니다.

에디터로는 vi, emacs등을 사용하실 수 있습니다. 아래는 맥의 BSD 유닉스 환경(아래의 모습은 리눅스와 완전 동일 합니다.)에서  hello, world를 편집하고 컴파일, 실행해 본 화면입니다.
사용자 삽입 이미지

사용자 삽입 이미지


3. MS Windows

윈도우즈 환경에선 대표적인 Visual C++이나 VS 2005가 있지만, 무료이고 용량도 작은 DEV-C++ 공개 컴파일러를 추천합니다.

VC의 경우에는 초보자가 공부를 위해 사용하기에 환경이 지나치게 복잡하고, 책들의 예제와는 다른 환경이나 메시지가 나올 수 있습니다. 예를 들면 strcpy 함수를 사용하면 strcpy_s로 사용하라고 경고 메시지를 내보냅니다. (경고는 끌 수 있습니다)

MS측에선 C 런타임 라이브러리의 안정성을 높이기 위해서 만들었다고는 하지만, 혼돈이 있을 수 있습니다. C언어 공부를 위해선 Dev-C++을 사용하는 것이 더 좋습니다. Dev-C++는  http://www.bloodshed.net/dev/devcpp.html 에서 다운 받으실 수 있습니다.

테스트를 위해선 설치를 하고 실행시킨 후, 메뉴에서 파일/새로만들기/프로젝트를 클릭합니다. 새로운 프로젝트에서 Console Application을 선택하고, 적당한 프로젝트 명을 입력하고 C를 선택한 후에 확인 버튼을 클릭합니다.
사용자 삽입 이미지

아래는 편집 화면이고 컴파일 후에 실행하여 결과를 확인할 수 있습니다.
사용자 삽입 이미지

위와 같이 소스를 작성 하면 컴파일 후 실행하면, 프롬프트 창이 너무 빨리 사라져 결과를 알 수 없습니다. 원래 main.c는 return 0; 위에 system("PAUSE"); 라는 라인이 자동으로 추가되어 있었는데, 이곳에서는 소스를 똑같이 보이기 위해 삭제했습니다.

실제 사용시에는 system("PAUSE"); 라인 위에 소스를 입력하시면, 결과를 확인할 수 있습니다.
이상 C를 공부하는데 필요한 내용과 어플리케이션에 관해 알아 보았습니다. 다음 장에서는 C 프로그램을 위한 기본적인 내용과 용어에 관해 설명하겠습니다.

'프로그래밍 강좌 > C 언어 기초' 카테고리의 다른 글

6. 제어문  (0) 2007.06.14
5. 연산자  (0) 2007.06.13
4. 변수  (2) 2007.06.12
3. C 기초문법  (0) 2007.06.05
2. 소스코드, 컴파일, 링크  (6) 2007.06.04
1. C언어 공부를 위한 준비  (9) 2007.06.03

1.7.5 소스 수정

1) AppController.h 수정 및 MyWindow 연결

AppController.h를 오픈하여 아래와 같이 추가합니다. "/* */" 또는 "//"로 주석 처리된 부분은 생략 하셔도 됩니다.

#import <Cocoa/Cocoa.h>

/* 해당 버튼의 Tag 값 */
#define BTN_PLUS        100
#define BTN_MINUS        101
#define BTN_MULTIPLE    102
#define BTN_DIVISION    103
#define BTN_RESULT        104
#define BTN_CLEAR        105

@class MyWindow;

@interface AppController : NSObject {
    int prevOperation;       /* 사용자가 이전에 클릭한 연산 값 */
    int isClear;                   /* 연산을 클릭 후, 다시 값을 입력할 때 지원야 함을 알리는 플래그 */
   
    int totalValue;              /* 현재 계산 총 합 */
   
    IBOutlet MyWindow *myWindow;          /* 사용자 윈도우 */
    IBOutlet NSTextField *txtValue;             /* 현재/결과 값 표시 창 */
    IBOutlet NSTextField *txtOperation;      /* 현재 연산 모드 결과 표시 창 */
    IBOutlet NSTextField *txtHistory;          /* 연산 내역 표시 창 */
}

- (void)awakeFromNib;
- (void)processCalcul:(int)val;
- (void)processInput:(int)val;

- (IBAction) processCommand:(id)sender;
@end

저장 후에 Xcode의 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. Xcode 좌측에서   AppController.h를 드래그 하여 MainMenu.nib 윈도우로 드래그해서 놓습니다.

이 작업은 이전 장에서 하였지만 변경된 AppController를 인터페이스 빌더에서 작업하기 위해 다시 한번 반복합니다. 아래와 같이 AppController의 myWindow 아울렛을 Window에 연결합니다.
사용자 삽입 이미지

MainMenu.nib를 저장 후, 인터페이스 빌더를 닫습니다.

2) MyWindow 소스파일 변경

MyWindow.h를 오픈하여 아래와 같이 추가합니다.
#import <Cocoa/Cocoa.h>

@class AppController;

@interface MyWindow : NSWindow
{
    AppController *app;
}

- (void)setAppController:(AppController *)a;
- (void)keyDown:(NSEvent *)event;
@end

MyWindow.m을 오픈하여 아래와 같이 추가 합니다. 사용자의 키입력을 처리하고 AppController에 알려줄 수 있도록 합니다.
#import "MyWindow.h"
#import "AppController.h"

@implementation MyWindow
/* AppControllerd에서 호출 */
- (void)setAppController:(AppController *)a
{
    app = a;
}

/* 키가 눌려질 경우, 자동으로 호출된다. */
- (void)keyDown:(NSEvent *)event
{
    int keyCode;
    int val = -1;
 
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];


    NSLog(@"KEY: %d", keyCode);
   
    /* 키값을 AppController에서 처리할 수 있도록 변경해 준다.
        각각의 키값은 좌측 숫자키 보드에서 누른 키값들이며, 위의 NSLog에서 확인할 수 있다. */
    if(keyCode >= 82 && keyCode <= 90) // 0~7
        val = keyCode - 82;
    else if(keyCode ==91) // 8
        val = 8;
    else if(keyCode ==92) // 9
        val = 9;
    else if(keyCode == 67) // *
        val = BTN_MULTIPLE;   
    else if(keyCode == 75) // /
        val = BTN_DIVISION;
    else if(keyCode == 69) // +
        val = BTN_PLUS;
    else if(keyCode == 78) // -
        val = BTN_MINUS;
    else if(keyCode == 71) // clear
        val = BTN_CLEAR;
    else if(keyCode == 81) // =
        val = BTN_RESULT;
   
    /* val이 세팅되어 처리해야 될 값일 경우에, AppController의 processInput에 값을 넘겨 준다. */
    if(val != -1)
        [app processInput:val];
   
    [self interpretKeyEvents:[NSArray arrayWithObject:event]];
}
@end

3) AppController.m 소스파일 변경

#import "AppController.h"
#import "MyWindow.h"

@implementation AppController
/* awakeFromNib는 nib파일이 로드된 후, 오브젝트들에게 보내지는 메세지 입니다.
    어플리케이션 실행 후, 각종 초기화 작업을 할 수 있습니다.
*/
- (void)awakeFromNib
{
    isClear = 0;
    prevOperation = 0;
    totalValue = 0;

    /* myWindow의  AppController 변수를 세팅합니다.
        myWindow는 키보드 입력시 이 AppController로 전달합니다.
    */
    [myWindow setAppController:self];
}

/* 연산자 (+, -, X, /, = )가 입력되었을 경우, 처리 합니다. */
- (void)processCalcul:(int)val
{
    /* 이전에 설정된 연산자를 처리한다. */
    if(prevOperation == BTN_PLUS)
        totalValue += [txtValue intValue];
    else if(prevOperation == BTN_MINUS)
        totalValue -= [txtValue intValue];
    else if(prevOperation == BTN_MULTIPLE)
        totalValue *= [txtValue intValue];
    else if(prevOperation == BTN_DIVISION)
        totalValue /= [txtValue intValue];
   
    /* 연산명령이 처음 수행되었을 경우에는, 결과값을 현재 입력된 값으로 설정 합니다. */
    if([[txtHistory stringValue] length] < 1)
        totalValue = [txtValue intValue];
   
    char operation;
    NSString *strTemp;
  
    /* 입력된 키에 따라 출력될 문자를 설정 합니다. */
    if(val == BTN_PLUS)
        operation = '+';
    else if(val == BTN_MINUS)
        operation = '-';
    else if(val == BTN_MULTIPLE)
        operation = '*';
    else if(val == BTN_DIVISION)
        operation = '/';
    else
        operation = '=';

   
/* 계산 History에 출력될 값으로 = 일 경우에는 결과값을 출력합니다. */
    if(val == BTN_RESULT)
        strTemp = [[NSString alloc] initWithFormat:@"= %d\n", totalValue];
    else
        strTemp = [[NSString alloc] initWithFormat:@"%c", operation];
   
   
/* clear 모드를 1로 설정해 이후 다시 숫자를 입력 하면, 이전 결과 값이 지워진 후,
        새 입력값이 나올 수 있도록 합니다.
        prevOperation에 다음 연산 작업을 위해 입력된 연산값을 저장합니다.
    */
    isClear = 1;
    prevOperation = val;
   
    /* 연산 창에 현재 연산모드를 출력합니다. */
    [txtOperation setStringValue:[NSString stringWithFormat:@"%c", operation]];

    /* 연산내역 창에 현재 연산 내역을 추가 합니다. */
    [txtHistory setStringValue:[NSString stringWithFormat:@"%@ %@ %@",
        [txtHistory stringValue],
        [txtValue stringValue],
        strTemp]];
   
   
/* 결과 창에 현재까지 계산된 결과를 보여 줍니다. */
    [txtValue setFloatValue:totalValue];
    [strTemp release];
}

/* 버튼 클릭과 사용자의 키보드 입력을 처리 합니다. */
- (void)processInput:(int)val
{
    NSString *strTemp;
    
    if(val >= 0 && val <= 9)
    {
      
/* 숫자가 입력되었을 경우, 처리 합니다. */
        if(isClear == 1)
        {
             /* 연산을 클릭하고, 처음 숫자가 입력되었을 경우 결과창을 초기화 합니다. */
            [txtValue setStringValue:@""];
            isClear = 0;
        }   
       
        /* 결과 창에 현재 입력된 값을 추가하여 출력 합니다. */ 
        strTemp = [NSString stringWithFormat:@"%@%d", [txtValue stringValue], val];
        [txtValue setStringValue:strTemp];
    }   
    else if(val == BTN_CLEAR)
    {
       /* clear 키를 입력하였을 경우, 데이터와 출력을 초기화 합니다. */
        isClear = 0;
        prevOperation = 0;
       
        [txtValue setStringValue:@""];
        [txtHistory setStringValue:@""];
        [txtOperation setStringValue:@""];
    }
    else if(val >= BTN_PLUS && val <= BTN_RESULT)
    {
       /* 연산이 입력되었을 경우에 현재 값이 있는지 확인 후 처리 합니다. */
        if([[txtValue stringValue] length] > 0)
            [self processCalcul:val];
    }
}

/* 버튼이 클릭되었을 경우에, processInput에 알려 줍니다. */
- (IBAction) processCommand:(id)sender
{
    /* 현재 선택(클릭)된 버튼의 tag값을 얻어 옵니다. */
    int val = [[sender selectedCell] tag];
   
    [self processInput:val];
}
@end

이제 모든 작업이 완료되었습니다. 빌드하고 실행시켜 계산기 프로그램을 테스트 해 봅니다.

아직 처리하지 않는 부분이 많이 있습니다. 이 부분과 몇 가지 기능들을 다음 튜토리얼을 통해 계속 수정/추가해 나가겠습니다. 이번 장부터는 소스코드를 압축하여 올립니다. 참고하실 분은 다운 받아 압축을 풀고, Xcode에서 확인하시고, 이 소스코드에는 주석이 되어 있지 않습니다.


1.7.1 프로젝트 개요 및 생성

1) 개요

사용자 삽입 이미지
이전에도 간단한 계산기를 만들어 보았지만, 이번에는 연속계산이 가능하고, 키보드 입력과  몇 가지 출력을 추가한 업그레이드 된 계산기를 만들어 보겠습니다.

좌측과 같은 계산기인데 첫번째  232라고 되어있는 창에 입력한 값과 계산된 값이 출력 됩니다. 그 옆의 작은 창에 현재 계산 모드가 출력 됩니다. 그 아래의 큰 창에는 계산했었던 내용들이 보여 집니다. 입력은 숫자 키패드와 버튼으로 받습니다.

이번 튜토리알에서는 새로운 내용인 키입력 처리와 NSMatrix에 대해서 알아 보겠습니다.


2) 프로젝트 생성

Xcode를 실행하고, MyCalculator로 cocoa aplication 프로젝트를 새로 만듭니다. 메뉴에서 File/New File을 선택하여 Objective-C 클래스를 선택하고 AppController 클래스를 만듭니다.

위의 과정에 대한 상세한 설명은 이전 포스트에서 설명하였으니, 앞으로는 자세한 과정은 생략하도록 하겠습니다.

1.7.2 AppController 생성 및 변경
Xcode에서 AppController.h을 열어 아래와 같이 추가하고, 저장합니다. 소스에 대한 설명은 다음 장에서 하겠습니다.

#import <Cocoa/Cocoa.h>

@interface AppController : NSObject {
    int prevOperation;
    int isClear;
   
    int totalValue;
   
    IBOutlet NSTextField *txtValue;
    IBOutlet NSTextField *txtOperation;
    IBOutlet NSTextField *txtHistory;
}

- (IBAction) processCommand:(id)sender;
@end

1.7.3 사용자 인터페이스 편집

1) AppController 인스턴스 생성

Xcode의 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. Xcode 좌측에서   AppController.h를 드래그 하여 MainMenu.nib 윈도우로 드래그해서 놓습니다.

사용자 삽입 이미지
Classes에서 AppController를 우클릭 한 후, 메뉴에서 Instantiate AppController를 클릭하여 인스턴스를 만듭니다.

좌측과 같이 인스턴스가 만들져 있는 것을 확인합니다.





2)  Window 컨트롤 배치 및 속성 변경

위의 Intances 창에 보이는 Window를 더블 클릭하여 윈도우를 엽니다.

사용자 삽입 이미지
팔레트에서 텍스트 필드 세개와 버튼 하나를 윈도우에 가져다 놓고 좌측과 같이 배치합니다.

처음 긴 텍스트 필드는 계산 결과 및 입력값, 우측 작은 텍스트 필드는 현재 연산자, 아래의 넓은 택스트 필드는 히스토리를 보여줍니다.

버튼 모양은 인스펙터에서 아래와 같이 설정하였습니다. 원하시는 모양을 선택하시면 됩니다.
사용자 삽입 이미지






사용자 삽입 이미지
첫번째 텍스트필드를 선택하고 인스펙터를 엽니다. 좌측과 같이 정렬을 우측으로 설정합니다. 인스펙터는 해당 오브젝트를 클릭 후에 메뉴 에서 선택 또는 shitf + commad(사과 키) + i를 동시에 누릅니다.

두번째 연산모드 텍스트 필드의 정렬을  가운데로 설정합니다.




사용자 삽입 이미지
위의 세 텍스트필드 모두, 옵션을 좌측과 같이 모두 체크를 해제 합니다. 키보드 입력을 윈도우가 모두 받기 위함입니다.


3) MSMatrix 생성

위의 작업들은 이전 튜토리얼에서 해 본 작업 들입니다. 이해가 가지 않는 분들은 이전 포스트를 확인해 주세요. 이제 NSMatrix를 사용하기 위해 새로운 작업을 해보겠습니다.

사용자 삽입 이미지
버튼을 클릭한 후, 쉬프트 + 옵션 + 마우스를 클릭한 상태에서, 버튼 우측 하단의 원을 클릭하면 좌측과 같이 하나의 원만 남습니다.

위의 상태에서 이 원을 드래그 하여 크기를 늘리면 버튼이 늘어 나는데, 4X4개의 버튼이 되게 합니다. 그리고 텍스트필드와 윈도우를 적당한 크기로 조절합니다.
 
사용자 삽입 이미지

위의 작업이 완료가 되면 좌측과 같은 모습을 보실 수 있습니다. 버튼을 더블클릭하면 텍스트를 수정 할 수 있습니다.

버튼을 더블클릭하여 아래와 같이 모든 버튼들의 텍스트를 변경합니다. 곱하기는 대문자 x를 사용합니다.
사용자 삽입 이미지







4) 버튼셀 tag 변경

이 작업은 매우 중요합니다. 아래와 같이 Tag 값을 정확히 설정하여야, 정확한 계산 결과 값을 보실 수 있습니다.

아래와 같이 0 버튼을 클릭하고 인스펙터에서 Tag값을 0으로 세팅합니다. 0~9까지의 버튼들을 모두 텍스트와 같은 숫자(1->1, 2->2, ...)로 변경합니다.
사용자 삽입 이미지

사용자 삽입 이미지
숫자를 제외한 +, -, X, /, =, C 기호들은 아래와 같이 Tag 값을 입력해 줍니다.

+->100, -->101, X->102, /->103, =->104, C->105


5) 텍스트 필드 Connection 설정

아래와 같이 텍스트필드들을 AppController의 아울렛들과 연결해 줍니다.

사용자 삽입 이미지
좌측과 같이 NSMatrix를 AppController의 processCommand에 연결 시킵니다.












6) 윈도우 서브클래스 생성 및 Window와 연결

이제 윈도우를 위한 서브클래스와 소스파일을 생성합니다.
사용자 삽입 이미지
MainMenu.nib파일의 Classes 윈도우에서 NSWindow의 서브클래스를 생성합니다.

NSWindow는 NSObject > NSResponder 밑에 있습니다.






사용자 삽입 이미지
NSWindow의 서브클래스 MyWindow를 우클릭하여 나오는 메뉴에서  Create Files for MyWindow를 선택하여, MyWindow.h와 MyWindow.m 파일을 생성합니다.





사용자 삽입 이미지
Window의 인스펙터를 오픈한 후, Custome Class에서 위에서 만든 MyWindow를 선택합니다.





1.7.4 윈도우, 메뉴, 아이콘 설정

1) 윈도우 설정

사용자 삽입 이미지
왼쪽과 같이 윈도우 타이틀을 MyCalculator로 변경합니다.

Title bar controls에서 Zoom (and resize)를 해제 하여 사용자가 윈도우의 크기를 변경 할 수 없게하며, 우측 하단에 나오는 사이즈 변경 탭이 사라집니다.



2) 메뉴 설정

사용자 삽입 이미지
MainMenu를 클릭하여 좌측과 같이 메뉴 부분에서 New Application으로 되어 있는 부분을
MyCalculator로 변경합니다.










3) 아이콘 설정

이전 튜토리얼 SimpleViewer 이미지뷰어 (2) 1.6.4 어플리케이션 다듬기에서 4) 아이콘 변경을 참조하여, 원하는 아이콘으로 변경합니다.

저는 계산기 어플리케이션 자체를 아이콘으로 만들었습니다. shift + command + 4를 동시에 누르면 캡쳐할 범위를 선택할 수 잇는 십자모양의 커서가 나옵니다. 이 커서를 계산기로 이동하여 스페이스를 누르면 해당 윈도우만 캡쳐할 수 있습니다.

위의 튜토리얼에서 3) About SimpleViewer 판넬 변경을 참조하여, 판넬부분도 변경하여 줍니다. 빌드 후 어플리케이션을 실행시킵니다. 메뉴에서 About MyCalculator를 클릭하여 아래와 같이 변경사항을 확인합니다.
사용자 삽입 이미지

이제 계산기 어플리케이션의 겉모습이 완성되어습니다. 다음 장에서는 실제로 계산기가 동작하도록 소스코드를 작성해 보겠습니다.

개인적으로 일이 있어 오랫만에 포스트를 올립니다. 저도 오랫만에 Xcode를 실행해 보니 왠지 낯서네요. 꾸준히 올릴 수 있도록 하겠습니다.



1.6.4 어플리케이션 다듬기
이번에는 이전에 만들어 놓은 SimpleViewer에 스크롤을 추가하고, 아이콘을 변경하고, 몇 가지 세부사항을 변경하는 작업을 해보겠습니다.

1) 스크롤 추가

인터페이스 빌더에서 윈도우를  선택하고 메뉴에서 아래와 같이  Layout/Make Subviews of/ScrollView를 클릭합니다.
사용자 삽입 이미지

사용자 삽입 이미지
위와 같이 하면 ImgView 우측과 하단에 좌측과 같이 여백이 생기는 것을 확인하실 수 있습니다.










ImgView와 NSScrollView의  인스펙트를 오픈 해  Size의 Autosizing 항목을 각각 아래와 같이 변경해 줍니다. NSScrollView는 우측 하단 모서리 부분을 클릭하면 선택됩니다. 인스펙터 창의 상단 타이틀에서 현재 선택된 항목을 확인 합니다.
사용자 삽입 이미지사용자 삽입 이미지

2) 메뉴 타이틀 변경

사용자 삽입 이미지
SimpleViewer를 실행 시키고 메뉴를 보면 좌측과 같이
About NewApplication
Hide NewApplication
Quit NewApplication
과 같이 어플리케이션 이름이 아니라 인스펙트 빌더의 기본값인 NewApplication으로 되어 있습니다.



사용자 삽입 이미지
인스펙터 빌더에서 MainMenu.nib에서 각각의 항목을 더블클릭 해 좌측과 같이 모든 NewApplication을 SimpleViwer로 변경합니다.

Nib 파일은 실행시 로드되기 때문에 컴파일 없이 인스펙터 빌더에서 저장하고 어플리케이션을 실해 하여 확인해 봅니다.





3) About SimpleViewer 판넬 변경

사용자 삽입 이미지
어플리케이션을 실행 시키고, 메뉴에서 About SimpleViewer를 클릭하면 좌측과 같은 판넬을 볼 수 있습니다.

__MyCompanyName__으로 되어 있는 부분과 버젼을 변경해 보겠습니다.





사용자 삽입 이미지
Xcode에서 좌측과 같이 Resources 그룹에서 info.plist를 에디터에서 오픈합니다.

하단을 보면 아래와 같이 CFBundelVersion 키에서 string으로 되어 있는 부분에 1.0으로 되어 있는 값을 원하는 버젼으로 수정합니다.

    <key>CFBundleVersion</key>
    <string>0.9</string>



이제는 그 밑의 infoPlist.strings를 열어 NSHumanReadableCopyright 부분을 원하는 저작권자 명으로 변경하여 줍니다.

NSHumanReadableCopyright = "© cocoadev.tistory.com, 2007";

이제 다시 프로그램을 실행시키고 메뉴에서 About을 클릭하면 아래와 같이 변경된 사항을 확인할 수 있습니다.
사용자 삽입 이미지


4) 아이콘 변경

사용자 삽입 이미지
프로그램을 실행해서 하단의 독을 보거나,  폴더에서 실행파일의 아이콘을 보면 좌측과 같이 맥의 기본 응용프로그램 아이콘으로 되어 있습니다.

이번에는 이 아이콘을 변경해 보겠습니다.

/Developer/Applications/Utilities에서 Icon Composer를 실행합니다.
사용자 삽입 이미지
좌측과 같이 Image RGB/Alpha의 각각의 사각형 영역에  원하는 이미지 파일을 드래그해서 가져다 놓습니다.

저는 128X128 JPG 이미지를 하나 만들어 Thumbnail(128X128) 영역에 드래그 해 놓고, 그 부분을 위로 계속 드래그 시켜 항목을 채웠습니다.

이제 메뉴에서 아이콘을 적당한 이름으로 저장합니다.





사용자 삽입 이미지
위에서 저장한 아이콘 파일(*.icns)을 Xcode 좌측 메뉴에서 Resources 그룹에 드래그 해서 가져다 놓으면 좌측과 같은 창이 나옵니다.

상단의  Copy items into destination group's folder(if needed) 항목의 체크를 확인하고 add 버튼을 클릭합니다.





이제 이전에 열었던 info.plist를 열어 CFBundleIconFile 항목을 아래와 같이 설정합니다.
    <key>CFBundleIconFile</key>
    <string>simpleviewer</string>
저는 아이콘 파일명을 simpleviewer.icns로 만들었습니다. 이전에 저장한 icns를 제외한 파일명을 입력해 줍니다.

이제 어플리케이션을 실행하여 About 메뉴를 클릭하면,  아래와 같이 변경된 내역을 확인할 수 있습니다.
사용자 삽입 이미지





Objective-C는 1980년대 Stepstone사의 Brad Cox와 Tom Love에 의해 기존의 C에 SmallTalk의 객체지향 장점을 추가하여 만들어진 멋진 언어라고 합니다. 하지만 주위에선 별로 사용자를 볼 수가 없습니다.

개발환경이 특정 하드웨어나 OS로 한정되어 있고, 가장 많은 사용자를 가진 윈도우즈용 어플리케이션을 제작할 수 없어서(제가 아는 한에서 이며, 확실치 않습니다.) 인 것 같습니다.

보기에도 언뜻 Objective-C 소스를 보면 C와는 상관없는 전혀 별개의 언어로 보여, C/C++ 사용자가 접근이 힘들어 보입니다. 이유는 C에다 클래스와 메시지 전달 방식의 메소드 등 추가된 문법 때문입니다.

하지만 이 부분은 쉽게 배울 수 있으며, 어디선가 C를 알고 객체지향 프로그램에 대한 이해가 있다면 2시간 이면 Object-C를 배울 수 있다는 내용을 본적이 있습니다. 저같이 C/C++을 잘하지 못하는 사람도 대충 배워 가는 것을 보면, 틀린 얘기는 아니라고 생각됩니다.

그래서 C/C++, 또는 윈도우즈에서 VC++을 사용해 보시고, Objective-C 경험이 전혀 없으신 분들을 위해 이 블로그의 제목처럼 맛만 보실 수 있도록 부실한 내용을 시작 하겠습니다.
 
우선 Xcode에서 cocoa application 프로젝트를 만들면, Xcode에서 자동으로 생성해주는 main.m를 확인해 보겠습니다. (만드는 방법은 튜토리얼 분류쪽의 포스트에 있습니다.)

우선 Objective-C에서는 헤더파일은 .h로 같은 이름을 사용하지만, 소스파일명에는 .c 대신에 .m 확장자를 사용함을 알 수 있습니다. 소스를 보겠습니다.

#import <Cocoa/Cocoa.h>

int main(int argc, char *argv[])
{
    return NSApplicationMain(argc,  (const char **) argv);
}

MS처럼 윈도우로 넘어 오면서 C/C++ 고유의 main이란 이름을 가만히 놔두지 않는 것에 비하여, 전형적이고 친숙한 C의 main이 보입니다.

import만 생소하고 모두 C와 동일합니다. import는 Objective-C에서 추가된 전처리 명령어로 include와 유사하지만, 중복될 경우에는 한번만 include합니다. import대신 include의 사용도 가능합니다. 하지만 중복되어 인클루드될 경우를 대비해 헤더파일에
#ifndef _HEADER_NAME_H
#define _HEADER_NAME_H
.........
#endif  //_HEADER_NAME_H
과 같은 처리가 필요하지만 import를 사용하게 되면, 중복 오류를 신경 쓸 필요가 없습니다.

우선 테스트를 위해 아래와 같이 C코드를 추가하고 컴파일을 해봅니다.
void copy_str(char* des, char* src)
{
    strcpy(des, src);
}

int main(int argc, char *argv[])
{
    char buffer[12];
    char* ptr;
    
    copy_str(buffer, "Hellow!");
    ptr = buffer;
    
    printf("str: %s\n", ptr);
    
    return NSApplicationMain(argc,  (const char **) argv);
}

main을 봐도 알수 있듯이 C문법이 오류없이 컴파일 되며, 실행을 하게 되면 빈 윈도우와 함께 로그 윈도우에 str: Hello!를 출력합니다.

이 예를 들은 이유는 Objective-C가 C와 전혀 별개의 언어가 아닌 C언어의 확장으로 봐도 무방할 것 같습니다. 위에 언급한 바와 같이 C언어 또는 C++ 사용이 가능하면, 작은 노력으로도 쉽게 Objective-C를 사용할 수 있습니다.

이제 다른 소스를 확인해 보겠습니다. 아래는 Xcode에서 제공되는 샘플소스의 헤더 파일과 소스파일의 일부분입니다.

@interface DotView : NSView {
    float radius;
}
// Standard view create/free methods
- (id)initWithFrame:(NSRect)frame;
- (void)dealloc;
@end

#import <Cocoa/Cocoa.h>
#import "DotView.h"

@implementation DotView

- (void)mouseUp:(NSEvent *)event {
    NSPoint eventLocation = [event locationInWindow];
    center = [self convertPoint:eventLocation fromView:nil];
    [self setNeedsDisplay:YES];
}
@end

위에 테스트로 작성된 소스와는 다르게 객체를 사용하기 때문에, 전혀 C와는 관련 없는 소스로 보입니다.

C++이나 Java등을 사용하신 분들은 class의 선언과 구현인지 대충 짐작이 가시겠지만, C 문법이라고 보기엔 import, -, @, [], : 등 많은 부분이 눈에 거슬립니다. 아래에서 확연히 다른 점을 간단히 다루어 보겠습니다.

1) "@" 예약어

우선 "@"으로 시작되는 것은 Objective-C에서 추가된 예약어 입니다.

위에서 예를 보면 클래스의 선언부분은  @interface, @end로 구현부분은 @implementation, @end의 사이에 위치합니다.

또한 "hello"는 char* 형의 문자열을 의미하지만, @"hello"는 NSString에서 사용하는 문자열(참고로 @""는 아스키 코드만 가능하며, 한글은 UTF8로 처리해야 합니다.)을 의미하는 것과 같이 기존 C의 문법과 구별이 필요할 때에 사용한다고 보시면 됩니다..

2) 함수 선언

헤더파일을 보면 클래스의 메소드 선언 시 C++/Java와는 달리 클래스 선언 구역({})의 외부에서 선언 됩니다.

위의 - (id)initWithFrame:(NSRect)frame; 선언을 예로 들어 보겠습니다.

함수 앞에는 "-" 표시가 있는데 이는 인스턴스 메소드를 나타내며, "+"일 경우에는 클래스 메소드를 나타냅니다. "+"는 C++/Java의 static 맴버함수와 유사하여 인스턴스의 생성 없이 바로 사용할 수 있는 메소드입니다.

(id) 는 반환될 타입입니다. ()로 처리된다는 것만 제외하고 C와 동일합니다. 참고로 id는 모든 오브젝트를 가리키는 포인터입니다.

":"는 다음에 인자를 의미하며 인자가 ":(타입)이름"과 같이 나온다는 것을 의미합니다. 인자가 2개 이상일  경우에는 스페이스로 구분하며 아래와 같이 선언 합니다.

- (NSPoint)convertPoint:(NSPoint)aPoint fromView:(NSView *)aView;
보면 fromView라는 것이 혼돈을 주는데 인자의 별칭(alias)이라고 생각하시면 되고, 실제 호출 시에는 아래와 같이 사용합니다.

[self convertPoint:eventLocation fromView:nil];
이렇게 함으로써 소스코드는 길어 지지만, 메소드와 인자의 용도를 명확하게 합니다.

여기에는 없지만 변수앞에 사용하는 IBOutlet과 메소드에 사용하는 IBAction란 예약어가 있습니다. 이는 인터페이스 빌더에서 참조를 위한 것으로 변수와 메소드 타입에 영향을 주지 않습니다.  VC의 AFX_ 류로 생각하시면 됩니다.


3) 메소드 호출

center = [self convertPoint:eventLocation fromView:nil];
소스파일에 있는 위의 코드를 C++로 변경하면 아래와 같습니다.

center = this->convertPoint(eventLocation, NULL);

Objective-C에서는 메소드 호출(정확히는 메세지 전달)시 에는 [object method]의 형태로 사용됩니다. [object method:[object method]]와 같이 중첩해서 사용이 가능하며, 초기에는 혼돈이 오지만 자주 보면 object->method(object->method)와 같이 친숙해 집니다.


4) 프레임워크

라이브러리와 유사한 의미로 프레임워크라는 용어를 사용합니다. cocoa 프로젝트를 생성하면 Xcode 좌측의 Groups & Files에서 FrameWorks란 폴더를 찾을 수 있습니다.

 하단의 Other Frameworks를 보면 AppKit.framework와 Foundation.framework를 확인하실 수 있습니다. 이는 VC에서 MFC 클래스 라이브러리와 유사합니다.

Foundation은 NSObject, NSString, NSArray등의 기본적인 클래스들로 구성이 되어 있으며, AppKit은 NSWindow, NSButton, NSImage등 사용자 UI에 관련된 클래스들로 구성되어 있습니다.
 
사용자 삽입 이미지
좌측과 같이 이 두 framework 아래의 headers를 클릭해 보시면 cocoa 개발을 위한 기본적인 클래스 목록들을 확인하실 수 있습니다.






5) 기타

- Objective-C는 C++/Java와는 달리 class에 생성자/소멸자가 없지만, 이를 대치해서 사용할 수 있는 메소드와 이벤트가 있습니다.

- retain이라는 사용 카운터를 사용하여 오브젝트가 메모리에서 삭제되는 시기를 결정합니다. 인스턴스가 추가(alloc)되면 retain이 증가되고, 사용이 완료되면 release라는 메소드로 retain을 감소 합니다. retain이 0이 되면 삭제됩니다.

- Nib 파일 - NeXT Interface Builder의 약자로 오브젝트, 클래스, 리소스, 컨넥션, UI등의 정보와 파일을 가지고 있는 cocoa 어플리케이션에서는 매우 중요한 역활과 의미를 가지고 있는  파일 입니다.

이상으로 마치며... 프로그래밍 언어를 한번 정도 다루어 보신 분들, 특히 C++, Java라면 Objective-C는 쉽게 접근할 수 있는 언어입니다. C에 객체지향을 더했다는 측면에서, C++과 만들어진 이유와 나온 시기도 비슷하여 서로 비교해 보는 것도 재밌습니다. Cocoa와 MFC, VC++과 Xcode도 그렇고요.

앞으로 맥사용자들이 많이 늘어, C++ 사용자 처럼 Objective-C 사용자들이 많이 늘었으면 하는 바램입니다.
 

'Xcode 2 > Objective-C' 카테고리의 다른 글

Objective-C 코딩 스타일  (6) 2008.09.25
Objective-C class의 특징  (2) 2008.03.17
C/C++ 사용자를 위한 간단한 Objective-C 소개  (17) 2007.05.14

1.5.0  프로젝트 개요
이번에는 NSView를 이용하여 도형들과 텍스트를 출력하는 예제를 만들어 보겠습니다. 아래와 같이 각각의 버튼들을 클릭하면 각각의 내용들을 출력하는 간단한 프로그램 입니다.
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지
사용자 삽입 이미지






1.5.1  프로젝트 생성

이전 포스트에서 경험 해 본 코코아 프로젝트와 소스파일을 생성하고, 인터페이스 빌더와 연결 할 수 있다는 전제 하에 설명하겠습니다. 이 부분에 이해가 안되시면 1.2와 1.3 포스트를 해보시고 오시기 바랍니다.

Xcode를 실행하고 아래와 같이 SimpleDraw 코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. roject Name에 SimpleDraw를 입력하고 finish 버튼을 클릭합니다.

1.5.2 인터페이스 빌더에서 작업
1) NSView 서브클래스 만들기

사용자 삽입 이미지
Xcode에서 MainMenu.nib를 더블클릭하여 인터페이스 빌더를 실행합니다. 좌측과 같이 Classes 에서 NSObject > NSResponder > NSview를 우클릭(또는 control + 클릭) 하여 메뉴에서 Subclass NSView를 선택 합니다.


사용자 삽입 이미지
MyView라는 서브 클랙스가 생성됩니다. 여기서는 MyView라는 이름을 사용하지만, 용도에 맞는 이름으로 변경하셔도 됩니다.







사용자 삽입 이미지
다음은 MyView에 그리기 명려을 처리할 메소드를 추가합니다. 좌측과 같이 MyView를 우클릭 하여 Add Action to MyView를 선택 합니다.




 
사용자 삽입 이미지
좌측과 같이 기본으로 만들어진 myAction을 drawItem으로 변경합니다.







사용자 삽입 이미지
이제 소스파일을 생성하기 위해 MyView 클래스를 다시 우클릭하여 Create Files for MyView를 선택합니다.

[Choose] 버튼을 클릭하여 MyView.h와 MyView.m 파일을 생성합니다.



2) NSView 속성 설정
 
이제는 NSView와 버튼들을 배치하고 속성들을 설정해 보겠습니다. 아래와 같이 윈도우를 열고 팔레트에서 여섯번째 메뉴인 Containers를 선택하고, 윈도우로 CustomView를 드래그 하여 가지고 옵니다.
사용자 삽입 이미지

사용자 삽입 이미지
윈도우에서 CustomView가 선택된 상태에서 [shift + command + i]를 눌러 인스펙터를 오픈한 후, 상단에서 Custom Class를 선택합니다.

또는 [command + 5]를 눌러서 바로 Custom Class를 오픈해도 됩니다.

Class 항목에서 이전에 만들어 놓은 MyView를 선택합니다.


사용자 삽입 이미지
완료되면 CustomView가 MyView로 변경됩니다. MyView의 사이즈를 좌측과 같이 적절하게 조절 합니다.

상단의 Window에서 SimpleDraw로의 변경은 이전 장에서 해본 것과 같이 윈도우의 인스펙터를 불러내어 Window Title 항목을 변경합니다.






3) 버튼 생성 및 속성 설정

이제 버튼을 배치합니다. 아래와 같이 팔레트에서 버튼을 불러와 버튼을 복사/붙여넣기 하여 네개의 버튼을 만든 후 MyView 하단에 배치 합니다.
사용자 삽입 이미지

사용자 삽입 이미지
좌측과 같이 네개의 버튼의 제목을 각각 사각형, 원, 선, 문자로 변경합니다.

자세히 보면 버튼의 모양이 기본 버튼과 다른데, 이는 아래에서 설명하겠습니다.









사용자 삽입 이미지
사각형 버튼에서 인스펙터를 열어 보면, Tag에 1로 되어 있습니다. 이는 각각의 버튼이 클릭되었을 때, MyView의  DrawItem 메소드에서 구별하기 위해 설정 합니다.

사각형은 1, 원은 2, 선은 3, 문자는 4로 Tag 값을 각각 설정 합니다.

위의 버튼 모양은 Type 항목에서 Round Textured Button을 선택한 모양 입니다.


이제 버튼 클릭시 동작을 연결합니다. 이전 장과 같이 사각형 버튼을 control키를 누른 상태에서 드래그 하여 MyView에 가져다 놓습니다. 아래와 같이 인스펙터 창이 나타나면 Target/Actiond에서 drawItem을 선택하고 하단의 Connect 버튼을 클릭합니다.
사용자 삽입 이미지
위의 작업을 원, 선, 문자 버튼에서도 똑같이 drawItem에 연결 하여 줍니다.


1.5.3 소스코드 작성

1) 헤더파일 수정

이제 Xcode로 돌아 와서 소스코드를 수정합니다. 우선 MyView.h를 에디터에서 오픈합니다.

#import <Cocoa/Cocoa.h>

@interface MyView : NSView
{
    int itemType;
   
    NSString *txt_message;
    NSMutableDictionary *txt_attributes;
}
- (IBAction)drawItem:(id)sender;
@end

헤더파일에는 아래와 같이 세가지 속성들이 추가되었습니다.

int itemType;
어떤 방식으로 그릴지 설정을 저장할 변수 입니다. 위에서 버튼들의 인스펙터에서 설정 한 tag값(1, 2, 3, 4)들이 저장됩니다.

NSString *txt_message;
출력할 문자를 저장합니다.

NSMutableDictionary *txt_attributes;
폰트, 색상 등 문작의 출력 속성을 저장 합니다.

2) 소스 파일 변경 사항

이제 MyView.m을 에디터에서 오픈합니다. 이번에는 소스가 조금 길어 졌습니다.

#import "MyView.h"

@implementation MyView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
        itemType = 0;
        
        txt_message = [[NSString alloc] initWithString:@"Hello!!!"];
        txt_attributes = [[NSMutableDictionary alloc] init];
        [txt_attributes setObject:[NSColor blueColor]
                                   forKey:NSForegroundColorAttributeName];
    }
    return self;
}

- (void)dealloc {
    [txt_message release];
    [txt_attributes release];
    [super dealloc];
}

- (void)drawRect:(NSRect)rect
{
    const int ITEM_WIDTH = 50;
    const int ITEM_HEIGHT = 50;
   
    NSRect itemRect;
    NSBezierPath *path;
        
    itemRect.origin.x = (rect.size.width/2) - (ITEM_WIDTH/2);
    itemRect.origin.y = (rect.size.height/2) - (ITEM_HEIGHT/2);
   
    itemRect.size.width = ITEM_WIDTH;
    itemRect.size.height = ITEM_HEIGHT;

    switch (itemType) {
    case 1:
        path = [NSBezierPath bezierPathWithRect:itemRect];
        [path fill];
        break;
    case 2:
        path = [NSBezierPath bezierPathWithOvalInRect:itemRect];
        [path fill];
        break;
    case 3:
        path = [NSBezierPath bezierPath];
        [path moveToPoint:itemRect.origin];
        [path lineToPoint:(NSPoint) { itemRect.origin.x + itemRect.size.width, itemRect.origin.y + itemRect.size.height }];
        [path setLineCapStyle: NSButtLineCapStyle];
        [path setLineWidth: 3];
        [path stroke];
        break;
    case 4:
        [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
        break;
    default:
        break;
    }
}

- (IBAction)drawItem:(id)sender
{
    NSLog(@"%d", [sender tag]);
   
    itemType = [sender tag];        
    [self setNeedsDisplay:YES];
}

@end

3) 오브젝트 생성과 해제

자동으로 생성된 initWithFrame 메소드는 이름에서 짐작하듯이 NSView가 처음 초기화 될 때 자동으로 호출됩니다. 주석 설명(// Add initialization code here)과 같이 초기화할 내용들을 아래와 같이 입력합니다.

itemType = 0;

초기에 아무것도 그려지지 않도록 0으로 넣습니다.
      
txt_message = [[NSString alloc] initWithString:@"Hello!!!"];
출력할 텍스트 오브젝트를 생성하고 "Hello!!!"란 문구로 초기화 합니다.

txt_attributes = [[NSMutableDictionary alloc] init];
[txt_attributes setObject:[NSColor blueColor]
                            forKey:NSForegroundColorAttributeName];
텍스트 속성 오브젝트를 생성하고, 폰트칼라만 파란색([NSColor blueColor])으로 설정합니다.

- (void)dealloc {
    [txt_message release];
    [txt_attributes release];
    [super dealloc];
}
dealloc은 오브젝트 사용이 끝나고 메모리에서 해제될 때 호출됩니다. alloc으로 메모리를 할당한 오브젝트들(txt_message, txt_attributes)을, release로 메모리를 해제해 줍니다.

4) 그리기

drawRect는 NSView가 그려져야 할 때, 자동으로 호출됩니다. 그리는 작업을 이곳에서 하면 됩니다.

const int ITEM_WIDTH = 50;
const int ITEM_HEIGHT = 50;
그려질 도형들의 크기를 설정합니다.
  

NSRect itemRect;
NSBezierPath *path;
        
itemRect.origin.x = (rect.size.width/2) - (ITEM_WIDTH/2);
itemRect.origin.y = (rect.size.height/2) - (ITEM_HEIGHT/2);
itemRect.size.width = ITEM_WIDTH;
itemRect.size.height = ITEM_HEIGHT;
그려질 도형들의 위치와 크기를 설정합니다. 인자로 넘어온 rect는 그려져야 할 영역인데 NSView의 크기로 보시면 됩니다. NSView의 중앙에 오도록 계산하여 x, y를 설정 합니다.
 

switch (itemType) {
case 1:
    path = [NSBezierPath bezierPathWithRect:itemRect];
    [path fill];
    break;
case 2:
    path = [NSBezierPath bezierPathWithOvalInRect:itemRect];
    [path fill];
    break;
case 3:
    path = [NSBezierPath bezierPath];
    [path moveToPoint:itemRect.origin];
    [path lineToPoint:(NSPoint) { itemRect.origin.x + itemRect.size.width, itemRect.origin.y + itemRect.size.height }];
    [path setLineCapStyle: NSButtLineCapStyle];
    [path setLineWidth: 3];
    [path stroke];
    break;
case 4:
    [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
    break;
default:
    break;
} 
itemType에 따라 NSBezierPath를 사용하여 사각형, 원, 직선과 문자를 출력합니다. 상세한 설명은  튜토리알 이 후에 다시 하겠습니다. 용도는 오브젝트, 변수, 메소드 명을 보시면 대충 짐작하실 수 있습니다.

다만 [txt_message drawAtPoint:(NSPoint) { 20, 20 } withAttributes:txt_attributes];
를 보시면 x, y 20, 20으로 출력하는데 생각과는 달리 좌측 상단에 나오지 않고 좌측 하단에 출력 됩니다. 이는 기본 좌표 체계가 좌측 하단을 기준으로 시작하기 때문입니다.

5) 사용자 입력 처리

- (IBAction)drawItem:(id)sender
{
    NSLog(@"%d", [sender tag]);
   
    itemType = [sender tag];        
    [self setNeedsDisplay:YES];
}
버튼이 클릭될 때, 호출되는 메소드 입니다. 네개의 버튼이 연결되어 잇지만 [sender tag]로 현재 어느 버튼이 클릭되었는지 알 수 있습니다.

[self setNeedsDisplay:YES];
NSView가 다시 그려져야 됨을 알립니다. drawRect가 호출됩니다.

6) 마무리

이제 빌드 후 실행하고 테스트 해 봅니다. 오류가 나거나 정상적으로 동작하지 않으면 위의 내용을 확인해 보시기 바랍니다.

완전한 이해를 위해서는 Object-c의 문법과 메소드 기타 상세한 설명이 필요 하지만, 단순히 따라 해보는 튜토리알에 의미를 두었습니다. 추후 튜토리알과 다른 항목에서 설명하도록 할려고 합니다.

또한 결정적인 이유는 송구스럽게도 저도 대충 이해하고 구현에 중점을 두고 해보기 때문입니다.