iPad가 Release되면서 새로추가된 Class로 사용자 TouchGesture를 인식하는 Class다.
오늘 ( 2010.11.17 )을 기준으로는 iPad(iOS 3.2) 에서 밖에 사용할 수 없는데,
iOS 4.2 에도 포함되었기에, 곧 iPhone 용 Project에서도 사용가능하다.


Detect 할 수 있는, Gesture의 종류는 위와 같다. 
CustomGestureRecognizer도 구현이 가능한데, 그것은 필요 할때 되서 스터디 할 생각이다.

일단은 UIPinchGestureRecognizer를 해볼생각인다.

UIGestureRecognizer의 Event 처리방식은 Delegate Pattern이 아닌, Target Action을 따른다.
Gesture라는게 여러 상황이 발쌩할 수도 있는데, Target Action Pattern을 이용한다는 뜻은, 
결국 한종류의 Event만을 사용하게는 뜻으로 보인다.

여러 종류의 Event를 처리 하고 싶으면, 여러 종류의 UIGestureRecognizer를 붙여야 할테다. 

UITouch 와 달리 Gesture는 UIResponder 가 아니라, UIView에서만 동작한다.

UIKIT_CLASS_AVAILABLE(2_0) @interface UIView : UIResponder<NSCoding> {

  @package

    CALayer        *_layer;

    id              _tapInfo;

    id              _gestureInfo;

    NSMutableArray *_gestureRecognizers;


Class 선언에서 보는것과 같이 View에 UIRecognizer를 add 시키면, 내부적으로 Array로 관리한다.
UIView에 UIGestureRecognizer 를 add 하고 remove하는 method들은 아래와 같다.


@interface UIView (UIViewGestureRecognizers)


@property(nonatomic,copy) NSArray *gestureRecognizers __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2);


- (void)addGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2);

- (void)removeGestureRecognizer:(UIGestureRecognizer*)gestureRecognizer __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_3_2);


@end


UIGestureRecognizer Object를 생성하고,  특정 View에 그 Object를 Add 해주면 자동적으로
Target-Action Mechanism 으로 특정 Selector가 불러진다.
아래는 UIPinchGestureRecognizer 를 생성하고 추가 하는 예제 소스 코드이다.


- (void)viewDidLoad {

    [super viewDidLoad];

UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc]

initWithTarget:self action:@selector(handlePinchGesture:)];

    [self.view addGestureRecognizer:pinchGesture];

    [pinchGesture release];

}


이 것만 해주면 Pinch Gesture가 해당 View에서 일어나면, @selector(handlePinchGesture:) 가 invoke 된다.
아래 소스는 selector의 구현 예제다.


- (void)handlePinchGesture:(UIGestureRecognizer *)sender {

UIPinchGestureRecognizer *pinchGesture = (UIPinchGestureRecognizer *) sender;

NSLog(@"scale = %f", [pinchGesture scale]);

NSLog(@"velocity = %f", [pinchGesture velocity]);

if([pinchGesture velocity] > 0.0f) {

label.text = @"Outward Pinch";

}else {

label.text = @"Inward Pinch";

}

}


argument 로 UIGestureRecognizer 의 object가 던져 지는데,
UIPinchGestureRecognizer의 경우 scale ( 절대 값) 과 velocity ( 상대값 ) 이 들어 온다.
velocity가 + 값이면 확대 Gesture , - 값이면 축소하는 Gesture라고 보변 된다.

UIGestureRecognizer가 없을때는 UIResponder의 Touches값을 이용해서 이래저래 번거로이 구현했는데,
UIGestureRecognizer를 이용하면 쉽고, 빠르게 구현할 수 있다.

덤으로, "시작하세요! 아이폰 프로그래밍" 의 PinchMe 예제를 UIGestureRecognizer를 이용해서 재구현한 프로젝트를 
첨부하니, 참고용으로 사용하시기 바랍니다.

주의) 최신버전의 iOS SDK(3.2.4)에서는 동작하지 않고, 현재에는 Xcode 3.2.5 GM을 이용해서 해야 합니다.


최근 아이폰 프로젝트를 아이폰/아이패드 Project로 Migration하는것을 스터디 하고 있는데,

제대로 마이그레이션 할려면, 웹개발자 들이 브라우저 별로 처리를 하듯이,
현재 Device체크를 해서 따로 둬야겠지만, 좀 더 고해상도로만 보여지더라도, iPad용으로 Release를 하는것은 꽤나 간단하게 할 수 있습니다.

첫번째로, Target설정을 Universal 로 바꿔줘야합니다.

Build Configuration 에서 Target Device Family 를 iPhone/iPad로 변경합니다.


그러면 iPad에서 실행 시켰을대 Application이 아이폰 App을 실행시켰을때와 달리, 큰 해상도로 나타나게 됩니다.
그리고 iPhone으로만 설정되어 있을때 와 달리, Executable에 iPad Simulator가 추가 되어서 노출됩니다.


이것으로 끝나게 되면 정말 좋을텐데, 문제가 하나 터집니다.
이걸 알아 내는라 몇시간 삽질했습니다. ;ㅂ; 

예를 들어서 NavigationBar가 존재 하는 어플리 케이션의 경우 우측 버튼이 동작하지 않는 경우가 발생합니다.

우측 상단에 있는 저 버튼이 아무리 해도 터치가 되지 않는데, 사실 저부분뿐 아니라 일정 영역이상에서는 모두 터치가 안됩니다.
그 이유는 UIWindow의 사이즈 에 있습니다.

iPhone으로 개발 된 프로젝트는 UIWindow사이즈가 iPhone에 맞춰서 설정 되어 있습니다.
특히 MainWindow.xib를 그대로 사용한 경우에도 마찬가지인데요.

그럼 MainWindow.xib를 따로 설정해주어야 하는건가 라는 생각을 할 수 있지만,
그냥 Window사이즈만 적절히 설정해주는것만으로도 완벽하게 동작합니다.

    CGRect  rect = [[UIScreen mainScreen] bounds];

    [window setFrame:rect];


해당 소스를 ApplicationDidFinishLaunching쪽에다가 넣우주시면

UIScreen에 맞게 윈도우 사이즈를 조절하게 됩니다.

사실 제대로 Universal을 만들려면, xib파일 분리가 불가피 하겠지만,
간단히 작업해서 우선 Release만 하는 선택을 할때 요긴하지 않을까 생각합니다.



UIImagePickerController 를 사용해서, Image를 가져와야 하는 일이 생겼습니다.

iPhone내의 Photo Library 혹은 Camera를 통해서 Image를 가져와야 하는데, 문제는 iPod Touch등에선 Camera가 없다는 점입니다.

시뮬레이터-No Camera-에서 UIImagePickerController의 SourceType을 UIImagePickerControllerSourceCamera로 세팅하게 되면, App이 멈추는것을 미루어봤을때, 실제 Device에서도 마찬가지 일것입니다.

최초에 든 생각은 UIDevice Class내의 model property를 이용해서, Camera가 있는 Device와 없는 Device를 구분하는것이었습니다.

이 경우에는 또다른 종류의 Device ( iMat ?? :) ) 가 Apple사로 부터 발매 된다면 추가적인 작업을 해줘야 한다는게 걸립니다.

그래서 좀 찾아 보았는데, 역시나 있더군요 ㅎ

UIImavePickerController의 Class Methoad중 isSourceTypeAvailable이라는 Method가 있습니다.




위와 같이 코딩을 하면, App이 동작하는 Platform에서 Camera가 Support되는지 쉽게 알수가 있습니다.
간단한 일 하는 녀석치고, 코드길이가 길어져서 보기 불편한 단점이 있긴 하지만요. ㅎ

1. Target 생성

Application Test는 Logic Test와는 달리 실제로 실행을 하기때문에, Executable Target을 필요로 합니다.
아래와 같이 기존의 Executable Target을 Duplicate하여 원래 프로그램의 설정그대로 실행할수 있는 Target을 생성합니다.


이때, Target의 이름에 규칙이 있습니다. 그것은 '<application_name>' Testing 입니다.
prefix는 기호에 따라서 아무렇게 써도 상관이 없지만, suffix는 Testing이 되어야합니다.

2. iPhone Unit-test Bundle 생성

Logic Test와 마찬가지로, Unit-Test Bundle을 생성해주어야합니다.
단, Executable Target과 연동하는 부분이 Logic Test를 위한 bundle을 생성하는것과는 차이가 있습니다.

지난 포스팅을 참조하여, Unit-Test Bundle을 생성한후, 아래와 같이  <blah>Testing Target에 dependancy를 설정합니다.


그리고, MyAppTests를 build하면 나오는 MyAppTest.ocutest Binary를 <blah>Testing Executable Target의 Copy Bundle Resource로 등록해줍니다.

자 이제 Target & Bundle 생성 및 Dependay 설정이 완료 되었습니다.

3. TestCase 작성

LogicTest와 마찬가지로, Objective-C TestCase Class 파일을 생성합니다.
이때 Target Owrnership은 Executable Target이 아닌 Test Bundle이어야 합니다.


4. 실행

Application Test는 Device에서만 동작합니다.
Build Target을 Device 3.0 or Later로 설정을 한후 실행을 하면 Debugger Console 창에서 결과를 확인할수 있습니다.

기본적으로 AppDelegate 를 가져와서, AppDelegate Object가 nil 인지 아닌지 체크를 하는 테스트가 들어 있습니다.

실행 결과는 다음과 같습니다.


만약 Console창에 위와 같은 메세지가 나오지 않는다면, Target Dependancy 부분을 다시 한번 확인해보시기 바랍니다.


요새 개발하고 있는 Application이 SQLite를 사용하고 있습니다.
SQLite Interface Module을 OCUnit Test를 이용하여, TDD로 개발하였고, 1차 완료 하였습니다.
아직 익숙하지 않은 탓에 오히려 시간이 많이 걸리는것 같다는 느낌을 받고 있지만, 확실히
TestCase가 미리 작성된 탓에, API사용자 입장을 고려하여, Module을 Design 할 수 있어,
개발 초기 부터 단단한 프로그래밍이 가능해졌습니다.

다음에 Writing TestCase라는 주제를 끝으로 OCUnit 을 통한 Unit Test에 대한 글을 마치려고 합니다.




NSDate를 SQLite DB에 저장을 해야 할일이 생겼습니다.

어떻게 할까 고민을 좀 해봤습니다.

NSDateFormatter를 이용해서, String으로 변환시킨후 저장을 하고, 다시 Formatter를 이용해서 NSDate로 Parsing하는 방법이 있는데,
Raw Data가 아닌 변환된값을쓴다는점과, Parsing에 들아가는 Cost등을 고려할때 좋은 방법이 아닐듯 싶습니다.

그래서 생각을 해보았는데, NSTimeInterval을 이용하기로 하였습니다.

NSDate에는 NSTimerInterval을 구해주는 여러 메소드가 있습니다.



timeIntervalSince1970을 이용하면, 1970년 1월 1일 부터의 duration을 second 단위로 얻을수 있습니다.

이걸 SQLite에 저장해두고, 다시 불러다가 쓰면 깔끔하게 구현이 될것 같습니다.

NSTimeInterval은 실제론 double 을 typedef 해서 정의된 자료형이기때문에, SQLITE의 column type을 'REAL'로 정의해서 저장하도록합니다.

DB Schema는 정의되어 있다고 가정하고, 아래는 NSTimeInterval을 DB에 삽입하는 코드 입니다.



그럼 DB에서 가져오는 코드를 보도록 하겠습니다.



첨에는 좀 까다롭지 않을까 싶었는데, 뭐 딱히 어려운게 없습니다 ㅎ
그냥 스윽~하면 스윽~되는거죠 ㅎㅎ

필요하시면 아래 예제 소스를 참조 하시고, 좋은 어플들 많이 개발하세요 ^^


이글은 iPhone Development Guide from Apple 을 보고 작성되었습니다.
OCUnit에 대한 글은 3부작(Logic Test, Application Test, Test Cases)로 나누어 진행됩니다.
이글에 나오는 Screen shot은 XCode 3.2.2 에서 Capture되었습니다.

저는 프로젝트를 진행할때, Unit 테스트용 코드를 따로 작성하는 편인데,

이제까지는, Build Configuration을 추가하는 형식으로 모듈 테스트 코드를 작성하곤 합니다.


이런식으로 테스트할 Module을 나누어서 했는데, 꽤나 원시적인 방식이라, 소스코드가 지저분해지기도 하고,
뭔가 깔끔하지 못하다는 생각이 많이 들어서,
Target을 나누어, 필요한 Class만 넣어서 Unit Test를 진행하는게 깔끔할것이라는 생각이 문득 들었습니다,

그러다 문득, 아이폰 OS 3.0 이 나왔을때 OCUnit이라는 녀석이 공식적으로 추가 되었다는것을 상기했습니다..

새로 진행하게 된 Project도 있고 하니, 어디 한번 시도 해보는거 나쁘지 않겠죠? ^^

Xcode는 2가지 Type의 Unit Test를 지원합니다.

Logic Test. App상이 아니라 Build Phase에서 Test진행, 말그래도 소프트웨어 Logic ( or algorithm )을 테스트하기 위한 Unit Test

Application Test. App상에 설치되어 Test가 진행됨. iPhone에 있는 Reousrce들을 이용한 모듈에 대한 Test가 가능함.


프로젝트에 OCUnit을 추가하려면, 'SenTestingKit' Framework을 추가 해주어야 합니다.
이 Framework은 /Developer/Library/Framework에 있다. ( Xcode가 Default Path로 설치된 경우 )

곧 알겠지만, Test별로 unit-test-bundle target을 추가 하게 되는데, SenTestingKit Framework의 owrnership을 이들 target으로 변경해주어야 합니다.


그럼  Logic Test를 수행하기 위한 절차를 살펴 보면,

1. 타겟 생성 ( iPhone OS unit-test-bundle type )
2. 생성된 타겟을 active Target으로,
3. TestCase Class 추가.
5. TestCase 작성
6. Build ( = Test ) 

의 순서로 진행 됩니다.

1. 타겟 생성


Target Group에서 우클릭(or cmd + click)한 후 Add->New Target을 선택합니다.


그리고 Cocoa Touch(iPhone OS) -> Unit Test Bundle을 선택한후, 적당한 이름으로 지정하여 생성합니다.

2. 새로 생성한 Target을 Active Target으로 설정합니다.


3. TestCase Class를 추가합니다.



iPhone OS -> Cocoa Touch Class -> Object-C test case class 를 선택하여, File을 생성합니다.



여기서 주의 할점이, Target Owrnership이 Execute Targe이 아니라, 새로 추가한, Test-bundle target이어야 한다는 점입니다

4. TestCase 작성

자동으로 생성되는 코드를 살펴보면, App. Test,Logic Test에 관한 코드가 둘다 담겨져 있는데,
기본적으로 App Test용으로 Compile되게 되어 있으니, Logic Test용으로 바꿔주어야 합니다.

.h 파일에서
#define USE_APPLICATION_UNIT_TEST 1

으로 되어 있는 부분을 찾아서 1 -> 0 으로 바꿔 주면 Logic Test용 코드가 동작하게 됩니다.

.m 파일에서 testMath()라는 Method가 자동적으로 생성되어 있는데, 1+1 == 2 ? 라는 테스트가 되게 되어 있습니다.

일단 실패 하는 결과를 보는게 우선임으로, 1+1 == 2 를 1+1 == 3 으로 바꿔 봅시다.

5. Build (Test)

Active Target/ Target Owrner ship 등이 제대로 설정되어 있다면, Build(= Test)를 수행하면 아래와 같이 Error가 나타날것입니다.



혹시 Build Success 가 된다면, Active SDK 가 iPhone Simulator 3.0 or later 인지 확인하자. Logic Test는 Simulator일때만 동작합니다.


보신바와 같이, 그리 어렵지 않게 Logic Test를 추가 할수 있고, App 을 실행하지 않아도 되니, 빠르게 결과를 확인할 수 있습니다.

분명 Unit Test를 통한 Test자동화는 개발 프로세스에 효율을 가는데, 가끔 이런 도구에 너무 집착하여 개발 효율을 오히려 저해하는 경우도 꽤나 많습니다.

언제나 그렇듯 도구는 수단이 되어야지 목표가 되어서는 안된다고 생각합니다. ^^




제가 좋아하는 홍익대학교의 박지헌 교수님은 이런 말씀을 하셨습니다. ㅎ

"무협지에서 주인공이 무공비급을 보면서 절세의 고수로 거듭나는것처럼

우리는 세상에 널려있는 공개소스를 봐야한다"



그래서 저는 공개된 소스를 자주 분석 하는 편입니다.

최근에는 iPhone SDK에 대해 공부 하기떄문에 iPhone Sample code들을 분석하려고 노력하고 있습니다.

Apple에서 공개한 소스 인 만큼 가장 정석적인 코드라고 봐도 무방하지 않을까 합니다.  (얘들도 사람이기때문에 실수는 하겠지만 ㅎ)


iPhone SDK에서 DB 를 사용하는 법을 공부 하기 위해서 SQLiteBooks 라는 Sample Code 를결정했습니다.

프로그램 이름에도 나와 있듯이 iPhone SDK는 SQLite 를 DBMS로 사용하고 있습니다.

이번 리뷰는 DBMS 공부가 목적이기 때문에 UI part 는 제외하고 DBMS 사용 즉 sqlite3 쪽 사용에만 초점을 두고 진행하겠습니다.

목차는 아래와 같습니다 :)

1. Introduction
a. What does this App. do ?
2. Analyze
a. DB- AppDelegate
b. DB- Book Class


[1-a What does this App. do ( SQLiteBooks 는 어떤 일을 하는가 )? ]

제목에서 알수 있듯이, 책을 관리 하는 프로그램입니다.
책을 하나의 list 로 관리 하며, 하나의 책 object 는 세가지 속성 ( Title, Copyright, Author ) 을 가지게 됩니다.

직관적으로 알수 있듯이, 책 추가, 책 삭제, 책의 정보 보기, 정보 수정하기의 기능을 합니다.



[2-a DB - AppDelegate] 

ApplDelegate 는 DB 제어 용이 아니라, App. 시작과 종료등에 날라오는 Delegate 즉, Application Delegate Protocol 을 구현하는 Class 이다.
이 sample 에서는 AppDelegate에서 DB 제어에 사용되고 있다.
사실 DB 제어 하는 부분의 소스코드는 썩 맘에 들지는 않는다.
DB 접근이 Class AppDelegate 와 Class Book에서 나누어져 이루어져 있다.
DBM Class 를 구현해서 한곳으로 모았으면 어땠을까 하는 생각이 든다.

어찌되었건, Class 의 선언 부분을 보도록 하자.

// This includes the header for the SQLite library.
#import <sqlite3.h>

// Inform the compiler that the following classes are defined in the project:
@class Book, MasterViewController, DetailViewController, AddViewController, EditingViewController;

@interface AppDelegate : NSObject {
    IBOutlet UIWindow *window;
    IBOutlet UINavigationController *navigationController;
    NSMutableArray *books;
    // Opaque reference to the SQLite database.
    sqlite3 *database;
}

@property (nonatomic, retain) UIWindow *window;
@property (nonatomic, retain) UINavigationController *navigationController;

// Makes the main array of book objects available to other objects in the application.
@property (nonatomic, retain) NSMutableArray *books;

// Removes a book from the array of books, and also deletes it from the database. There is no undo.
- (IBAction)removeBook:(Book *)book;
// Creates a new book object with default data.
- (void)addBook:(Book *)book;

@end

먼저 Attributes 들을 보면
IB에서 생성된, window 와 navigationController,
Data 관리를 위한 books, database 가 있습니다.

UI 와 관련된 부분은 관심사가 아니기 때문에 Data 와 관련된 부분만 살펴 보도록 하겠습니다..

books 는 DB 에서 load 된 book list 들을 가지고 있는것이고,
database attribute 는 DB 접근을 위한 Object 입니다..
SQLite 는 많이들 사용하는 Mysql  과 달리, Server-Client 구조가 아니라,
하나의 파일을 open-read-write-close 하는 구조입니다..
sqlite 파일을 sqlite3_open 을 통해서 open 하고, query 를 통해서 read/write 하고 , sqlite3_close 를 통해서 close 합니다.
sqlite3 * 는 sqlite3_open 의 return type 이고, sqlite3_open 이외의 대부분의 api 가 sqilte3 *를 아규먼트로 요구 합니다.

더 자세한 정보는 sqlite3 홈페이지 에서 확인하시기 바랍니다.

자그럼 AppDelegate에서 구현하고 있는 Method 들에 대해서 살펴 보도록 하겠습니다.

@interface AppDelegate
// Removes a book from the array of books, and also deletes it from the database. There is no undo.
- (IBAction)removeBook:(Book *)book;
// Creates a new book object with default data.
- (void)addBook:(Book *)book;
@endif

// Private interface for AppDelegate - internal only methods.
@interface AppDelegate (Private)
- (void)createEditableCopyOfDatabaseIfNeeded;
- (void)initializeDatabase;
@end

// Implementation of Application Delegate Protocol
- (void)applicationDidFinishLaunching:(UIApplication *)application
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
- (void)dealloc
- (void)applicationWillTerminate:(UIApplication *)application


여러가지가 있지만 우선 Application Delegate Protocol 을 구현하고 있는 메소드들 부터 살펴보도록 하겠습니다.
우선 applicationDidFinishiLaunching 메소드를 살펴 보도록하겠습니다.
applicationDidFinishiLaunching Delegate 는 다들 잘아시겠지만.
Application 이 실행된후 최초로 불러지는 Delegate 입니다.
최초로 불러 지는것인 만큼 주로 초기화가 필요 한 코드들을 실행하게 됩니다.
이 Application 에서는 MainView를 윈도우에 할당하고, DBMS 샘플 소스 인만큼 DB 관련 초기화 코드인
createEditableCopyOfDatabaseIfNeeded 와 initializeDatabase 를 불러 줍니다.

소스는 아래와 같습니다.

- (void)applicationDidFinishLaunching:(UIApplication *)application {
    // The application ships with a default database in its bundle. If anything in the application
    // bundle is altered, the code sign will fail. We want the database to be editable by users,
    // so we need to create a copy of it in the application's Documents directory.    
    [self createEditableCopyOfDatabaseIfNeeded];
    // Call internal method to initialize database connection
    [self initializeDatabase];
    // Add the navigation controller's view to the window
    [window addSubview:navigationController.view];
    [window makeKeyAndVisible];
}


다른 Delegate Protocol 들은 메모리 해제 등의 상투적인 일을 하고, 이번 소스코드 분석이 AppDelegate분석에 초점을 맞춘것이 아니기 때무에 넘어 가도록 하겠습니다


그럼 createEditableCopyOfDatabaseIfNeeded 와 initializeDatabase Method를 살펴 보도록하겠습니다.

우선 createEditableCopyOfDatabaseIfNeeded를 보도록하겠습니다.
이름에서 보다 시피 EditableDatabase를 생성하는 함수 입니다.
Project를 살펴보시면, Resource->bookdb.sql 이라는 파일로 DB 파일이 존재 하는걸 알수 있습니다.
그런데 왜 Editable 한 DB 가 필요 하냐고 생각하실수 있습니다.
" 그럼 Bundle로 들어간 DB 는 Read-Only 란 말이냐? " 라고 생각하실수 있는데.
네 Read-Only 입니다. Application Executable File 과 번들들은 iPhone의 Sandbox 라는 Read-Only 영역에 들어 가있습니다.
Application 에서 파일 생성및 수정등의 Write 작업이 필요 한 경우에는 $(APP_HOME) Directory 밑에서 이루어져야 합니다.
즉 DB를 수정하기 위해서는 $(APP_HOME) 로 옮기는 작업이 필요 합니다.
이작업은 App.이 최초에 실행될때 한번만 이루어지게 됩니다.

자 그럼 소스를 보도록 하겠습니다.

// Creates a writable copy of the bundled default database in the application Documents directory.
- (void)createEditableCopyOfDatabaseIfNeeded {
    // First, test for existence.
    BOOL success;
    // NSFileManager 는 iPhone SDK 에서 File I/O 를 담당하는 Class 입니다.
    // 이 Class 의 object 를 통해 Editable한 DB 의 존재 유무를 확인하거나 Copy할수 있습니다.
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSError *error;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *writableDBPath = [documentsDirectory stringByAppendingPathComponent:@"bookdb.sql"];
    //NSFileManager를 통해 Writable DB 의 존재 유무를 확인합니다.
    success = [fileManager fileExistsAtPath:writableDBPath];
    // Writable한 DB 가 존재 할때는 아무작업도 하지 않습니다.
    if (success) return;
    // The writable database does not exist, so copy the default to the appropriate location.
    NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:@"bookdb.sql"];
   //NSFileManager를 통해 Copy 하는 코드 입니다
    success = [fileManager copyItemAtPath:defaultDBPath toPath:writableDBPath error:&error];
    if (!success) {
        NSAssert1(0, @"Failed to create writable database file with message '%@'.", [error localizedDescription]);
    }
}


자 그럼 initializeDatabase Method 를 살펴보도록하겠습니다.

initializeDabase를 이름만 보고 판단했을때는, DB object를 초기화 하는구나 싶습니다.
하지만 이게 다가 아닙니다 =ㅁ=.
DB object 를 초기화 하고, DB 로 부터 UI display에 필요한 최소한의 정보들을 Load하는 일을 함께 합니다.
이런 명명법은 딱히 좋아 보이지 않습니다.

그럼 소스를 보면서 살펴보도록 하겠습니다.

// Open the database connection and retrieve minimal information for all objects.
- (void)initializeDatabase {
    // BookList 를 관리할 NSMutableArray Object인 bookArray를 생성하고, AppDelegate Object에 링크 시킵니다.
    NSMutableArray *bookArray = [[NSMutableArray alloc] init];
    self.books = bookArray;
    [bookArray release];
    // The database is stored in the application bundle.
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"bookdb.sql"];
    // sqlite3_open 을 통해 DB 를 오픈 합니다.
    // 실패한 경우의 코드를 봐도 알수 있듯이, 실패 하더라도 꼭 sqlite3_close를 불러줘야,
    // Memory Leak이 발생하지 않습니다.
    // Open the database. The database was prepared outside the application.
    if (sqlite3_open([path UTF8String], &database) == SQLITE_OK) {
        // Get the primary key for all books.
        const char *sql = "SELECT pk FROM book";
        sqlite3_stmt *statement;
        //sqlite3 DB 파일에 쿼리를 던져주고, 처리 하기 위해서 sqlite3_stmt 라는 구조체를 사용합니다.
        //query 를 바인딩하고, sqlite3_step 등의 api 를 통해서 행을 처리하게 됩니다.
       // statement 를 이용해서 작업이 끝난 후에는 sqlite3_finalize를 불러줘서 Memory를 해제 해야합니다.
        // Preparing a statement compiles the SQL query into a byte-code program in the SQLite library.
        // The third parameter is either the length of the SQL string or -1 to read up to the first null terminator.       
        if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK) {
            // We "step" through the results - once for each row.
            while (sqlite3_step(statement) == SQLITE_ROW) {
                // The second parameter indicates the column index into the result set.
                int primaryKey = sqlite3_column_int(statement, 0);
                // We avoid the alloc-init-autorelease pattern here because we are in a tight loop and
                // autorelease is slightly more expensive than release. This design choice has nothing to do with
                // actual memory management - at the end of this block of code, all the book objects allocated
                // here will be in memory regardless of whether we use autorelease or release, because they are
                // retained by the books array.
                Book *book = [[Book alloc] initWithPrimaryKey:primaryKey database:database];
                [books addObject:book];
                [book release];
            }
        }
        // "Finalize" the statement - releases the resources associated with the statement.
        sqlite3_finalize(statement);
    } else {
        // Even though the open failed, call close to properly clean up resources.
        sqlite3_close(database);
        NSAssert1(0, @"Failed to open database with message '%s'.", sqlite3_errmsg(database));
        // Additional error handling, as appropriate...
    }
}


자 DB 초기화 하는 부분까지 살펴 봤고, 이제 Public Method 인 addBook 과 removeBook 에 대해서 살펴보도록하겠습니다.
addBook 은 Book object 를 받아서 class Book 의 method insertIntoDataBase를 통해 DB에 추가 해주고,
Book List 를 관리하는 bookArray에 추가를 해주는것으로 끝이 납니다.
간단함으로 소스는 살펴보지 않도록 하겠습니다.

removeBook 의 경우 IBAction 즉 IB 를 통해 정의된 Method 입니다.
IB 를 열어서 살펴보면 아시겠지만 Connection 은 이루어 져 있지 않습니다.


addBook 함수 처럼 왜 그냥 선언하지 않고 IB 를 통해서 정의 했는지는 잘 모르겠습니다.
혹시 아시는 분은 좀 알려주시기 바랍니다 :)
removeBook은 addBook과 반대의 일을 합니다. DB 로부터 삭제, bookArray로 부터 제거.
addBook 과 마찬가지로 코드가 간단하기 떄문에 소스를 샆펴보지 않겠씁니다.

자 이것으로 AppDelegate Class 의 구현을 모두 살펴 보았습니다 :)

[2-b DB- Class Book]

자 그럼 Book Class 에 대해서 살펴 보도록 하겠습니다.

Book Class 의 선언은 다음과 같습니다.

@interface Book : NSObject {
    // Opaque reference to the underlying database.
    sqlite3 *database;
    // Primary key in the database.
    NSInteger primaryKey;
    // Attributes.
    NSString *title;
    NSDate *copyright;
    NSString *author;
    // Internal state variables. Hydrated tracks whether attribute data is in the object or the database.
    BOOL hydrated;
    // Dirty tracks whether there are in-memory changes to data which have no been written to the database.
    BOOL dirty;
    NSData *data;
}

// Finalize (delete) all of the SQLite compiled queries.
+ (void)finalizeStatements;

// Creates the object with primary key and title is brought into memory.
- (id)initWithPrimaryKey:(NSInteger)pk database:(sqlite3 *)db;
// Inserts the book into the database and stores its primary key.
- (void)insertIntoDatabase:(sqlite3 *)database;

// Brings the rest of the object data into memory. If already in memory, no action is taken (harmless no-op).
- (void)hydrate;
// Flushes all but the primary key and title out to the database.
- (void)dehydrate;
// Remove the book complete from the database. In memory deletion to follow...
- (void)deleteFromDatabase;
@end

Attribute 의 다른 부분들은 직관적으로 아실꺼라 생각합니다. 눈여겨봐야 할것은
hydrated , dirty 가 아닌가 생각합니다.

Mobile 환경에서의 부족한 메모리를 효율적으로 상용하기 위해 Book Class 의 경우 Cache Concept을 사용하였습니다.
기본적으로 Display 하기에는 title 만 있으면 됩니다.
그래서 Class Appdegate 의 메소드 initializeDatabase에서 살펴 봤듯이 최초에 key와 title만 가져옵니다.
이때 hydrated = NO 로 세팅하고 실제 copyright 등의 data 가 필요 할때 DB 로 부터 가져와서 채워주게 됩니다.
이때 hydrated = YES 로 표기 하게 되는것이죠.
그리고 Application Delegate Protocol 중 하나인 applicationDidReceiveMemoryWarning:(UIApplication *)application 이 불러 졌을때
hydrated 된 object 들을 dehydrate 해줌으로써 메모리 효율을 늘려주는식의 구현이 되어 있습니다.

그리고 dirty 필드는 edit 가 일어 났을때 ( 이때는 항상 hydrated=YES 임) 바로 DB 에 써주는것은 성능에 좋지 않기때문에
기왕 Memory에 관리 하고 있는 book object 에만 기록하고 DB 에는 나중에 쓰는 delayed-Write를 구현하기 위한 attribute입니다.

applicationDidReceiveMemoryWarning 프로토콜이 불러졌을때 혹은 App.을 종료할때 Write 해줌으로써 프로그램 성능을 높이는 형태입니다.

자 그럼 Method 들에대해서 살펴 봐야 하는데 그전에 Class Book 에서 sqlite3_statement 를 관리 하는 구조를 살펴 보도록하겠습니다.

book.m 파일 상단에 보면 아래와 같은 코드를 볼수 있습니다.

static sqlite3_stmt *insert_statement = nil;
static sqlite3_stmt *init_statement = nil;
static sqlite3_stmt *delete_statement = nil;
static sqlite3_stmt *hydrate_statement = nil;
static sqlite3_stmt *dehydrate_statement = nil;

이 코드는 book object 별로 db 제어를 해야 하는데, object 별로 statement 를 관리하면, 같은 statement 가 여러번 할당되어
낭비를 막고자 작성된 코드 입니다.

사견을 덧붙히자면, DB 제어를 한곳으로 모았다면 사실 이런 귀찮은 방법을 써가면서 메모리 효율을 생각할 필요까진 없지 않았을까 생각합니다.

여튼 최초에 nil 로 되어 있다가 최초에 statement 가 할당될때, 초기화 하고 ( 사용하지 않을수도 있기 때문에 )
프로그램 종료시에 해제 해주게 됩니다.

자 그럼 Method들을 살펴 보도록 하겠습니다.

- (id)initWithPrimaryKey:(NSInteger)pk database:(sqlite3 *)db

당연히 book object 를 initiailize 하는 함수 입니다 ㅎ

그럼 코드를 살펴 보도록 하겠습니다

        if (init_statement == nil) {
            // 앞서 설명 드린대로 init_statement는 global variable 이고, 최초에 사용할때 이를 할당하게 됩니다.
            // query 의 마지막 부분 pk=? 를 보시면 pk 는 primary key field 이고 value가 ? 인것을 알수 있습니다.
            // '?' 는 printf 같은 함수를 쓸대 '%s'등 과 같다고 보시면 될듯합니다 값은 sqlite3_bind_xx 함수로 채우게 됩니다.
// Note the '?' at the end of the query. This is a parameter which can be replaced by a bound variable.
            // This is a great way to optimize because frequently used queries can be compiled once, then with each
            // use new variable values can be bound to placeholders.
            const char *sql = "SELECT title FROM book WHERE pk=?";
            if (sqlite3_prepare_v2(database, sql, -1, &init_statement, NULL) != SQLITE_OK) {
                NSAssert1(0, @"Error: failed to prepare statement with message '%s'.", sqlite3_errmsg(database));
            }
        }
        // For this query, we bind the primary key to the first (and only) placeholder in the statement.
        // Note that the parameters are numbered from 1, not from 0.
        // query 의 ? 부분을 채워주는 부분
        sqlite3_bind_int(init_statement, 1, primaryKey);
        if (sqlite3_step(init_statement) == SQLITE_ROW) {
            self.title = [NSString stringWithUTF8String:(char *)sqlite3_column_text(init_statement, 0)];
        } else {
            self.title = @"No title";
        }
        // 재사용을 위해 reset 을 해주는 아마도 bind를 해제 하는것 같습니다.
        // Reset the statement for future reuse.
        sqlite3_reset(init_statement);

- (void)insertIntoDatabase:(sqlite3 *)db
- (void)deleteFromDatabase

 이 Method 들은 init함수와 마찬가지로 DB 제어 를 통해서 read/write 하는 부분들이기때문에 initWithPrimaryKey 와 모습이 거의 비슷합니
다. 그러므로 생략하고 넘어 가도록 하겠습니다.

- (void)hydrate
- (void)dehydrate

이 Method 들 역시 앞서 설명드린, Cache 컨셉과 , Dirty Filed 컨셉을 위해 DB 와 Book Object 연동을 다룬 Method이기때문에
앞의 설명을 숙지하고 코드를 보시면 충분히 이해하실수 있따고 생각하여 넘어가도록 하겠습니다.

이것으로 SQLibteBooks DB 제어관련 소스 리뷰는 마치도록 하겠습니다

장문의 글 읽어주셔서 감사합니다 :)


프로그램을 개발하다 보면, 특정 Class 의 선언부 혹은 특정 함수의 구현부분을 봐야할 필요가 있습니다

vim 에서 개발할때는 ctag 라는 프로그램을 통해서 tag를 생성하고, vim 에서 그 태그를 사용하여

정의부분 혹은 구현부분을 탐색할수 있었습니다.

Xcode 에서도 그런것이 가능할까요?

당연히 가능합니다 :) 가능하지 않았다면 이글을 쓰지도 않았겠죠 ㅎㅎ

클래스 이름 혹은 함수 이름을 마우스로 드래그 하여 선택을 한후

Control + Click ( 혹은 Right-Click)을 하면 옵션 Popup 이 뜨는데 그중 'Jump To Definition'이 바로 그기능을 하는 녀석입니다 ㅎ

참 쉽죠 ? :)

 
본자료는 iphonedevcentral.org에 공개되어있는 Tutorial Video에 의거하여 작성되었음을 알려드립니다.
Video Link : http://iphonedevcentral.org/tutorials.php?page=ViewTutorial&id=49&uid=27391603


리눅스 환경에서 C Programming 을 하면서 밥 벌어 먹고 살다 보니, vim 과 Makefile System을 이용한 CUI 환경에 너무 익숙해져 있어, Xcode의 GUI 환경은 무척이나 불편하고 ㅎ, 생소한점이 많다.

거기다가 언어도 한번도 해본적 없는 Obj-C 라니 orz. 이래저래 공부해야 할것이 태산이다.

이랬거나 저랬거나 언제나 그래왔듯이 새로운 언어와 플랫폼을 공부할때는 역시나 Hello World 를 짜면서 이래저래 살펴 보는게 좋은것 같다.

역시나 Hello World 프로그램 짜는게 가장 자료가 많은것 같다 ㅋㅋㅋ

You Tube 에도 강좌가 올라와 있고 ( 저해상도의 압박으로 보기가 많이 힘들다 )

이런저런 카페에도 글이 많다.

그중에서 가장 괜찮은게 위에 링크 걸려 있는 자료가 아닌가 싶다.

Hello World 프로그램 관련 자료는 꽤나 오래전에 작성된것이 많아 현재 Xcode 버전과도 맞지 않는 점이 많다.

내가 내 블로그에 이런 글을 쓰고는 있지만, 사실 내 글과 링크된 동영상 둘중 하나만 봐야만 한다면 난 당연히 동영상을 볼것임으로 읽는 분들은 꼭 동영상을 한번 보시길 적극 추천한다.

지금 만들어 볼 프로그램은 Button을 클릭하면 Hello World 메세지가 나타나는 프로그램이다.

자 그럼 시작해보자

1. Project 생성하기

사용자 삽입 이미지

위의 이미지에 올라가 있는 것처럼 File->New Project 클릭혹은 단축기를 이용하여, 프로젝트 생성을 시작할수 있다.

사용자 삽입 이미지


New Project 를 실행하면 Iphone 용 Project 혹은 Mac OS X 용 Project 를 선택 할수 있는데,
Iphone 용 Project 중 Window-Based App. 를 선택하면 된다.

하단에 설명이 나와 있는것처럼 Window-based App.는 어떠한 형태의 App.도 될수 있는 가장 원시적인 형태의 App. 라고 볼수 있다. 그외의 타입은 Window-based App. 에서 목적에 따라 기초적인 코드가 자동으로 작성되는 Project라고 생각하면 되겠다.

사용자 삽입 이미지

Project 이름을 결정하고 ( 당연히 Hello World ) 로 하고, 'Save'버튼을 누르면 Project 생성이 완료된다.

2. Interface Builder를 이용한 Inteface 설계및 Class 생성하기

사용자 삽입 이미지

Project 생성이 완료 되고 나면 Interface Builder(이하 IB) 를 통해서 UI 를 디자인 하고 해당 Class 를 등록해야한다.

현재 Project의 UI design 을 위해 IB 를 실행시키기 위해선 .xib 파일을 더블 클릭 하면 된다.

좌측 Overview 칼럼에 Project의 하부 그룹들중에 Resources 을 열어 보면 MainWindow.xib 를 찾을 수 있다.

사용자 삽입 이미지

위의 그림은 IB 를 실행 시킨 모습이다. 최초에 IB 를 실행하면 우측에 있는 윈도우가 없다 ( 나는 없었다 ㅜ.ㅜ )

Tools -> Identity Inspector 를 클릭하면 짠하고 나타날것이다 :)

제일 먼저 해야 할일은 윈도우에 View 를 할당하는 고, View를 컨트롤 하게 될 클래스를 설정하는것이다.

사용자 삽입 이미지

Library Window 를 살펴 보면 View 라는 컴포넌트가 있다. View를 드래그 하여 Window 창에 놓자.

드래그를 하면 최초그림과 달리 Window가 파랗게 변했음을 알수 있다.

파란색으로 색칠된 영역만큼 방금 할당한 View가 보여지게 된다. 영역의 크기는 변경가능하다.

이제 View를 넣었으면 Button 과 Hello World 를 표기할 Label ( Textbox 정도로 생각하면 되겠다 ) 을

생성할차례다


사용자 삽입 이미지

라이브러리 윈도우를 찾아오면 Round Rect Button 과 Label Component 가 있다. 각 컴포넌트를 View에 적당한 위치에 넣고 크기를 알맞게 적용 하면된다.

라벨과 버튼을 각각 더블클릭하면, 초기 텍스트를 변경할수 있다.

자 이제 Button 과 Label Component 를 처리 할 Class를 정의해야한다.

Window 창에서 UIView영역을 클릭하고 Identity 창 중 네번째 Tab(i) 를 클릭해보면

클래스 이름및 속성을 설정 하는 부분이 나온다.

사용자 삽입 이미지

Class 이름을 정의하고, Class Action ( Method 라고 생각하면 되겠다 ) 에 Button Event를 처리할
Action을 추가 하고, Class Outlets (Member Var 이라고 생각하면 되겠다 ) 에 Label과 연결된
Outlet을 추가 하자.

이때, 선언된 Outlet은 Label과 연결될것이기 떄문에 Type에 UILabel 이라고 써줘야 한다.

자 이제 UI Component(Label, Button)과 Class 를 연결할일만 남았다.

Identity Window 의 두번째 Tab (->) 으로 이동하자

사용자 삽입 이미지

위의 그림과 같이 Outlet 혹은 Action 을 Window창에 보이는 컴포넌트와 시각적으로 연결 시킴으로써, Class 와 Component를 연결시킬수 있다.
Outlet : mainText -> Label, Action : showText -> Button
이렇게 연결 시키면 된다.
showText를 연결시킬때는 어떤 이벤트에 대해서 연결한것인지 묻는데
'Touch Up inside'를 선택하도록 하자.

자 드디어 IB 에서 할일은 거의 끝났다.

마지막으로 File->Write Class File 을 선택하여 Xcode에 생성되어있는 Project 와 연결 시키면 된다.

'Write Class File' 을 선택하면, 저장여부를 묻고 Xcode에 열려 있는 Project에 추가 하겠냐는
창이 나오는데, 저장한후 HelloWorld Project에 추가 하면 된다.

자 이제 IB 를 종료 하고 Xcode로 돌아가자.
 
3. 코드 작성하기

사용자 삽입 이미지

Xcode로 돌아 가면, 아까 IB 에서 생성한 Class 가 Products 그룹에 있는걸 알수 있다.
우리가 구현하는 프로그램은 Button 과 Label 만 사용하는것이고, Class 와 Component가 연결된 상태이기 때문에 MainView Class만 수정하면 된다.

.m 과 .h 파일을 Classes 에 옮긴후 ( Class 니까:) ) , 수정하도록 하자

MainView.h

#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

@interface MainView : UIView /* Specify a superclass (eg: NSObject or NSView) */ {
    IBOutlet UILabel *mainText;
}

@property (nonatomic, retain) UILabel *mainText;

- (IBAction)showText;
@end


MainView.m

#import "MainView.h"

@implementation MainView

@synthesize mainText;

- (IBAction)showText {
  mainText.text = @"Hello World"; 
}
@end


빨간색 으로 처리된 부분이 새로 추가된 코드 부분이다.

생성된 MainView Class 에는 Super Class 선언이 빠져 있음으로 Super Class선언을 해주어야 한다.
MainView Class는 View Component 에 연결된 Class 이고, View Component는 UIView Class를
가져야 함으로 Super Class를 UIView 로 설정하도록 한다.

Property 와 Synthesize 부분은 아이님의 블로그에 자세하게 설명 되어 있으니, 참고 하길 바란다.

자 이제 모든 작업이 끝나고 기쁘게 실행 해보면 된다.


4. 실행하기


사용자 삽입 이미지

Xcode 창 상단에 Build and Go 를 선택하면 Build 한후 실행을 할수 있다.

그전에 Target이 iPhone Simulator로 설정 되어 있는지 확인해야 한다.

아래 화면은 Hello World 가 실행 된 모습이다.

사용자 삽입 이미지



어떤 프로그래밍 공부건 Hello World 를 짜보는것으로 그것을 할줄 안다고 자신있게 말할순 없다.
하지만 Hello World 프로그램을 짜보면 많은것을 알수 있다.

방금 해본 프로그램에서도 아주 기초적이지만, IB 를 사용하는법과,
Objective-C 에서의 Class 를 사용하는 법을 훓어 볼수 있었지 않나 생각한다.



+ Recent posts