최근에 받은 프로젝트 중에, 
KVO(Key-Value Observing)을 전투적으로 사용하는 프로젝트가 있습니다.

KVO 를 사용하면 안좋다고 생각하는 게 소프트웨어 가독성이 너무나 너무나 떨어집니다.
( 성능등 이런저런 이슈들도 있지만.. 제일 큰 문제가 이녀석이라고 생각합니다 )

현재 Target이 되는 Object를 어떤 녀석들이 Observe하고 있는지를 알기가 참 어려운점이지요.
얼마나 유연하게, 소프트웨어가 관리 되는가가 keyword인 요즘에 너무나 동떨어 지는 일이죠.

그래서 KVO를 걷어내는 일에 착수 했습니다.
그런데... 앞서 말한것처럼 누가누가 Observe하고 있냐를 알아 봐야 하는데, 이게 Code만 봐서는 알기가 매우 어렵습니다.
해당 Source Code를 Grep 하는 일 따위는 제일 나중에 하고 싶군요.

그래서 생각해본결과 

Target Object의 구현체에서 observer를 추가 하는 코드를 Override 해서 중간에 찾아 내는 코드를 찍는것입니다.

#pragma mark KVO걷어 내기 위해서가로 채서, Caller Log 찍기 위해 Override


- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {

NSLog(@"===================");

NSLog(@"[KVO Register] caller Class = %@", [observer class]);

NSLog(@"===================");

[super addObserver:observer forKeyPath:keyPath options:options context:context];

}


위의 코드를 Target Object의 구현체에 넣어주면, observer가 등록 될때마다, observing하는 Class이름을 로그에 찍어 주니,
추가하고, 소프트웨어를 돌려서 로그창을 긁으면 좀 더 쉽게 observing class를 찾아 낼 수 있습니다.

이 방법에 한가지 문제가 있는데, 그건 바로 "내가 소스코드를 가지고 있는게 아니라, 라이브러리나 Framework의 Object 를 Observing하고 있는 경우 어떻게 하느냐?" 입니다.

그건  Objective-C Runtime특성을 이용하면 됩니다.
이미 구현되어 있는 Method Implementation을 Runtime에 추가 할수 있고, 그리고 바꿔 끼어 넣는것도 가능합니다.
그건 블로그에 쓰기 귀찮으니, Googling해보시기 바랍니다 ㅎㅎ




Objective-C에서 Method를 Deprecated 마킹하는 방법에는 두가지가 있다.

-(id) initWithImageName:(NSString *) imageName __attribute__((deprecated));

-(id) initWithImageName:(NSString *) imageName withEditing:(BOOL) aEditing DEPRECATED_ATTRIBUTE;


사실 2번째 방법은 첫번째 방법을 Macro로 감싼것에 불과하다고 볼 수 있지만, 조금더 깔끔하다.

Deprecated Marking을 하고 나면, 아래와 같이, 해당 메소드를 사용한곳마다, Deprecated되었다고, 워닝을 때려준다.


API를 제공하는 단에서, 새로운 릴리즈때 API를 삭제 할 수도 있지만,
보통 Deprecated를 마킹 하고, 그다음에 삭제 하는게 API를 사용하는 사람을 배려 하는 것입니다.

다들 제발 그냥 지워버리지 말고, Deprecated해서 잠시동안만이라도 호환성을 맞춰주세요 ㅜ_ㅜ




Objective-C Class Category의 구현체에서는 @synthesize keyword를 사용하면, 
아래와 같이 에러가 뜬다. 
카테고리에서 @property를 선언하는것은 되는데, @synthesize하는것은 안된다. 

일단 이게 왜 필요 한가 ? 
Objective-C에서는  Private개념이 따로 없고, 저런식으로 Category를 이용해서 Private Interface를 사용하는데, 
내부에서만 사용하고 싶은 변수가 있을때, @property를 사용할 수 없으면, setter/getter를 손코딩 해야 하는데, 그건 참 귀찮은 일이다.

이에 대한 해결책은 의외로 간단하다.

@property는 Private Category 선언부에서 선언하고, 
@synthesize는 Main 선언부에서 선언하는것이다.

흐음.. 왜 이렇게 문법적으로 막혀있는지는 아직 잘 모르겠다 -_ - 
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을 이용해서 해야 합니다.


iOS 에서 사용될 라이브러리를 만들때, 

Simulator 와 Device Arch Type이 달라서, 2가지 library 파일을 생성해야한다.

최근에 어떤 프로젝트 소스 코드를 본적이 있는데,

libxxxx-iphoneos.a libxxx-iphonesimulator.a 를 생성해두고,

Other Linker Flag 에서 -lxxx-$(EFFECTIVE_PLATFORM_NAME) 이런식으로
Platform 에 dependency하게 명명해두고, 사용하는것을 봤다.

예전에, universal 한 static library 를 본적이 있는것 같아서, 언제나 처러 search 고고싱 ~!

lipo 라는 cli tool로 이런 multi arch type library를 merge 할 수 있는데, 

커맨드는 아래와 같다. 

$ lipo -create -output myLib.a myLib-arm6.a myLib-arm7.a myLib-i386.a


정상적으로 생성되었는지 확인하려면 'file'이라는 cli command로 확인가능하다.

$ file myLib.a myLib.a: Mach-O universal binary with 3 architectures

myLib.a (for architecture armv6): current ar archive random library 

myLib.a (for architecture armv7): current ar archive random library 

myLib.a (for architecture i386): current ar archive random library


자 이제 쓸데 없이 거추장 스러웠던 환경변수는 집어 던지고

하나의 library를 그냥 링크하면된다 

최근 아이폰 프로젝트를 아이폰/아이패드 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자동화는 개발 프로세스에 효율을 가는데, 가끔 이런 도구에 너무 집착하여 개발 효율을 오히려 저해하는 경우도 꽤나 많습니다.

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


+ Recent posts