새로운 언어를 익힐 때는 보통 해당 언어의 코딩스타일 가이드라인을 찾아 보거나, 원 제작자(사)의 문서나 예제에 사용된 소스의 코딩 스타일을 사용하는 경우가 많습니다.

저는 아직까지 Objective-C에 익숙하지 않아서 시작할 때 부터 애플에서 많이 사용하고 권장하는 형식으로 습관을 들일려고 하고 있습니다. 그런데 사소한 것이지만 애플의 샘플코드나 Xcode에서 생성해 주는 소스코드를 보면 조금씩 차이를 보이며, 이는 같은 코드 내에서도 스타일이 조금씩 다른 경우를 볼 수 있습니다.

* 스타일 1
/Developer/Examples/QuickTime/QTKit/QTKitPlayer/MovieDocument.m
-  (BOOL)panel:(id)sender shouldShowFilename:(NSString *)filename
{
    if (isDir)
    {
        return YES;
    }
}
'{'과 '}'이 한라인을 차지하는 스타일은 Xcode의 C++을 사용하는 카본에서도 많이 볼 수 있습니다. 샘플 코드중에도 MS VC++의 MFC를 보는 듯한 코드를 자주 볼 수 있습니다. 아마 애플의 기존 C++ 프로그래머가 작성하지 않았나 하는 추측이 듭니다.

하지만 애플의 일반적인 코코아에 관련된 문서나 Xcode에서 생성해 주는 코드에는 이런 스타일은 흔하지 않은 것 같습니다.

* 스타일 2
/Developer/Examples/Quartz Composer/Applications/ImageFX/AppController.m
-  (BOOL)  performDragOperation:(id<NSDraggingInfo>)sender
{
    if(_sourceRef) {
        if(imageRef = CGImageSourceCreateImageAtIndex(_sourceRef, 0, NULL)) {
        }
    }
}
메소드의 '{'는 한 라인을 차지하고 메소드 내에서의 '{'는 같은 라인에서 사용하고 있습니다. if문 바로 뒤에 '('를 공백없이 사용하는 것과 타입(BOOL)과 함수명 사이에 공백을 둔 것을 제외하면 흔히 볼 수 있는 스타일 같습니다. 많은 예제와 Xcode가 생성한 코드, 그리고 많이들 보시는 Cocoa Programming for mac os x의 소스에서도 이 스타일을 사용하고 있습니다.

* 스타일 3
-  (void)setTitle:(NSString *)newTitle {

    if (instanceVar != newTitle) {
        [instanceVar release];

        instanceVar = [newTitle copy];
    }
}
요즘 나오는 예제들과 코드에서는 주로 위와 같이 사용하는 경우가 많은 것 같습니다. 위의 코드는 Coding Guidelines for Cocoa 문서의 뒷부분에서 발췌한 것인데, 아마 애플에서는 저런 스타일을 지향하는 것 같습니다.

제가 주의 깊게 보는 곳은 메소드의  '-' 뒤에 공백이 오는지와 반환타입과 메소드명 사이에 공백이 오는지의 여부인데요. 위와 같이 '-' 뒤에는 공백을 두고 나머지는 공백 없이 쓰는 것이 더 많은 것 같습니다. 
 
개인적으론 2번째 스타일에다 if 뒤에 공백을 하나 두는 방식이 편한 것 같은데, 더 알아 보면서 남들이 많이 쓰는 형식으로 쓸려고 합니다.

Xcode의 샘플이나 생성된 코드의 스타일에 조금씩 차이를 보이는 것이 애플이 아주 소소한 부분은 개발자들의 취향에 맡기는 것인지, 각자의 고집이 반영된 것인지는 잘 모르겠습니다.

코코아에서 Objective-C의 코딩스타일 가이드라인은 ADCCoding Guidelines for Cocoa 문서를 참조하실 수 있습니다.

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

Objective-C class의 특징  (2) 2008.03.17
C/C++ 사용자를 위한 간단한 Objective-C 소개  (17) 2007.05.14
AND

WebKit에 포함된 WebView를 이용하여 초 간단한 웹브라우저를 만들어 보겠습니다.
(프로젝트 생성과 인터페이스 빌더에서 작업은 이전에 많이 다루었기 때문에 여기서는 자세한 설명은 생략하였습니다. 'Cocoa 따라해보기' 분류의 초기 포스트에 관련된 설명이 있습니다.)

0. WebKit
WebKit은 애플의 대표적인 오픈소스 프로젝트 중에 하나 입니다. 대표적인 사파리뿐만 아니라 아도비 에어, 아이폰, 노키아의 폰부라우져, 구글 안드로이드 플랫폼등 여러 프로젝트에서 사용되었습니다. (출처:WikiPedia/WebKit)

Webkit은 KDE의 웹브라우져 퀀커러의 엔진인 KHTML과 KJS에서 시작한 발전해 왔습니다. 주개발자는 Dave Hyatt로 넷스케이프에서 모질라를 개발하다 2002년에 애플에 합류해 현재 사파리, Webkit팀의 책임자로 있는 것으로 보입니다. 초기 파이어폭스, Camino와 탭브라우징, XBL/XUL을 만들고 W3C에도 많은 영향력이 있는 것 같습니다.

Webkit에 대한 상세한 정보는 WebKit 홈페이지에서 확인하실 수 있습니다.

1. WebKit 프레임워크 추가
Xcode의 New Project에서 Cocoa Application으로 프로젝트를 하나 만듭니다. WebKit 프레임워크를 추가하기 위하여 Groups & Files에서 우클릭하여 Add > Existing Frameworks를 선택합니다.

사용자 삽입 이미지

사용자 삽입 이미지
선택창에서 /System/Library/FrameWorks에 있는 WebKit.framework를 선택합니다. 좌측과 같이 WebKit.framework가 포함되어 있는 것을 확인할 수 있습니다.


2. 소스코드 편집

AppController이란 이름으로 Objective-C 클래스를 생성합니다. 아래와 같이 입력합니다.

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

@class WebView;

@interface AppController : NSObject {
    IBOutlet WebView *webView;
    IBOutlet NSTextField *inputUrl;
}

- (IBAction)goURL:(id)sender;

@end

2) AppController.m
#import "AppController.h"
#import <WebKit/WebKit.h>

@implementation AppController

-(void)awakeFromNib
{
    /* 기본 주소 설정 */
     NSURL *url = [NSURL URLWithString:@"http://www.cocoadev.co.kr"];
    [[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:url]];
}

- (IBAction)goURL:(id)sender
{
    /* 사용자 입력 주소 Load */
    [[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:
        [NSURL URLWithString:[sender stringValue]]]];
}
@end

3. 인터페이스 빌더
Xcode의 AppController.h를 MainMenu.nib로 드래그 해서 가져다 놓습니다. Classes 항목의 AppController를 우클릭하여 Instantiate AppController를 선택하여 인스턴스를 생성합니다.

팔레트에서 NSTextField와 CustomView를 드래그해서 아래와 같이 윈도우에 배치합니다.
사용자 삽입 이미지

1) NSTextField 속성 변경
사용자 삽입 이미지
NSTextFiled는 윈도우 크기가 변경되더라도 위치를 유지하면서 가로 크기만 변하도록 설정하겠습니다.

NSTextField의 속성창중 Size메뉴(단축키: Command+3)에서 AutoSizing 속성을 좌측과 같이 변경합니다.


2) CutomView 속성 변경
사용자 삽입 이미지
웹컨텐츠를 보여주는 View는 윈도우의 크기에 맞춰 변환되도록 AutoSizing을 좌측과 같이 설정합니다.

그리고 Custom Class 항목(단축키: Command+5)에서 WebView를 선택합니다.


3) AppController와 연결
이제 AppController의 inputUrl을 NSTextField로 webView를 webView로 연결합니다.  그리고 Action에서 NSTextField는 goURL로 연결합니다.

사용자 삽입 이미지

4. 테스트 or More..
빌드 후에 실행하면 아래와 같습니다. 입력창에 URL을 입력하면 다른 사이트로 연결됩니다.
사용자 삽입 이미지

More...
Developer/Examples/WebKit/MiniBrowser에 히스토리가 저장되고 앞으로, 뒤로 가기, 리로드, 정지, 폰트 확대/축소 등의 기능이 추가된 예제가 있습니다. 그외 ADC의 WebKit과 관련된 아래의 문서들이 도움이 되실 것입니다.
AND

NSFileManager로 파일과 디텍토리를 복사/이동/삭제하는 간단한 예제 소스입니다. 각 단계는 enter를 로그창에 입력하면 진행되면 아래와 같은 순서로 작업을 합니다.

  1. test 디렉토리 생성
  2. test 디렉토리를 test2로 변경
  3. test.txt 파일을 test2 디렉토리 아래에 new_test.txt로 복사
  4. test2 디렉토리로 이동
  5. new_test.txt를 re_test.txt로 변경
  6. re_test.txt 삭제
  7. 상위 디렉토리로 이동
  8. test2 디렉토리 삭제
테스트 전에 실행파일이 있는 디렉토리에 test.txt란 파일을 vi나 편집기를 이용해 만들어 놓으셔야 합니다.

아래 이미지의 좌측은 Log창에서의 진행화면이며 우측은 터미널에서 진행화면입니다. 터미널에서 확인 후에 로그 창에서 [enter]를 입력하면서 한단계씩 진행합니다.

사용자 삽입 이미지

아래는 소스파일입니다. 별다른 내용이 없으므로 간단한 주석으로 설명을 대치하였습니다.

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

    NSFileManager *FileManager;
    FileManager = [NSFileManager defaultManager];

    /** 현재 디렉토리에서 test란 디렉토리를 생성 */
    if ([FileManager createDirectoryAtPath:@"test" attributes:nil] == NO ) {
        NSLog(@"Fail to create directory");
        return 0;
    }
    NSLog(@"create directory [press return]");
    getchar();
   
    /** 현재 디렉토리에 test.txt 파일이 있는지 검사 */
    if ([FileManager fileExistsAtPath:@"test.txt"] == NO) {
        NSLog(@"test.txt file not exist");
        return 0;
    }
   
    /** 생성된 test 디렉토리를 test2로 변경 */
    [FileManager movePath: @"test" toPath: @"test2" handler:nil];
    NSLog(@"move directory [press return]");
    getchar();
   
    /** test.txt 파일을 test2 밑에 new_test.txt로 복사 */
    [FileManager copyPath: @"test.txt" toPath: @"./test2/new_test.txt"
        handler:nil];
    NSLog(@"copy file [press return]");
    getchar();
   
    /** 현재 디렉토리를 test2로 이동 후에 new_test.txt를 re_test.txt로 변경 */
    [FileManager changeCurrentDirectoryPath: @"test2"];
    [FileManager movePath: @"new_test.txt" toPath: @"re_test.txt"
        handler:nil];
    NSLog(@"move file [press return]");
    getchar();
   
    /** re_test.txt 파일 삭제 */
    [FileManager removeFileAtPath: @"re_test.txt" handler:nil];
    NSLog(@"delete file [press return]");
    getchar();
   
    /** 현재 디렉토리를 이전 디렉토리로 이동후에 test2 디렉토리 삭제 */
    [FileManager changeCurrentDirectoryPath: @".."];
    [FileManager removeFileAtPath: @"test2" handler:nil];
    NSLog(@"delete directory");
   
    [pool release];

    return 0;
}
AND

사용자 삽입 이미지
샤크는 프로그램의 성능 측정 도구(프로파일러)입니다. 성능 튜닝은 사냥꾼의 동물적인 감각이 필요하고 상어가 먹이를 추적하는데 가장 앞선 동물이라 샤크라는 이름을 지었다고 합니다.

벌레들을 잡아 먹는 사마귀(mantis)로 이름을 지은 버그 추적 시스템처럼 상어(shark)도 재미있는 이름 같습니다.



0. 준비
실행은 /Developer/Applications/Performans Tools/Shark를 클릭하거나, Xcode의 Debug 메뉴에서 Lauch Using Performans Tool에서 Shark를 클릭하여 실행 합니다.

만약 해당 디렉토리에 Shark가 없다면 Xcode 설치시 CHUD Tools를 선택하지 않았기 때문입니다. Xcode 설치 파일을 이용해서 Shark를 설치하면 됩니다.

1. 테스트 코드 작성

테스트를 위해서 Xcode에서 아래와 같은 샘플코드를 작성합니다.

#import <Foundation/Foundation.h>

@interface Musik1 : NSObject {
}
@end
@implementation Musik1
-(void) run {
    int i, n;
    for (i = 0; i < 100; i++) {
        n = i * i;   
    }
}
@end

@interface Musik2 : NSObject {
}
@end
@implementation Musik2

 - (void) run {
    int i, n;
    for (i = 0; i < 1000; i++) {
        n = i * i;   
    }
}
@end

void Run()
{
    int i;
       
    for(i = 0; i < 10000; i++) {
        Musik1 *musik1 = [[Musik1 alloc] init];
        Musik2 *musik2 = [[Musik2 alloc] init];
       
        [musik1 run];
        [musik2 run];
   
        [musik1 release];
        [musik2 release];
    }
}

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

    // insert code here...
    Run();
   
    [pool release];
   
    return 0;
}

무식하게 돌아가는 코드를 작성해 보자 하고 생각없이 이름을 지었는데, 그냥 변경없이 포스팅에 사용하기로 했습니다. 나중에 다른 툴에서도 예제로 사용하기 위해 클래스를 두개 만들고 메모리를 할당하고 해제하는 코드를 넣었습니다.

소스는 10000번 돌면서 Musik1과 Musik2를 생성 해제 하면서 각각의 run 메소드가 실행됩니다. Musik1의 run 메소드는 다시 100번 돌고 Musik2는 더 무식해서 1000번 돌도록 하였습니다.

2. Shark 실행
이제 빌드를 하고 Xcode의 Debug 메뉴에서 Lauch Using Performans Tool/Shark를 클릭합니다. 아래와 같이 Shark가 실행된 모습을 보실 수 있습니다.

사용자 삽입 이미지

샤크에서 좌측의 Start 버튼을 클릭합니다. 아래와 같은 창이 뜨면 원하시는 옵션을 선택한 후 [OK]를 클릭합니다.

사용자 삽입 이미지

이제 프로그램이 런칭되어 분석을 시작합니다. 30초 내에 프로그램이 종료하지 않으면 자동으로 종료하고 분석된 결과를 보여줍니다. Config 메뉴에서 View Mini Config Editor를 선택하시면 실행 전에 제한시간과 다른 샘플링 옵션을 변경하실 수 있습니다.

사용자 삽입 이미지
재밌는 것은 분석중에는 아이콘에서 상어의 모습이 붉은 색으로 변합니다. 맹렬히 먹이감을 쫓고 있는 것 같습니다. 무섭습니다.


3. 샘플링 데이터 확인
1) 프로파일 브라우져
완료되면 아래와 같이 분석된 결과를 보여 줍니다. 우측 하단의 View 항목에서 표현 방식을 변경할 수 있습니다. Heavy는 항목별로 Tree는 말 그대로 트리 구조로 결과를 보여 줍니다. 아래의 이미지를 보시면 둘의 차이점을 확인하실 수 있습니다. 각 컬럼은 아래와 같습니다.

  • self - 행당 항목의 비율
  • Total - 하위 항목을 모두 포함한 전체 비율
  • Library - Symbol이 포함된 라이브러리
  • Symbol  - 심볼 (함수명)

사용자 삽입 이미지

위를 보시면 실행시간의 64.1%를 차지하는 [Musik2 run]을 중점적으로 점검해야 됨을 알 수 있습니다. [Musik2 run] 항목을 더블클릭하면 아래와 같이 해당 소스가 오픈됩니다. 우측 상단의 버튼들을 클릭하면 원본 소스, 어셈블리 또는 같이 볼수 있습니다.
 
2) 소스코드 브라우져
사용자 삽입 이미지

코드에서 직접 부하를 확인할 수 있습니다. [i] 아이콘이 있는 곳은 해당 코드에 대한 도움말이 있습니다. 아이콘을 클릭하면 전체 내용을 볼 수 있습니다. 아래는 Both 버튼을 클릭하여 원본 소스와 어셈블리를 같이 보여 주는 모습니다.

사용자 삽입 이미지

run 함수에서 'n=i * i(진한 갈색 부분)'에서 실행속도의 64.2%를 소비하고, 나머지 for 루프를 위한 'i++, i < 1000(연한 갈색)'에서 나머지를 소비하고 있는 것을 알 수 있습니다.

좌측의 'n = i * i' 에 해당되는PPC 어셈블리 코드를 간단히 살펴 보겠습니다. (우측 하단의 [Asm Help]를 클릭하시면 어셈블리 명령어 셋에 관한 간단한 설명을 보실 수 있습니다)

lwz r2, 28(r30) // 28(r30)의 메모리 값(i)을 r2 레지스터로 불러 옵니다.
lwz r0, 28(r30) // 28(r30)의 메모리 값(i)을 r0 레지스터로 불러 옵니다.
mullw r0,r2,r0 // r2, r0을 곱한 값을 다시 r0에 저장합니다.
stw r0, 24(r30) // 레지스터 r0의 값을 28(r30)의 메모리(n)에 저장합니다.

위는 제 PPC의 어셈블리 모습이며 인텔맥에서는 아래와 같습니다.
 
L3:
    .stabd    68,0,8
    movl    -16(%ebp), %eax
    imull    -16(%ebp), %eax
    movl    %eax, -12(%ebp)
    .stabd    68,0,7
    leal    -16(%ebp), %eax
    addl    $1, (%eax)
L2:
    cmpl    $99, -16(%ebp)

4) Chart 뷰
이제 Chart를 클릭하여 함수의 호출 경로가 기록되어 있는 call stack을 확인해 보겠습니다. Chart에서는 1ms마다 call stack을 기록한 내용을 그래프와 목록으로 확인할 수 있습니다. 그래프 또는 목록을 클릭하면 좌측에서 해당 시간의 call stack의 내용을 확인할 수 있습니다.
사용자 삽입 이미지

"우선 여기서 알수 있는 것은 제 맥이 엄청 느리다는 것입니다. 대부분의 맥에선 아래의 샘플링 시간이 보시는 것 보다 적게 나올 것입니다. 혹시 사용하는 맥이 너무 빨라 Shark가 붉게 변하는 모습을 못 보셨으면 반복 횟수를 늘리거나 다른 코드를 넣어 무식이의 run을 더 무식하게 만드시면 됩니다."

초기에 스택이 큰 것은 기동 시 초기화 루틴에서 부르는 것들입니다. 전체적으로 5로 나타나는 것은 대부분의 로드가 [Musik1(또는 2) run]일어 나기 때문에 위이미지의 좌측에서 보시는 것 처럼 start > _start > main > Run > [Musik1(또는 2) run]의 5단계이기 때문입니다.

우측 하단 View에서 CPU를 선택할 수 있는데 저는 PPC라 1개가 나오지만 인텔 코어듀오가 장착된 맥에선 2개의 CPU가 나올 것입니다.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Shark에서는 샘플링된 데이터를 파일로 저장할 수 있습니다. 현재 데이터를 저장하고 최적화를 수행하고 다시 Shark를 실행하여 이전 데이터와 비교해보며 최적화의 결과를 확인할 수 있습니다.

Shark에 대한 자세한 내용은 ADCShark User Guide 를 참조하시고, Shark 이외에 /Developer/Applications/Performans Tools/ 디렉토리에서 유용한 다른 툴들도 확인하실 수 있습니다.

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

주석내의 __MyCompanyName__ 변경  (2) 2008.03.04
다이알로그 윈도우 구현  (2) 2008.01.09
Xcode에서 디버깅 작업  (6) 2007.12.20
Xcode에서 Flex 개발하기  (2) 2007.12.08
Xcode에서 파이어폭스 플러그인 컴파일 하기  (0) 2007.12.04
AND

Objective-C class의 사용에서 눈여겨 볼만한 점들이 세가지 정도 있었습니다.  바로  Protocol, Category와 NSObject의 poseAsClass입니다.

Protocol 자바, C#의  interface와 유사하며 선언된 메소드들은 프로토콜을 사용하는 클래스에서 반드시 구현되어야 합니다. 사용하는 이유는 C++의 abstract class와 비슷하지만 Objective-C, 자바, C#과 같이 다중상속을 지원하지 않는 언어에서 다중상속의 단점은 버리고 장점은 취하기 위해서입니다. (이에 관한 자세한 설명은 디자인 패턴에 관한 책이나 deadly diamond of death로 구글 검색에서 찾으실 수 있습니다.)

Category는 서브클래스를 구현하지 않더라도 이미 구현되어 있는 클래스에 새로운 메소드들을 추가할 수 있는 방법을 제공합니다. category의 메소드들은 해당 클래스의 변수와 메소드를 사용할 수 있으며 메소드 이름이 같을 경우에는 오버라이드되니 주의하셔야 합니다. Categtory는 서브클래스를 만드는 것 보다 더 간단한 방법으로 문제를 해결할 수 있습니다.

PoseAsClass는 가장 흥미로운 부분입니다.  짧은 제 영어로 번역하자면 '사칭'이 가까운 것 같습니다. 상위 클래스로 선언되었지만 하위 클래스의 PoseAsClass를 이용하면 그 인스턴스는 하위 클래스로 동작하게 됩니다.

아래의 소스를 실행해 보시면 확인해 보실 수 있습니다.

#import <Foundation/Foundation.h>

/** Protocol **/
@protocol MyData
-(void) print;
@end

/** MyNumber class **/
@interface MyNumber: NSObject <MyData>
{
    int number;   
}

-(void) setNumber: (int)n;
-(void) print;
@end

@implementation MyNumber;
-(void) setNumber: (int)n
{
    number = n;
}

-(void) print
{
    NSLog(@"MyNumber:print > %d", number);
}
@end

/** Category **/
@interface MyNumber (NewPrint)
-(void) print;
-(void) printPlus;
@end

@implementation MyNumber (NewPrint)
-(void) print
{
    NSLog(@"NewPrint:print > %d", number);   
}

-(void) printPlus
{
    NSLog(@"NewPrint:printPlus > %d", number + 1);
}
@end

/** Posing **/
@interface MyNumberMinus: MyNumber
{
}
-(void) print;
@end

@implementation MyNumberMinus;
-(void) print
{
    NSLog(@"MyNumberMinus:print > %d", number - 1);
}
@end

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

    // insert code here...
    [MyNumberMinus poseAsClass: [MyNumber class]];
   
    MyNumber *number = [[MyNumber alloc] init];
   
    [number setNumber: 5];
   
    [number print];
    [number printPlus];
       
    [number release];
   
    [pool release];
    return 0;
}

사용자 삽입 이미지
실행결과는 좌측과 같습니다.


[MyNumberMinus poseAsClass: [MyNumber class]];
이 라인은 MyNumberMinus는 앞으로 MyNumber 클래스로 사칭하고 다닌다는 의미입니다. 그래서 아래의 MyNumber *number = [[MyNumber alloc] init]; 선언은 MyNumberMinus *number = [[MyNumberMinus alloc] init]; 와 동일한 의미로 해석될 수 있습니다. 이 라인을 주석처리하고 빌드 후에 결과를 비교하시면 쉽게 이해하실 수 있습니다.

poseAsClass가 유효하기 위해서는 상위클래스가 생성되기 전에 호출되어야 합니다.

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

Objective-C 코딩 스타일  (6) 2008.09.25
C/C++ 사용자를 위한 간단한 Objective-C 소개  (17) 2007.05.14
AND

Cocoa 파운데이션의 NSFileManager 클래스를 이용하여 특정 패스내의 파일 목록을 검색하고 파일들의 속성을 알아내는 방법입니다. XCode 도움말에서 NSFileManager를 보시면 디렉토리와 파일 관리에 관한 다양한 정보를 확인하실 수 있습니다.

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
   
    NSFileManager *fileManager;
    NSDirectoryEnumerator *directoryEnum;
    NSString* str;

    fileManager = [NSFileManager defaultManager];

    directoryEnum = [fileManager enumeratorAtPath:
        [fileManager currentDirectoryPath]];
   
    while ((str = [directoryEnum nextObject]) != nil) {
        NSDictionary *dic = [fileManager fileAttributesAtPath: str
                                                 traverseLink: NO];
       
        NSLog(@"%@: %@, %@byte, %@", str,
              [dic objectForKey: NSFileType],
              [dic objectForKey: NSFileSize],
              [dic objectForKey: NSFileOwnerAccountName]);
    }

    [pool release];

    return 0;
}

위의 소스를 빌드하고 실행하면 결과는 아래와 같습니다.

사용자 삽입 이미지

- (NSDirectoryEnumerator *)enumeratorAtPath:(NSString *)path
path에 지정된 디렉토리내의 모든 서브 디렉토리와 파일목록을 가져 옵니다.

- (NSDictionary *)fileAttributesAtPath:(NSString *)path traverseLink:(BOOL)flag
path에 지정된 파일 또는 디렉토리의 속성을 가지고 옵니다. flag를 YES로 지정하면 심볼릭 링크된 파일의 원본 파일의 정보를 가져오며, NO일 경우에는 링크된 파일의 정보를 가지고 옵니다.

속성은 NSDictionary의 objectForKey를 이용하여 각각의 속성을 가지고 올 수 있습니다. 각 속성에 대한 Key는 NSFileManager.h에 아래와 같이 정의되어 있습니다.

FOUNDATION_EXPORT NSString * const NSFileType;
FOUNDATION_EXPORT NSString * const NSFileTypeDirectory;
FOUNDATION_EXPORT NSString * const NSFileTypeRegular;
FOUNDATION_EXPORT NSString * const NSFileTypeSymbolicLink;
FOUNDATION_EXPORT NSString * const NSFileTypeSocket;
FOUNDATION_EXPORT NSString * const NSFileTypeCharacterSpecial;
FOUNDATION_EXPORT NSString * const NSFileTypeBlockSpecial;
FOUNDATION_EXPORT NSString * const NSFileTypeUnknown;
FOUNDATION_EXPORT NSString * const NSFileSize;
FOUNDATION_EXPORT NSString * const NSFileModificationDate;
FOUNDATION_EXPORT NSString * const NSFileReferenceCount;
FOUNDATION_EXPORT NSString * const NSFileDeviceIdentifier;
FOUNDATION_EXPORT NSString * const NSFileOwnerAccountName;
FOUNDATION_EXPORT NSString * const NSFileGroupOwnerAccountName;
FOUNDATION_EXPORT NSString * const NSFilePosixPermissions;
FOUNDATION_EXPORT NSString * const NSFileSystemNumber;
FOUNDATION_EXPORT NSString * const NSFileSystemFileNumber;
FOUNDATION_EXPORT NSString * const NSFileExtensionHidden;
FOUNDATION_EXPORT NSString * const NSFileHFSCreatorCode;
FOUNDATION_EXPORT NSString * const NSFileHFSTypeCode;
#if MAC_OS_X_VERSION_10_2 <= MAC_OS_X_VERSION_MAX_ALLOWED
FOUNDATION_EXPORT NSString * const NSFileImmutable;
FOUNDATION_EXPORT NSString * const NSFileAppendOnly;
FOUNDATION_EXPORT NSString * const NSFileCreationDate;
FOUNDATION_EXPORT NSString * const NSFileOwnerAccountID;
FOUNDATION_EXPORT NSString * const NSFileGroupOwnerAccountID;
#endif
#if MAC_OS_X_VERSION_10_4 <= MAC_OS_X_VERSION_MAX_ALLOWED
FOUNDATION_EXPORT NSString * const NSFileBusy;
#endif

AND

* NSFileManager : - (NSString *)currentDirectoryPath
    어플리케이션의 현재 디렉토리를 반환합니다.

* NSBundle  : - (NSString *)bundlePath
    어플리케이션의 번들 디렉토리를 반환합니다.

* NSString *NSHomeDirectory(void)
    현재 사용자의 홈 디렉토리를 반환합니다.

* NSString *NSHomeDirectoryForUser(NSString *userName)
    특정 계정(userName) 사용자의 홈디렉토리를 반환합니다.

* NSTemporaryDirectory()
    임시(temp)로 사용할 수 있는 디렉토리를 반환합니다.

* NSString *NSUserName(void)
    현재 사용자의 계정 이름을 반환합니다.

* NSString *NSFullUserName(void)
    현재 사용자의 전체 이름을 반환합니다.

#import <Foundation/Foundation.h>

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

    // insert code here...
    NSLog(@"Cur Path: %@",
        [[NSFileManager defaultManager] currentDirectoryPath]);
    NSLog(@"Bundle Path: %@", [[NSBundle mainBundle] bundlePath]);
   
    NSLog(@"Home Path: %@", NSHomeDirectory());
    NSLog(@"User Home Path: %@",
        NSHomeDirectoryForUser(NSUserName()));
    NSLog(@"Temp Path: %@", NSTemporaryDirectory());
   
    NSLog(@"User Name: %@", NSFullUserName()); 
   
    [pool release];
    return 0;
}

실행 결과는 아래와 같습니다.
사용자 삽입 이미지
AND

Xcode에서 생성해 주는 소스파일 상단의 주석을 보면 아래와 같이 __MyCompanyName__.로 되어 있는 부분이 있습니다.

사용자 삽입 이미지

터미널에서 아래와 같이 입력하시면 __MyCompanyName__을 변경하실 수 있습니다. 아래는 "cocoadev.co.kr"로 변경하는 예입니다.

> defaults write com.apple.Xcode PBXCustomTemplateMacroDefinitions -dict ORGANIZATIONNAME "cocoadev.co.kr"
또는
> defaults write com.apple.xcode PBXCustomTemplateMacroDefinitions '{ORGANIZATIONNAME = "cocoadev.co.kr" ; }'

이제 Xcode에서 소스 파일을 생성하면 아래와 같이 주석 부분이 변경되어 있습니다.

사용자 삽입 이미지

Xcode 2.5에서만 테스트 해보았고 3.X 버젼에서는 확인을 하지 못하였습니다.

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

Shark를 이용한 성능 최적화  (0) 2008.03.23
다이알로그 윈도우 구현  (2) 2008.01.09
Xcode에서 디버깅 작업  (6) 2007.12.20
Xcode에서 Flex 개발하기  (2) 2007.12.08
Xcode에서 파이어폭스 플러그인 컴파일 하기  (0) 2007.12.04
AND

유닉스 계열에서는 실제 파일뿐만 아니라 소켓, 파이프, 장치등 모든 것을 파일로 간주하고 관리합니다. (pipe에서의 사용예는 제 블로그의 pipe를 이용한 간단한 프로세스간의 통신에서 확인하실 수 있습니다.)

NSFileHandle은 코코아 파운데이션 프레임워크에 포함된 저수준의 File Descriptor와  그와 관련된 open, close, read, write등의 관련된 함수들의 래퍼 클래스입니다.

코코아에서는 겍체의 아카이브를 지원하고 xml등 데이터 타입에 따라 파일이나 URL로 부터 편리하게 읽고 쓸 수 있게 하는 클래스들이 있기 때문에, 일반적인 파일에 관련된 작업에서는 NSFileHandle을 사용하여 직접 파일을 제어할 경우는 그다지 많지 않습니다.

NSFileHandle은 데이터를 읽고 쓰는데 1바이트 바이너리로 데이터를 저장하는 NSData를 사용합니다. NSData는 많은 클래스들에서 용도에 맞게 변경하는 초기화 메소드를 제공하므로 필요에 따라 사용하시면 됩니다.

1. 파일에서 텍스트 읽기
기존에 존재하는 test.txt 텍스트 파일을 읽어 출력하는 간단한 예입니다.

* 파일 핸들러 얻기  
fileHandleForReadingAtPath로 읽기전용으로 파일을 오픈합니다. 실패시에는 nil을 반환합니다.

* 파일읽기
readDataToEndOfFile로 파일의 전체를 읽어 오고 readDataOfLength로 특정 크기만큼 읽어 올 수 있습니다.

* 파일닫기
사용을 완료하였을 경우에는 closeFile을 이용해서 열려 있는 파일을 닫습니다.

NSFileHandle *readFile;

readFile = [NSFileHandle fileHandleForReadingAtPath:@"test.txt"];
if(readFile == nil)
{
    NSLog(@"fail to read file");
    return 1;
}
   
NSData *data = [readFile readDataToEndOfFile];
NSString* text = [[NSString alloc] initWithData: data
                                                        encoding: NSUTF8StringEncoding];
NSLog(@"%@", text);

[text release];
[readFile closeFile];

2. 파일에 텍스트 쓰기
new.txt 텍스트 파일을 만들어 "new text..."란 텍스트를 입력하는 예입니다.

* 파일 생성
[[NSFileManager defaultManager] createFileAtPath:@"new.txt"
    contents: data attributes:nil];

두번째 인자인 contents에 data를 지정하여 data의 내용으로 파일이 생성됩니다. 빈 파일 생성시에는 nil로 설정합니다. 생성 후에 [writeFile writeData: data] 메소드를 이용하여 파일에 입력할 수 있습니다. NSFileManager에 관해서는 다음 포스팅에서 자세히 설명하겠습니다.

* NSFileHandle없이 파일 제어
[NSData dataWithContentsOfFile:@"new.txt"]

NSFileHandle을 거치지 않고 NSData에서 바로 파일을 읽어 올 수 있습니다. 반대로 저장도 가능합니다. 위에 언급한 것과 같이 클래스들이 파일에 관련된 메소드를 가지고 있습니다. NString도 NSData를 거치지 않고 아래와 같이 텍스트 파일에서 내용을 바로 읽어 올 수 있습니다.

NSString* readText =[NSString stringWithContentsOfFile:@"new.txt"
            encoding:NSUTF8StringEncoding error:NULL];

NSString* text = @"new text...";
NSFileHandle *writeFile;
NSData *data = [NSData dataWithBytes:[text cString]
    length:[text cStringLength]];   

[[NSFileManager defaultManager] createFileAtPath:@"new.txt"
    contents: data attributes:nil];

writeFile = [NSFileHandle fileHandleForWritingAtPath:@"new.txt"];
if(writeFile == nil)
{
    NSLog(@"fail to open file");
    return 1;
}
   
[writeFile closeFile];

/** 기록된 파일 확인 */
NSData* readData = [NSData dataWithContentsOfFile:@"new.txt"];
NSString* readText = [[NSString alloc] initWithData: readData
    encoding: NSUTF8StringEncoding];

NSLog(@"READ: %@", readText);

3. 기존 파일 변경
파일 포인터(offset) 이동
seekToFileOffset은 C에서 fseek, lseek와 같이 파일의 특정위치로 이동하게 해줍니다. 아래에 사용된 [writeFile seekToFileOffset: 2]는 파일 포인터를 두번째 바이트에 위치시키며 이후로 writeData로 기록할 때는 두번째 바이트 뒤로부터 파일에 쓰여집니다. 이와 유사하게 seekToEndOfFile는 파일의 마지막으로 파일포인터를 이동합니다.

아래는 위에서 생성한 new.txt 파일의 세번째 바이트 위치부터 test.txt 파일의 내용을 추가하는 예입니다.
NSFileHandle *readFile;
NSFileHandle *writeFile;

readFile = [NSFileHandle fileHandleForReadingAtPath:@"test.txt"];
if(readFile == nil)
{
    NSLog(@"fail to read file");
    return 1;
}

writeFile = [NSFileHandle fileHandleForWritingAtPath:@"new.txt"];
if(writeFile == nil)
{
    NSLog(@"fail to open file");
    return 1;
}

NSData *data = [readFile readDataToEndOfFile];

[writeFile seekToFileOffset: 2];
[writeFile writeData: data];

[readFile closeFile];
[writeFile closeFile];
AND

이번에는 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 어플리케이션을 만들어 보았습니다. 프로젝트 파일은 아래의 아이콘을 클릭하여 다운로드 받으실 수 있습니다.

'Xcode 2 > Cocoa 따라해보기' 카테고리의 다른 글

간단한 웹브라우저 만들기  (0) 2008.04.02
1.10 기본 콘트롤 사용법 (2)  (0) 2008.02.13
1.10 기본 콘트롤 사용법 (1)  (2) 2008.02.03
1.8 간단한 슈팅게임 (2)  (2) 2007.10.03
1.8 간단한 슈팅게임 (1)  (4) 2007.08.14
AND

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

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


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

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

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







AND

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

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



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 창에서 아래와 같이 메시지를 보내고 받는지 확인합니다.

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

AND

다이알로그는 MS 윈도우의 MessageBox와 같이 간단한 메시지 출력이나 사용자로 부터 선택사항을 입력 받는 윈도우입니다. cocoa에서 다이알로그를 구현하는 방법에 대해서 간단하게 알아 보겠습니다.


1. NSAlert

사용자 삽입 이미지

AppKit의 NSAlert은 위와 같이 간단한 경고창을 출력하게 해주는 기본 클래스 입니다. 아래는 NSAlert을 사용하여 위와 같은 알림창을 뛰우는 간단한 예입니다.

int returnValue;
NSAlert *alert = [[NSAlert alloc] init];
   
[alert addButtonWithTitle:@"button1"];
[alert addButtonWithTitle:@"button2"];
[alert addButtonWithTitle:@"button3"];
[alert setMessageText:
        [NSString stringWithUTF8String:"알림입니다."]];
[alert setInformativeText:
        [NSString stringWithUTF8String:"이알림은 테스트용 입니다."]];
[alert setAlertStyle:NSWarningAlertStyle];
  
returnValue = [alert runModal];
   
[alert release];

1) addButtonWithTitle:
버튼을 추가합니다.

2) setMessageText:
출력될 메시지를 설정합니다.

3) setInfomativeText:
메시지에 대한 상세 설명을 설정합니다.

4) setAlertStyle:
경고창의 아이콘을 결정합니다. 아이콘의 종류는 아래와 같습니다.

> NSWarningAlertStyle
일반적인 경고창입니다.

> NSInformationalAlertStyle
NSWarningAlertStyle과 동일합니다. 하위 호환성을 위해서 존재하는 것 같습니다

> NSWarningAlertStyle
사용자 삽입 이미지
좌측과 같이 어플리케이션 아이콘 뒤로 경고 아이콘을 출력합니다.




5) runModal
경고창을 오픈합니다. 종료시에는 사용자가 클릭한 버튼 값을 반환합니다. 첫번째 버튼부터 아래와 같이 정의되어 있습니다. 버튼이 세개 이상일 경우에는 1003부터 순차적으로 증가된 값을 반환합니다.

enum =  {
    NSAlertFirstButtonReturn  = 1000,
    NSAlertSecondButtonReturn  = 1001,
    NSAlertThirdButtonReturn  = 1002
};

6) Help
사용자 삽입 이미지

위와 같이 경고창 좌측에 Help(?) 버튼을 두고 사용자가 클릭시 원하는 행동을 지정할 수 있습니다. NSAlert에서 아래와 같이 속성을 설정합니다.

[alert setShowsHelp:YES]

   경고창에 Help 버튼을 출력합니다.

[alert setDelegate:self];
   Help 버튼 클릭시 alertShowHelp 메시지를 받을 오브젝트를 설정합니다.

아래는 버튼 클릭 시 웹페이지 또는 html 파일을 오픈하는 예입니다.

- (BOOL)alertShowHelp:(NSAlert *)alert
{   
    NSURL *url = [NSURL URLWithString:@"http://www.cocoadev.co.kr"];
    return [[NSWorkspace sharedWorkspace] openURL:url];

/* 번들 리소스의 help.html에서 불러 옵니다. 

    NSString *path = [[NSBundle mainBundle]
            pathForResource:@"help" ofType:@"html"];
    return [[NSWorkspace sharedWorkspace] openFile:path];
*/   
}

NSAlert *alert = [[NSAlert alloc] init];
   
[alert addButtonWithTitle:@"button1"];
[alert addButtonWithTitle:@"button2"];
[alert addButtonWithTitle:@"button3"];
   
[alert setMessageText:
        [NSString stringWithUTF8String:"알림입니다."]];
[alert setInformativeText:
        [NSString stringWithUTF8String:"이알림은 테스트용 입니다."]];
[alert setAlertStyle:NSCriticalAlertStyle];

[alert setShowsHelp:YES];
[alert setDelegate:self];
   
returnValue = [alert runModal];
   
[alert release];


2. Sheet
사용자 삽입 이미지

Sheet은 위와 같이 윈도우의 타이틀바 밑으로 내려오는 판넬을 의미합니다. NSAlert의 beginSheetModalForWindow으로 구현할 수 있으며 사용예는 아래와 같습니다.

- (void)alertDidEnd:(NSAlert *)alert
           returnCode:(int)returnCode
          contextInfo:(void *)contextInfo
{
    NSLog(@"returnCode %d", returnCode);
}

NSAlert *alert = [[NSAlert alloc] init];
   
[alert addButtonWithTitle:@"button1"];
[alert addButtonWithTitle:@"button2"];
[alert addButtonWithTitle:@"button3"];
[alert setMessageText:
        [NSString stringWithUTF8String:"알림입니다."]];
[alert setInformativeText:
        [NSString stringWithUTF8String:"이알림은 테스트용 입니다."]];
[alert setAlertStyle:NSWarningAlertStyle];
   
[alert beginSheetModalForWindow:[NSApp mainWindow]
                      modalDelegate:self
                     didEndSelector:@selector(alertDidEnd:returnCode:contextInfo:)
                        contextInfo:NULL];  

1) Window
Sheet의 부모 윈도우를 설정합니다. 이 윈도우의 타이틀 바에서 Sheet가 오픈됩니다.

2) modalDelegate
Sheet의 메시지 처리를 위임 받을 오브젝트를 설정합니다. 이 오브젝트가 didEndSelector에서 설정된 메소드로 종료 메시지를 받습니다.

3) didEndSelector
Sheet가 종료될 때 불려지는 메소드입니다. 사용자의 선택에 따라 처리할 내용이 있을 경우, 여기서 처리합니다. 사용하지 않을 경우에는 nil로 설정합니다.

4) contextInfo
종료시 호출되는 메소드(didEndSelector)에 전달되는 사용자 정의 인자입니다.  종료 메소드에서 필요한 정보가 있을 경우에 설정합니다.


3. NSRunAlertPanel
NSRunAlertPanel 함수를 이용하면 NSAlert 클래스를 이용하는 것보다 편리하게 경고창을 구현할 수 있습니다. NSRunAlertPanel은 아래과 같이 선언되어 있습니다.

NSInteger NSRunAlertPanel (
  NSString *title,
  NSString *message,
  NSString *defaultButton,
  NSString *alternateButton,
  NSString *otherButton,
  ...
);

title, message,  세개의 버튼 타이틀을 인자로 가지고 있습니다. 사용하지 않는 버튼은 nil로 선언하면 출력되지 않습니다. 유의 하실 점은 마지막에 "..."로 가변인수를 가지고 있어 편리하게 사용할 수 있다는 점입니다.

이 가변인수는 message필드에 적용되며 실제 사용 예는 아래와 같습니다.
 
NSRunAlertPanel([NSString stringWithUTF8String:"알림입니다."],
      [NSString stringWithUTF8String:"이알림은 %d, %s 입니다."],
      @"default", @"alternate", @"other", 10, "test");

이를 실행하면 아래와 같습니다.
사용자 삽입 이미지

반환값은 아래와 같습니다. 오류(NSAlertErrorReturn)가 있을 경우를 제외하고는 각각 클릭된 버튼값을 반환합니다.

enum {
   NSAlertDefaultReturn = 1,
   NSAlertAlternateReturn = 0,
   NSAlertOtherReturn = -1,
   NSAlertErrorReturn = -2
};

NSAlert의 NSWarningAlertStyle과 같은 아이콘을 출력하실려면, NSRunCriticalAlertPanel을 사용하시면 됩니다.


4. NSBeginAlertSheet


사용자 삽입 이미지

 NSRunAlertPanel과 같은 사용법으로 NSAlert 클래스를 이용하는 것보다 편리하게 Sheet를 구현할 수 있습니다. NSBeginAlertSheet은 아래과 같이 선언되어 있습니다.

void NSBeginAlertSheet (
   NSString *title,
   NSString *defaultButton,
   NSString *alternateButton,
   NSString *otherButton,
   NSWindow *docWindow,
   id modalDelegate,
   SEL didEndSelector,
   SEL didDismissSelector,
   void *contextInfo,
   NSString *msg,
   ...
);

사용법은 이전의 예와 유사하며, 새로 추가된 인자만 설명하겠습니다.

1) didDismissSelector
Sheet가 사라될 때 불려지는 메소드이며, 사용하지 않을 경우에는 nil로 설정합니다.
didEndSelector보다 나중에 호출됩니다.

2) contextInfo
종료시 호출되는 메소드(didEndSelector, didDismissSelector)에 전달되는 사용자 정의 인자입니다.  종료 메소드에서 필요한 정보가 있을 경우에 설정합니다.

Sheet 사용 예는 아래와 같습니다.

- (void)sheetDidEndShouldDelete: (NSWindow *)sheet
                     returnCode: (int)returnCode
                    contextInfo: (void *)contextInfo
{
    NSLog(@"result %d", returnCode);
}

NSBeginAlertSheet([NSString stringWithUTF8String:"알림입니다."],
                      @"default",
                      @"alternate",
                      @"other",
                      [NSApp mainWindow],
                      self,
                      @selector(sheetDidEndShouldDelete:returnCode:contextInfo:),
                      NULL,
                      NULL,
                      [NSString stringWithUTF8String:"이알림은 %d, %s 입니다."],
                      10, "test");

NSAlertParnel과 동일하게 NSBeginCriticalAlertSheet을 사용하면 경고 아이콘을 출력할 수 있습니다.
AND

Xcode에서 디버깅을 하는 방법에 대해서 알아 보겠습니다.

기본적인 내용만 간단히 알아 보는 것이므로, 보다 상세한 내용은 Xcode 메뉴얼이나 gdb 메뉴얼을 참고해 주세요.


1. 소스코드 편집

Xcode를 실행하고 New Project에서 Command Line Utility/Foundation Tool로 새로운 프로젝트를 생성합니다. 전 testDebug로 하였습니다. 소스파일 (testDebug.m)을 열고 아래와 같이 편집합니다.

#import <Foundation/Foundation.h>

int plusNum(int a, int b)
{
    int c;
    
    c = a + b;
    
    return c;
}

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

    // insert code here...
    int i;
    char buff[10];
    
    for(i = 0; i < argc; i++)
    {
        NSLog(@"ARG: %d - %s", i+1, argv[i]);
    }
    
    NSLog(@"MY PATH: %s", getenv("MY_PATH"));
    
    for(i = 0; i < 10; i++)
    {
        buff[i] = '1' + i;
    }
    
    int a, b, c;
    a = 3;
    b= 5;
    
    c = plusNum(a, b);
       
    [pool release];
    return 0;
}


2. 실행인자, 환경변수 설정

빌드 후 실행하면 아래와 같이 ARG 값과 현경변수 MY_PATH의 값을 보실 수 있습니다. 실행 파일의 기본인자는 일반적으로 그 실행파일의 전체경로가 첫번째로 전달됩니다. (사용하시는 OS에 따라 간혹 다를 수 있습니다.) 임의의 MY_PATH 환경변수값은 설정되어 있지 않으므로 null로 출력되었습니다.

사용자 삽입 이미지

이제 디버깅 작업전에 프로그램 실행 시에 줄 수 있는 옵션들에 대해 간단히 알아 보겠습니다. Xcode 좌측의 Groups & Files에서 Executables항목에서 실행파일명을 더블클릭하여 정보창을 엽니다.

사용자 삽입 이미지
좌측과 같이 Arguments 항목 하단 좌측의 [+] 버튼을 눌러 Argument에는 "--all"을 입력합니다.

동일하게 아래의 환경변수창에서도 하단의  [+] 버튼을 눌러 Name에 MY_PATH를 Vale에 임의의 디렉토리를 입력합니다.

(참고로 세번째 Debugging 탭을 클릭하시면 사용하는 디버거, 기본 입출력, 원격 디버깅 등 상세한 디버거 정보를 설정할 수 있습니다. 일반적으로는 기본설정된 상태로 사용하시면 됩니다.)

이제 프로그램을 다시 실행시켜서 결과를 확인 합니다.





아래와 같이 추가된 인자  "--help"와  환경변수 MY_PATH가 "/usr/local/src"로 설정되어  출력되는 것을 확인하실 수 있습니다.

사용자 삽입 이미지


3. 디버거

Xcode에서 [shift+command+y] 또는 메뉴의 Debug/Debugger를 클릭하여 디버거를 실행합니다. 디버거를 실행하면 상단에서 아래와 같은 메뉴를 확인하실 수 있습니다.

사용자 삽입 이미지

> Build and Debug
빌드를 하고 성공하면 디버그를 실행합니다.

> Terminate
디버깅시 나타나며 클릭하면 실행과 디버깅을 종료합니다.

> Fix
디버깅 실행중에 소스 수정이 필요할 경우에는 소스를 수정하고 Fix를 실행하면, 종료/재시작 없이 계속 디버깅을 진행할 수 있습니다.

> Restart
Terminage와 Debug 명령을 동시에 실행하여, 종료 후 바로 디버깅을 재시작합니다.

> Pause
실행을 잠시 멈춥니다.

> Continue
실행이 중지되어 있을 때, 다음 브레이크포인트를 만나기 전이나 오류발생, 종료 전까지 계속 실행합니다.

> Step Over
함수를 만나도 함수내부로 디버깅을 하지 않고, 다음 라인을 실행합니다.

> Ste Into
함수를 만날 경우 함수내부로 진입하여 디버깅을 진행합니다.

> Step Out
현재 함수를 완료하고 호출된 지점으로 돌아 갑니다.

> Breakpoints
현재 라인에 브레이크포인트를 추가 또는 제거 합니다. 브레이크포인트는 디버깅 진행시 사용자가 확인을 위하여 어플리케이션 실행이 잠시 중지되는 위치 입니다.

> Console
gdb를 직접 사용할 수 있게 해줍니다.


4. 디버깅

1) 브레이크포인트 설정
디버깅 시 가장 중용한 것 중 하나가 브레이크포인트 입니다. 실행 중간 중간 변수 값 확인을 위해서 의심가는 위치에 실행을 정지하기 위해 브레이크포인트를 설정합니다.

브레이크 포인트는 소스코드 에디터상에서 [command+\]나 에디터 좌측의 판넬(gutter)를 더블클릭함으로서 설정/해제할 수 있습니다. 이제 위의 소스에서 b=5; 라고 되어 있는 라인에 브레이크포인터를 설정하고 디버그를 실행합니다.

사용자 삽입 이미지

위와 같이 브레이크포인트가 설정된 라인에서 프로그램 실행이 멈추었습니다. 우측 상단의 현재 변수값들을 확인해 보면 a는 3으로 지정되었으며 b는 아직 5로 설정되기 전이라 스택의 초기화되지 않은 값이 들어 있습니다.


2) 진행
이제 상단의 [Step Over]를 클릭 또는 [command+shift+o]로 한라인을 실행합니다.

사용자 삽입 이미지
좌측과 같이 b=5;의 한라인 실행되고 다음 라인으로 진행되었습니다.

우측 상단을 보시면 방금 변경된 변수 b가 붉은 색으로 5로 설정되어 있음을 알수 있습니다.




사용자 삽입 이미지
여기서 우측 상단의 변수 c를 우클릭하여 Whatch Variable로 설정합니다. Watch Variable은 변수값이 변경되었을 때 사용자에게 알려 줍니다. 설정되면 변수의 좌측에 돋보기 모양의 아이콘이 나타납니다.



사용자 삽입 이미지
이제 [Step Into] 버튼 [command+shift+i]로 plusNum함수 내부로 들어 가보겠습니다. 실행하면 좌측과 같이 plusNum 함수로 들어와 다음 실행을 위해 대기하고 있습니다.

여기서 잠시 중지하고 변수값을 변경해 보겠습니다.


사용자 삽입 이미지
디버거는 실행 중에 변수값을 변경해 가면서 테스트를 할 수가 있습니다. a=3, b=5로 소스코드에서 설정되어 있습니다. 이제 b의 값을 변경해 보겠습니다. 우측 상단 창에서좌측과 같이  b의 값을 10으로 변경합니다.

이제 다시 Step Over로 한 라인을 진행합니다.

사용자 삽입 이미지
진행하면 c는 13으로 되어 있슴을 확인할 수 있습니다. 또한 c는 Watch Variable로 설정되어 있기 때문에 값이 변했을 때 좌측과 같은 메시지 창이 나타납니다.



중요한 변수를 Watch Variable로 설정해 놓으면, 따로 브레이크포인트를 설정하거나 단계별로 진행하지 않더라도 변경시에만 편하게 알려 줍니다.


5. 기타

1) gdb
Xcode는 디버깅에 gdb를 이용합니다.  우측 상단의 [Console] 버튼을 클릭하면 gdb의 상태를 확인하거나 명령어를 직접 수행할 수 있습니다. 아래는 gdb에서 실행된 모습입니다. next 명령어로 한라인을 진행하고 배열 buff의 값을 파일로 덤프해 보았습니다.

사용자 삽입 이미지

사용자 삽입 이미지

 


위와 같이 터미널에서 buff에 들어 있는 메모리 값을 확인할 수 있습니다. gdb의 사용법을 익혀 놓으시면 빠르고 다양한 방법으로 디버깅을 할 수 있습니다.


2) 브레이크포인트 추가 옵션 설정
[option+command+b]로 브레이크포인트 창을 오픈하여 다양한 옵션을 설정할 수 있습니다. 아래는 해당 브레이크포인트 위치로 왔을 때 Log로 알려 주는 옵션입니다. 우측 하단에  Speak로 설정되어 있으므로, 해당 내용을 음성으로 출력합니다. Log로 설정하면 콘솔창으로 출력해 줍니다. 이외에도 다양한 명령어 및 로그를 설정할 수 있습니다.

사용자 삽입 이미지

Xcode에서 디버깅을 하는 방법에 대해서 간단히 알아 보았습니다. 프로그램은 구현하는 시간외에도 오류를 찾고 수정하는데도 많은 시간이 걸립니다. 디버거를 이용하면 쉽고 빠르게 오류를 찾아 낼 수 있습니다. 또한 디버깅 작업은 메모리리나 컴파일러가 수행하는 작업을 이해하는데 많은 도움을 줍니다.

gdb의 간단한 사용법만 숙지하시면 쉽게 Xcode의 디버깅 작업을 이해하고 수행할 수 있습니다.
AND

몇일전 즉흥적으로 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 ^^"만 쓰고 튈수도 없고 해서 그냥 마음으로만 드립니다.

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

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

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

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

사용자 삽입 이미지

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

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

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

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

파이프(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의 특징을 잘 보여주는 것 같습니다.

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

Xcode에서 Flex 개발하기  (2) 2007.12.08
Xcode에서 파이어폭스 플러그인 컴파일 하기  (0) 2007.12.04
Cocoa에서 MySQL 라이브러리 사용하기  (6) 2007.11.13
Xcode에 Subversion 적용하기  (6) 2007.11.10
Xcode 단축키  (2) 2007.10.26
AND

코아에서 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
Xcode에 Subversion 적용하기  (6) 2007.11.10
Xcode 단축키  (2) 2007.10.26
#pragma mark  (2) 2007.08.04
AND

프로젝트 진행 시에 다수 작업, 디버깅, 백업의 의미로 버젼관리는 매우 중요합니다. 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 단축키  (2) 2007.10.26
#pragma mark  (2) 2007.08.04
AND

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 판넬에서 현재 선택된 클래스의 소스파일을 생성한다.
AND