이 글은 인트로스펙션이 무엇인가 아는데만 도움이 되는 수준이다. iOS Model구축을 위해서는 이 내용뿐 아니라 Key-Value Observing, Key-Value Coding도 아는게 좋다. http://blog.jidolstar.com/854


우리가 일상 c나 c++ 스타일로 개발하다보면 구조체나 클래스를 선언하고 그 선언된 구조체나 클래스를 malloc이나 new를 통해 직접 참조하고 사용한다. 이는 정적인 개발법으로 컨텍스트 통제를 철저히 하고 역할이 분명한 상태에서는 좋은 방법이다. 이 방법은 컴파일 타임에 에러를 추측할 수 있어 컴파일 기반 언어 장점을 가진다. 반대로 컴파일 타임이 아닌 런타임에 클래스를 생성하거나 메서드를 임의로 추가/삭제/교체 하던가 하는 동적인 개발법도 있다. 이 방법이 좋은 것은 매우 유연하고 통제에서 자유로워서 런타임에 컨텍스트를 내 마음대로 주무를 수 있다. 하지만 에러를 통제하기 힘들고 잘못사용하면 보안상 위험한데다가 메모리 관리도 신경쓰지 않으면 성능저하도 일으킬 수 있다는 단점이 있다.


요즘 개발법은 딱히 동적이냐 정적이냐에 치우치지 않으며 많은 언어가 이 두 방법을 섞어쓴다. 동적언어냐 정적언어냐라는 것은 사실 얼마나 한쪽에 치우쳐있는가로 갈리는 것뿐이다. 


리플렉션(Reflection)은 정의된 것을 찾는 행위로서 동적언어의 특징이다. 즉, 클래스나 메서드는 컴파일 입장에서 메모리에 올라가서 프로그램 입장에서는 그것을 호출하거나 사용할 수만 있는데, 리플랙션을 지원하는 언어는 그것 뿐 아니라 아에 정의자체를 찾아와서 조작할 수 있는 기능까지 지원한다. 



리플렉션(reflection)은 영어로 반사라는 뜻이 있다. 하지만 개발언어에서는 다른 뜻이며 한글로 반사라고 해석하면 안된다. 리플렉션은 결국 개발계의 고유명사이다. ^^



하지만 리플렉션은 컴파일 검사를 무시하게 되고 권한이나 보안을 깨뜨리기 된다. 그러므로 범용적으로 리플렉션을 쓰는 것은 그리 권장할 만한 것은 아니다. 대신 리플렉션이 정말 유용하게 쓰이는 것은 바로 펙토리 패턴(factory pattern)을 이용해 객체를 생성할 때이다. 정의된 클래스로부터 객체를 생성할 때 switch나 if문으로 분기처리하는 방법대신 클래스 이름만 알고 있으면 아름다운 방법으로 객체를 생성할 수 있다. Java의 경우 다음처럼 할 수 있다.


String[] clasNames = { "myClass1", "myClass2", "myClass3", "myClass4", "myClass5"};
try{
    @SuppressWarnings( "rawtypes" ) 
   Class[] t1 = {};
   for( int i = 0, j = clasNames.length ; i < j ; i++ ){ 
      _types.put( "test." + t0[i].substring( 2 ).toLowerCase(), 
       Class.forName( "com.cookilab.test." + t0[i] ).getConstructor( t1 ));
   }
}

위처럼 클래스의 Contructor를 저장해 두면 아래와 같은 형태로 클래스의 정의 참조가 필요없게 되어 조건문을 없애버리고 객체를 생성할 수 있다.

SuperClass r = (SuperClass) _types.get( "test.myClass2" ).newInstance(); 

iOS에서는 리플랙션(reflection)을 인트로스펙션 (Introspection)이라고 한다. 거의 마찬가지로 객체의 메타데이터(객체의 클래스, 구현 메소드, 프로퍼티, 프로토콜 등의 객체 정보)를 조사하는 과정을 의미한다.  아래 코드를 보자.

Class class = NSClassFromString(@"NSString");
id idOfClass = [[class alloc] initWithString:@"Reflection"];
SEL selector = NSSelectorFromString(@"length"];
NSLog(@"%d", (int)[idOfClass performSElector:selector];
[str release];

클래스를 직접적으로 접근하지 않고 NSString 클래스 명만 가지고 문자열 객체를 만들었고 또한 문자열 객체의 메시지도 직접 접근하지 않고 selector를 만들어 접근한다. 물론 selector가 있는지 없는지도 respondsToSelector:를 통해 알 수 있다. 아무튼 어느것 하나 진짜 정의를 직접 접근하지 않는다. 결국 이런 형태는 컴파일 타임에 체크할 수 없게되고 런타임이 되어야 에러인지 판명이 난다. 


이쯤되면 리플렉션이나 인트로스펙션이 재미있어질지 모르겠다. iOS의 경우 다음 글을 보면 이해가 될지 모른다.


Objective-C instrospection : http://soulpark.wordpress.com/2012/09/03/objective_c_introspection/

Objective-C Runtime Programming Guide : https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Introduction/Introduction.html


인트로스펙션을 사용하면 아래처럼 재미있는 유틸을 만들 수 있다. 이 유틸의 정적 메서드들은 동적으로 클래스나 객체의 프로퍼티를 직접 접근하지 않고 프로퍼티 이름만 가지고 정의 유무를 체크할 수 있고 값을 설정하거나 가져올 수 있다. 또한 클래스나 객체에 정의된 프로퍼티 이름 리스트까지 반환할 수 있다. 또한 프로퍼티의 클래스 정의도 가져올 수 있다. (ARC환경이라고 가정하자)

//클래스로부터 클래스 이름을 가져온다.
+(NSString*)stringFromClass:(Class)clazz {
    return NSStringFromClass( clazz );
}
//클래스 이름으로부터 클래스를 가져온다.
+(Class)classFromString:(NSString*)className {
    return NSClassFromString( className );
}
//객체로부터 클래스를 가져온다.
+(Class)classFromObject:(id)object {
    return [object class];
}
//객체로부터 클래스 이름을 가져온다.  
+(NSString*)classNameFromObject:(id)object {
    return NSStringFromClass( [object class] );
}
//객체에서 주어진 이름을 가진 프로퍼티의 값을 셋팅한다.
+(void)setPropValueOfObject:(id)object name:(NSString*)name value:(id)value {
    Ivar ivar = class_getInstanceVariable([object class], [[NSString stringWithFormat:@"_%@", name] UTF8String]);
    object_setIvar( object, ivar, value );;
}
//객체에서 주어진 이름을 가진 프로퍼티의 값을 가져온다. 
+(id)getPropValueOfObject:(id)object name:(NSString*)name {
    Ivar ivar = class_getInstanceVariable([object class], [[NSString stringWithFormat:@"_%@", name] UTF8String]);
    return object_getIvar( object, ivar );
}
//객체에서 주어진 이름의 프로퍼티를 가지고 있는가?
+(BOOL)hasPropAtObject:(id)object name:(NSString*)name {
    return [self hasPropAtClass:[object class] name:name ];
}
//클래스에서 주어진 이름의 프로퍼티를 가지고 있는가?
+(BOOL)hasPropAtClass:(Class)clazz name:(NSString*)name {
    objc_property_t p0 = class_getProperty(clazz, [name UTF8String]);
    return p0 ? YES : NO;
}
//주어지는 이름을 가진 객체의 프로퍼티를 찾아 그것의 클래스를 가져온다. 
+(Class)getPropClassOfObject:(id)object name:(NSString*)name {
    return [self getPropClassOfClass:[object class] name:name];
}
//주어지는 이름을 가진 클래스의 프로퍼티를 찾아 그것의 클래스를 가져온다. 
+(Class)getPropClassOfClass:(Class)clazz name:(NSString*)name {
    const char *nm = [name UTF8String];
    objc_property_t p0 = class_getProperty( clazz, nm );
    if( p0 == NULL ) {
        return NULL;
    }
    NSString *attr = [NSString stringWithFormat:@"%s", property_getAttributes( p0 )]; 
    NSArray *attrSplit = [attr componentsSeparatedByString:@"\""]; //"T@"NSString",R,V_test"에서 NSString만 추출해야 한다.
    NSString *className = nil;
    if ([attrSplit count] >= 2) {
        className = [attrSplit objectAtIndex:1];
    }
    if( className == nil ) return NULL;
    return NSClassFromString( className );
}
//클래스의 프로퍼티 이름을 배열로 가져온다. superInquiry는 해당클래스의 부모클래스 프로퍼티도 탐색할 것인지 결정하는 플래그다. 
+(NSArray*)getPropNamesOfClass:(Class)clazz superInquiry:(BOOL)superInquiry{
    if( clazz == NULL || clazz == [NSObject class] ) {
        return nil;
    }
    NSMutableArray *r = [[NSMutableArray alloc] init];
    unsigned int count, i;
    objc_property_t *ps = class_copyPropertyList( clazz, &count );
    for( i = 0; i < count; i++ ) {
        objc_property_t p = ps[i];
        const char *pn = property_getName( p );
        if( pn ) {
            [r addObject:[NSString stringWithUTF8String:pn]];
        }
    }
    free( ps );
    if( superInquiry ) {
        NSArray *sr = [self getPropNamesOfClass:[clazz superclass] superInquiry:YES];
        if( sr != nil ) [r addObjectsFromArray:sr];
    }
    return [NSArray arrayWithArray:r];
}

사실 위 코드만 가지고는 별로 유용하다고 볼 수 없다. 일단 유용성을 떠나서 동적으로 프로퍼티 정의가 있는지 체크하는 로직은 뭔가 캐쉬처리를 통해 속도개선해야 한다. 또한 인터페이스를 더욱 세밀하게 만들어 다양한 곳에 활용할 수 있게 만들 수 있다. 가령 값을 셋팅한다고 할때... (구축한게 아니라 이런걸 지원하는 걸 만들겠다는 것이다.)

MyModel *model = [[MyModel alloc]init];
[PropUtil setPropValueOfObject:model key:@"node1.node2[]" value:@"myValue"];

이렇게 만들면 model에 정의된 node1이름의 프로퍼티에 node2 프로퍼티가 정의되어 있고 node2는 배열이다. 여기에 문자열 "myValue"를 추가할 수 있다. 이쯤 되야 비로서 유용해질 수 있을 듯하다. 만약, 매크로(define)이나 c함수등의 인터페이스를 더 만들면 아래처럼도 가능해진다.

model( @"node1.node2[]", @"myValue"); 
NSString *v = model( @"node1.node2[#last]" ); //myValue 
즉, node1.node2배열에 "myValue"를 추가하고 방금 추가한 값을 돌려받는다. get, set은 인자의 숫자로 생각하면 된다. 즉, 짝수이면 set 홀수이면 get이다. 이걸 더 확장해서 생각해보면 아래처럼도 된다.

NSString *v = model( @"node1.node2[]", @"myValue", @"node1.node2[#last]" ); //myValue 

결국 set하고 마지막에 홀수 인자를 가지므로 그 값을 반환한다. 이쯤되면 아래와 같은 인터페이스도 가능하겠다.

NSString *v = model( @"node1.node2[]", [@"v1", @"v2", @"v3"], @"node1.node2[#last]", @"v4", @"node1.node2[#last]", __DEL__ , @"node1.node2[#last]" ); //v3

정적방식이였으면 아래처럼 해야한다.

MyModel *model = [[MyModel alloc]init];
MyNode1 *node1 = model.node1;
NSMutableArray *node2 = nodel1.node2;
[node2 addObject:@"v1"];
[node2 addObject:@"v2"];
[node2 addObject:@"v3"];
[node2 addObject:@"v4"];
[node2 removeLastObject];
NSString *v = [node2 lastObject];

길이가 확 늘어났다. 이뿐이겠는가? 위 코드는 컨테이너인 배열(NSMutableArray)의 참조를 받아 addObject, removeObject하고 있다. Model입장에서는 이런식으로 처리되는 것을 원치 않을 것이다. 뭔가 Model 내에 값이 바뀔 때, Model의 변화를 한번에 통지해야하는 방식으로 취해야 하거나 값의 유효성을 검증시켜야 한다는 상황이 발생하면 분명 해결하기 어려운 방법이다. 하지만 그에 앞선 코드인 1줄 코드를 보면 코드만 짧아진게 아니라 model()내부적으로 값의 변화를 감지해서 통제할 수 있게 된다. Model에 들어오는 자신의 데이터를 통제하고 변화를 외부에 통지할 수 있을때 진정한 Model이 아니었던가? 리플렉션(인트로스펙션)을 사용하면 바로 이러한 점을 장점으로 승화시킬 수 있게 된다.


참고 

SEL Method IMP 자료형에 대해서 : http://10apps.tistory.com/131

Java Reflection API의 사용 http://micropilot.tistory.com/entry/Java-Reflection-API-introduction

Java 리플렉션에 대한 재고(reflection)(1) http://www.hanb.co.kr/network/view.html?bi_id=1369 

Java 리플렉션에 대한 재고(reflection)(2) http://www.hanb.co.kr/network/view.html?bi_id=1370 


글쓴이 : 지돌스타(http://blog.jidolstar.com/851)




아이폰용도 나왔습니다. ^^ 

http://itunes.apple.com/app/id446340438
  



스타플(http://starpl.com)에서 친구들과 함께 하는 SNS형 다마고치 '스타펫' 어플을 출시했습니다. 

지금 바로! 안드로이드 마켓에서 다운로드 받을 수 있어요.(아이폰용도 조만간 오픈할 것입니다.)

스타펫받기 : https://market.android.com/details?id=com.starpl.android.starpet


 


귀여운 스타펫과 함께 보내신다면 여러분의 하루가 더 즐겁지 않을까요? ^^












*** [스타펫]으로 작성한 글/댓글은 스타플(starpl.com) 웹서비스에도 자동 업로드됩니다. 
PC에서 www.Starpl.com으로 접속하시면 더욱 다양한 기능의 스타플 서비스를 이용하실 수 있습니다.


*** 다양한 스타플 연동 어플리스트

(안드로이드)바른생활
(안드로이드)책을읽자!
- (안드로이드)아이돌배틀
- (안드로이드)자랑질

(아이폰)책을읽자!
(아이폰)바른생활
(아이폰)신조어능력시험
(아이폰)어플지름신
(아이폰)타임라인 
(아이폰)오픈배경화면

- (아이폰)자랑질 

글쓴이 : 지돌스타(http://blog.jidolstar.com)



Adobe AIR 2.7 버전이 정식 배포되었습니다. 개발자를 위한 SDK와 Runtime이 함께 배포되었습니다. Adobe AIR 2.7은 데스크탑 또는 모바일 환경 모두 동작하며 모바일의 경우 안드로이드 2.2 이상, BlackBerry Tablet OS, iOS 4.0 이상에서 동작합니다. 

현재 Flash Builder 4.5는 아직 Flex기반 iOS 어플 개발이 불가능하지만 Flash builder 4.5가 이번 달에 새로 배포되면 AIR 2.7이 같이 탑재되면서 Flex로 iOS 어플 개발이 가능하게 됩니다. 현재 Flash Builder 4.5에 AIR 2.7이 없어서 Flex로 iOS 개발을 막아놓았던 것 같네요.

iOS를 위한 AIR 어플은 CPU모드에서 4배이상 빠른 렌더링을 지원합니다. 기존에 Flex로 iOS기반 어플을 만들게 되면 퍼포먼스 문제가 있었는데 이 부분이 말끔하게 해결된 듯합니다. 다음 동영상을 보세요. 

Adobe AIR 2.7 : Faster app performance on iOS  http://goo.gl/VJxnR 

 iOS에서 디버깅 및 테스팅 환경이 많이 개선되었다고 합니다. 또한 Android기반에서 SD카드에 AIR 런타임을 설치할 수 있게 되었습니다. 

데스크탑환경에서 Media Measurement를 위한 통합기반이 지원됩니다. Adobe Flash Player 10.3에서 부터 지원되었으면 AIR 2.7에서도 이것을 지원합니다. 관련된 내용은 아래 링크를 참고하세요.

Measuring video consumption in Flash : http://goo.gl/wPcf0 

또한 Acoustic Echo Cancellation를 지원합니다. Flash Player 10.3에서도 지원하고 있습니다. 여기에 관련된 API가 도입됨에 따라 헤드셋없이도 애코조절이 훨씬 원할해집니다.

그리고 HTMLLoader에서 HTML 컨텐츠에 대한 로케이션 제어 권한이 훨씬 향상되었습니다. 과거에 이 부분때문에 고생한 적이 생각난다는..... 

다음 사이트들을 둘러보시면 AIR 2.7을 이해하는데 더욱 도움이 될 것입니다. 

Adobe AIR 2.7 Now Available : iOS apps 4x Faster! : http://goo.gl/5IEIz 
Adobe AIR 2.7 Developer Release Notes : http://kb2.adobe.com/cps/906/cpsid_90612.html 
Adobe AIR 공식페이지 : http://www.adobe.com/products/air/
Download Adobe AIR SDK : http://www.adobe.com/products/air/ 
Adobe Flash Builder : http://www.adobe.com/products/flash-builder.html

글쓴이 : 지돌스타(http://blog.jidolstar.com
Adobe CS 5.5가 출시 되었습니다. 동시에 Flash Builder 4.5와 Flash Catalyst CS 5.5도 함께 출시되었습니다. 개인적으로는 Flash Builder 4.5를 많이 기다렸습니다. 

Flash Builder 4.5의 공식 홈페이지는 다음과 같습니다.

Adobe Flash Builder 4.5 : http://www.adobe.com/products/flash-builder.html 

개인적으로 보는 Flash Builder 4.5의 가장 큰 변화는 모바일, Flex 4.5, PHP 개발지원등이 아닌가 싶습니다. 
특별히 기존 beta버전으로 공개되었던 Burrito에서는 안드로이드 기기 컴파일만 지원했는데 이번에는 iOS 어플 개발도 지원해줍니다. Flash IDE를 잘 사용할 줄 모르는 저로서는 가장 기다렸던 기능중에 하나입니다. 

다운로드는 다음 링크에서 Try버전으로 다운받을 수 있습니다. 

Adobe Flash Builder 4.5 다운로드 : http://goo.gl/hDi55

아래 화면은 Flash Builder 4.5를 설치하는 중간 화면입니다. AIR For Apple iOS Support가 눈에 띕니다. 


Flash Builder 설치후 실행한다음 ActionScript Mobile AIR Project를 만들면 다음과 같은 Setting 화면을 볼 수 있습니다. 기존에는 Android 어플만 개발이 가능했으나 이제 1개의 프로젝트로 아이폰, 안드로이드 개발이 모두 가능해졌습니다. (참고 : Flex Mobile AIR Project 에서는 여전히 Android 기반으로밖에 지원하지 않는 것 같았습니다.)



처음 실행해보면 아래와 같은 설정화면이 나옵니다. 이 부분은 Flash Builder Burrito를 이용해 Android 개발을 해본 사람은 친숙한 화면입니다. Apple iOS가 Target platform으로 등록되어 있음을 주목하세요. (참고 - Flex로 쉽게 모바일 어플을 만들자. - Adobe AIR Launchpad)


이제.... Flash Builder 에서도 iOS, Android 어플 개발이 모두 가능해졌군요. 더불어 테스트 해볼 것이 많아졌습니다.

참고로 Adobe CS 5.5는 현재 Try버전으로 다운받아 설치해 볼 수 있습니다. 다음 링크를 참고하세요.

Download a free trial of Creative Suite 5.5 Master Collection 

위 링크에서 다운로드 받아 설치하고 있는 화면입니다. Adobe AIR를 이용해 설치하네요. 



Adobe Flash CS 5.5, Flash Builder 4.5, Flash Catalyst CS 5.5 에 대해서 더욱 자세히 알고 싶다면 다음 링크를 참고하면 좋겠습니다.

Adobe Flash Builder 4.5 : http://help.adobe.com/en_US/flashbuilder/using/index.html
Adobe Flash Professional CS 5.5 : http://help.adobe.com/ko_KR/flash/cs/using/index.html 
Adobe Flash Catalyst CS 5.5 : http://help.adobe.com/ko_KR/flashcatalyst/cs/using/index.html 


좀더 학습을 원하시면 다음 링크도 유용합니다.

Adobe Flex 4.5 문서 : http://help.adobe.com/en_US/flex/using/index.html
Adobe Flash Platform용 ActionScript 3.0 참조 설명서 : http://help.adobe.com/ko_KR/FlashPlatform/reference/actionscript/3/index.html
Adobe AIR : http://help.adobe.com/ko_KR/air/build/index.html 
Adobe 개발자 센터 :  http://www.adobe.com/devnet.html 
Tour de Flex : http://www.adobe.com/devnet/flex/tourdeflex.html 
 
글쓴이 : 지돌스타(http://blog.jidolstar.com/765)  
이 글은 iOS에서 Local Notification에 대한 전반적인 이해를 돕기위한 글이다. 그래서 유사형태인 APNS와 비교해보기도 했다. 검색해보면 Local Notification에 대한 문의는 있어도 이에 대해서 설명된 한글문서가 없는 것 같아 정리해보았다.

Local Notification에 대해
iOS 4.0 이상부터 Local Notification(지역알림, 내부통지, http://goo.gl/jFFsT)을 지원한다.  그러므로  iOS 4.0 미만 버전을 지원하는 어플의 경우 호환성을 유지할 수 있도록 개발해야할 것이다.

Local Notification은 어플이 실행중이 아니거나 Background, Foreground 상태에 관계없이 지정된 날짜와 시간에 필요한 최소한의 데이터를 전송하여 어플에게 전달해 실행하거나 다른 행동을 취할 수 있도록 구현할 수 있다. 심지어 Local Notification을 예약하고 iOS를 재부팅해도 정상적으로 동작한다.  이러한 형태가 가능한 것은 Local Notification 관리는 iOS 자체에서 하기 때문이다. 

Local Notification과 APNS(Apple Push Notification Service, http://goo.gl/fWknl)의 큰 차이점은 Notification을 위한 서버가 필요한가 아닌가의 차이에 있다. APNS는 서버가 준비가 되어 있어야 하고 Push를 위한 인증서도 발급해야한다. 처음 테스트 환경을 만들고 운용하는게 복잡한 편이다. 반면 Local Notification은 이런 절차가 전혀 필요없이 간단하게 코드상에서 처리할 수 있다. 이처럼 Local Notification을 사용하는 방법은 꽤 간단한 편이다. 하지만 APNS에 비해서 테스트하기가 좀 까다로운 면이 있다. 왜냐하면 Local Notification은 시간기반으로 하는 통지가 되는 형태이기 때문에 통지에 대한 제어권이 iOS에 있으며 개발자에게 있다고 볼 수 없기 때문이다. 반면에 APNS는 서버측에서 어떤 시간에 상관없이 개발자가 원하는데로 바로 통지를 할 수 있다. 

Local Notification와 APNS가 이처럼 차이점이 있지만 넘겨주는 데이터나 동작하는 방식은 거의 유사한 편이다.  그래서 어플내에서 동작하는 형태를 하나의 인터페이스로 만들어 운영할 수 있다. 이것이 가능한 이유는 둘다 PayLoad(http://goo.gl/mulVQ)가 유사한 형태를 가지며 통지되었을때 같은 동작을 하기 때문이다. (PayLoad는 일종의 메시지 데이터를 보유하는 패킷이나 프레임의 부분을 말한다.) PayLoad에는 Notification Type과 Custom Data를 담는다. Notification Type은 메시지 ,버튼 제목, 아이콘뱃지숫자, 사운드등으로 구성되어 있고 Custom Data에는 이외에 데이터가 필요한 경우  Dictionary형태로 구성된다. 하지만 APNS와 Location Notification간에 PayLoad데이터 관리의 차이는 있다. APNS는 Dictionary로 관리되며, Location Notification은 UILocalNotification 클래스(http://goo.gl/3TyTg)로 관리 된다. 

Local Notification은 APNS와 다르게 시간기반으로 운용된다. 이것은 iOS에서 스케쥴링을 해준다는 말과 같다. 그래서 통지가 일어나야하는 날짜와 시간을 정해줘야한다. 

Local Notification 사용법 
지금까지  Local Notification을 설명하면서 APNS와의 차이점도 알아보았다. 이러한 내용을 미리 언급하는 것은 처음 이를 접근할 때 Local Notification에 대한 전반적인 이해가 필요하기 때문이다. 여러가지로 복잡하고 잘 이해가 안되겠지만 실제로 해보면 이보다 쉬울 순 없다. 

Local Notification을 학습하기 위해서  Apple문서를 먼저 참고하는 것이 좋겠다.
 - About Local Notifications and Push Notifications : http://goo.gl/jFFsT

그럼 간단하게 Local Notification을 사용하는 방법을 알아보자. 크게 등록과 처리하는 방법을 구분해서 정리했다.

1. Local Notification 등록하기

Xcode상에서 Window Based 어플 프로젝트를 생성한뒤 AppDeleage 소스코드내에 application:didFinishLaunchingwithOptions: 메시지에 다음 코드를 넣는다.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    

    // Override point for customization after application launch.

    

    [self.window makeKeyAndVisible];


    //통지시간 정하기 

NSCalendar *calendar = [NSCalendar autoupdatingCurrentCalendar];

NSDateComponents *dateComps = [[NSDateComponents alloc] init];

[dateComps setYear:2011];

[dateComps setMonth:3];

[dateComps setDay:22];

[dateComps setHour:15];

[dateComps setMinute:30];

[dateComps setSecond:0];

NSDate *date = [calendar dateFromComponents:dateComps];

[dateComps release];

UILocalNotification *localNotif = [[UILocalNotification alloc]init];

if (localNotif != nil

{

//통지시간 

localNotif.fireDate = date;

localNotif.timeZone = [NSTimeZone defaultTimeZone];

//Payload

localNotif.alertBody = [NSString stringWithFormat:@"내부통지 %@",date];

localNotif.alertAction = @"상세보기";

localNotif.soundName = UILocalNotificationDefaultSoundName;

localNotif.applicationIconBadgeNumber = 1;

//Custom Data

NSDictionary *infoDict = [NSDictionary dictionaryWithObject:@"mypage" forKey:@"page"];

localNotif.userInfo = infoDict;

//Local Notification 등록

[[UIApplication sharedApplication] scheduleLocalNotification:localNotif];

}

[localNotif release];

    return YES;

}


이게 끝이다. Local Notification을 등록하는 시점은 어디라도 상관없다. 필요할 때 등록하면 된다. UILocalNotification 객체를 생성하고 통지시간, Payload, Custom Data를 설정한뒤 등록하면 지정된 시간에 통지가 발생할 것이다. APNS럼 APNS 서버에 DeviceToken을 받는 과정이 모두 생략되기 때문에 등록절차는 매우 쉬운 것을 알 수 있다. 

이 코드에서 Local Notification 등록을 scheduleLocalNotification을 사용했다. 필요한 경우에 스케쥴링 필요없이 즉각적으로 Local Notification을 줄 필요가 있는 경우에는 다음 메시지를 사용하면 되겠다. 

[[UIApplication sharedApplication] presentLocalNotificationNow:localNotif];


만약 fireDate를 현재시간보다 이전시간으로 등록하면 iOS는 바로 통지해버린다. 이 말이 그냥 쉽게 넘어갈 수 있는 문제인데 경험상 현재시간보다 이전시간으로 가는 경우는 없기 때문에 왠만하면 이전시간으로 등록하는 경우가 없도록 코딩을 해야 버그를 유발시키지 않을 것이다.

만약 이전에 등록된 Local Notification을 삭제하고 싶은 경우가 발생할 수 있다. 그런 경우에는 다음 메시지 중에 하나를 사용하면 되겠다.

[[UIApplication sharedApplication] cancelAllLocalNotifications];

[[UIApplication sharedApplication] cancelLocalNotification:localNotif];




2. Local Notification 처리하기 
앞서 Local Notification을 등록하는 방법을 알았다. 등록된 통지는 iOS에서 스케쥴링해준다.  어플의 실행여부, 및 iOS재부팅에 전혀 상관없이 동작하기 때문에 개발자는 이런부분에 대해 별다른 노력없이 통지 처리에 대한 로직만 만들면 된다.

내부 통지는 다음 3가지 형태로 처리가 가능하다. 이는 APNS와 유사하다.

[경우 1] 어플이 실행중이지 않은 경우 
이 경우에 iOS에 등록된 통지를 정해진 시간에 발생하면 자동으로 Alert창을 띄워준다. 거기에는 UILocalNotification에 Payload로 등록한 alertBody, alertAction이 반영되어 있으며 주어진 사운드를 들려준다. 어플의 아이콘에도 정해진 badge숫자가 표시된다. 


위의 경우 특별히 AppDelegate에서 application:didFinishLaunchingWithOptions: 메세지 내에서 통지된 UILocalNotification객체를 참고할 수 있다. Local Notification을 통해 어플이 실행되는 경우 별다른 작업이 필요하다면 이를 이용하면 유용할 것이다. 접근방법은 간단한다. 아래처럼 접근해서 사용하면 되겠다.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    

    // Override point for customization after application launch.

    

    [self.window makeKeyAndVisible];

UILocalNotification *notif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];

if (notif != nil

{

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"알림" 

message:[NSString stringWithFormat:@"didFinishLaunchingWithOptions %@",notif.alertBody

  delegate:nil 

  cancelButtonTitle:nil 

  otherButtonTitles:@"확인",nil];

[alert show];

[alert release];

}

    return YES;

}


이 경우 다음과 같은 화면을 볼 수 있을 것이다. 



또한 AppDelegate 상에 application:didReceiveLocalNotification: 메시지를 구현하면 Local Notification을 처리할 수 있다. 이 메시지는 다음에 나오는 [경우2], [경우3]에도 동일하게 동작한다. 아래처럼 구현해보자.

-(void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification

{

application.applicationIconBadgeNumber = 0;

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"알림" 

message:[NSString stringWithFormat:@"didReceiveLocalNotification %@",notification.alertBody

  delegate:nil 

  cancelButtonTitle:nil 

  otherButtonTitles:@"확인",nil];

[alert show];

[alert release];

}


다음은 위코드가 실행될 때 화면이다.



[경우2]어플이 실행중이나 background에 위치해 있는 경우 
[경우1]과의 유일한 차이점은 application:didFinishLaunchingWithOptions: 를 통해 UILocalNotification 객체정보를 얻을 수 없다는 것이다. 이 경우에는 이미 어플이 실행중이기 때문에 어플이 처음 실행할때 최초 한번만 호출되는 AppDeleage의 application:didReceiveLocalNotification:가 실행될 수 없기 때문이다. 이 의미는 application:didFinishLaunchingWithOptions: 와 application:didReceiveLocalNotification: 에서 얻어지는 UILocalNotification 객체정보가 같더라도 서로 다른 일을 할 수 있도록 한다는 점을 시사한다.  

application:didReceiveLocalNotification: 메시지는 여전히 호출되므로 이때 UILocalNotification 객체를 처리하면 되겠다.


[경우3]어플이 실행중이며 foreground에 위치해 있는 경우
마지막으로 어플이 화면위에 실행중인 경우이다. 이때도 [경우1], [경우2]와 차이점은 iOS에서 직접적으로 Alert창을 띄워주지 않는다는 점이다. application:didFinishLaunchingWithOptions: 는 당연히 호출되지 않으며 application:didReceiveLocalNotification:  만 호출된다. 


Local Notification 처리에 대한 경우 1, 2, 3을 표로 정리하면 다음과 같을 것이다. 
   경고창   didFinishLaunchingWithOptions를 통한 UILocalNotification참고  didReciveLocalNotification메시지를 통해 UILocalNotification 참고
 경우 1 (어플 실행X)  O
 경우 2 (어플 background)  O
 경우 3 (어플 foreground)


지금까지 설명한 내용 대해서 더 자세히 알고 싶다면 다음 문서를 참고한다.
Scheduling, Registering, and Handling Notifications : http://goo.gl/qHVPi


팁(Tip)
Local Notification을 사용하면서 이런 점은 알아둘 필요가 있다. 

1. 등록시 현재 시간보다 빠른 시간을 등록하면 바로 통지가 된다. 이 점은 어플 버그를 유발할 수 있으므로 각별히 주의하길 바란다. 개발 경험상에서 말씀드린다. (여기서 버그는 실제 버그가 아니라 의도하지 않는 동작을 유발시킨다는 말이다.)

2. 테스트를 위해서는 아이폰 자체 시간설정 기능을 적극 활용하면 되겠다. Local Notification의 통지는 iOS가 스케쥴링을 해주기 때문에 개발자가 통제할 수 없다. 그러므로 아이폰 자체 시간설정 기능을 활용해 테스트할 수 있겠다.

3. PayLoad대신 Custom Data를 사용하는 방법은 별도로 설명을 안했지만 UILocalNotification상에 userInfo속성을 활용하면 된다는 점을 설명하지 않아도 여러분은 잘 사용할 수 있을 것이라 생각한다. ^^


정리하며
Local Notification에 대해서 전반적인 내용을 다뤄보았다. 생각보다 개념자체는 간단하지만 어느 경우에 어떻게 사용하고 동작하는지에 대한 명확한 지식이 없으면 오용할 여지는 많다. 또한 잘못 사용하면 뜻하지 않는 동작을 유발할 수 있기 때문에 등록시 시간을 잘 설정해야하는 것은 꼭 염두해야 한다. 또한 클라이언트 베이스로 등록/처리를 해야하는 부분은 이에 대한 적절한 설계를 바탕으로 하지 않으면 로직이 복잡해져 관리가 어려워질 수 있다는 점도 개인적인 경험이다. 

아무튼 회사에서 이를 이용한 관련 어플을 만들면서 처음 사용해 봤는데 자세히 알지 않고 사용하면 문제가 될 것 같아 정리를 해보았다.


관련글
About Local Notifications and Push Notifications : http://goo.gl/jFFsT
The Notification Payload : http://goo.gl/mulVQ
UILocalNotification 클래스 : http://goo.gl/3TyTg
Scheduling, Registering, and Handling Notifications : http://goo.gl/qHVPi
내가 만든 어플에 Push Notification 적용하기 : http://goo.gl/kH7BQ 
APNS 개발버전과 배포버전의 차이점. : http://blog.jidolstar.com/726
APNS Device Token을 못받아올때 : http://blog.jidolstar.com/725
 
글쓴이 : 지돌스타(http://blog.jidolstar.com/758)  
얼마전 아이폰 어플의 개발 요구사항중에 아이폰내 AppStore로 이동시키는 기능구현이 있었다. 이 글은 그것을 이해하는데 필요한 전반적인 기본 지식을 간단히 정리했다.

Apple iOS Applications의 URL 스키마
인터넷에서 링크는 URL 스키마(scheme)인 http, mailto, ftp, feed등을 이용해 종류가 구분된다. 이는 범용적인 예이다.  반면  iOS 링크는 이보다 더 특별한 기능을 가진다. 가령 "sms:전화번호"로 하면 문자메시지 보낼 수 있는 링크가 되며 "tel:전화번호"는 자동으로 전화를 걸 수 있다. 이런 특별한 스키마를 사용하지 않더라도 http를 그대로써서 필요하다면 관련 어플로 이동시키는 것도 가능하다. 가령, 구글맵의 경우 "http://maps.google.com/maps?q=검색위치" 형태로 접근하면 구글맵 어플이 열린다. 이이튠즈도 "http://itunes.apple.com"으로 시작하면 아이튠즈 어플이 실행된다. 유투브도 비슷하다. 

활용하는 방법은 어플상에서 iOS SDK에서 제공하는 [[UIApplication sharedApplication] openURL: ]을 이용하거나 사파리 주소창이나 링크에 위 사항을 적용하면 되겠다.

지금까지 설명한 URL 스키마들은 모두 iOS에서 기본으로 제공하는 것이다. 

다음 링크에는 iOS URL 스키마의 종류와 사용예를 잘 정리하고 있다.


Third Party Applications의 커스텀 URL 스키마
개발자는 커스텀 URL스키마를 만들 수 있다. 이는 어플만의 URL 스키마를 사용하면 가능해진다. 즉, URL스키마를 이용하면 자신이 만든 어플도 사파리에서 띄울 수 있게끔 유도할 수 있다는 것을 뜻한다.  

다음 글에는 커스텀 URL 스키마를 어떻게 어플에 적용하는가 보여주고 있다.


다음 소개하는 어플은 [[UIApplication sharedApplication] openURL:]을 활용한 어플로 URL 스키마를 테스트 해볼 수 있는 어플이다. (찾아봤더니 진짜 있네? ^^) 

Open URL : http://goo.gl/8n9mV



아이폰 어플에서 AppStore 검색페이지로 이동하기

이 글의 목적은 바로 아이폰 어플에서 아래 화면처럼 AppStore 어플의 검색페이지 이동하여 특정키워드로 검색하는 방법을 설명하는 것이다.  지금까지 URL 스키마를 설명했으니 이것도 URL을 통해 실행이 가능하다.


iOS 개발자 분들이라면 아래와 같은 URL을 이용하면 AppStore의 해당 어플 페이지로 이동한다는 것은 알고 있을 것이다.

http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=406292683
http://itunes.apple.com/kr/app/id406292683

이것은 1개의 어플을 찾을때는 유용하다. 그럼 검색은? 특별히 우리 회사에서 만든 어플을 찾아내야 한다면?

http://itunes.com/회사명
http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewArtist?id=회사아이디
http://itunes.apple.com/us/artist/회사명/id회사아이디 
item-apps://itunes.apple.com/us/developer/회사명/id회사아이디

등등..... 검색해서 다 해봤지만 전부 아이튠즈 어플로만 이동한다. 앱스토어에 이동해야하는데 말이다. 물론 맥에서는 잘 찾아간다. 단지 아이폰/아이팟터치에서는 아이튠즈와 앱스토어 어플이 분리되어 있는게 문제인 것이다.

하루 걸러서 검색으로 찾은 결과 방법을 알아냈다.
이것도 결국 URL을 이용하면 된다. (맥 사파리나, 아이폰 사파리, [[UIApplication sharedApplication] openURL: ] 을 이용해 접근하면 확인할 수 있겠다.)


마지막 term 파라미터에 회사명만 적어주면 된다. 

아~ 이것을 알아내느라 몇시간을 소비했다. 
좋은 팁이였길 바란다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/751)








개인적으로 ActionScript 3.0 기반에서 개발을 많이 하다보니 Objective-C의 target/selector 형태로 접근하는게 익숙하지 않았다. 사실 ActionScript 3.0에서는 더 고수준으로 랩핑 처리하다 보니 selector격인 함수의 참조만 보내줘도 되긴하다. 그렇지만 이러한 사실이 이해가 된다고 하더라도 매번 target/selector를 보내고 처리하는게 여간 불편하지 않았다.

아래는 Objective-C에서 target/selector를 사용하는 방법이다. 뭔가 다른 클래스에 대리 요청을 하기 위한 방법으로 이렇게 많이 쓸 것이다.

위 방법이 익숙해지면 사용하는데 어렵지는 않다. 그냥 target/selector 넘겨주고 실행해주면 그만이다. 앞서 말했지만 나는 이러한 것에 익숙하지 않다. 그래서 2개를 보내는 형태가 아닌 하나로 묶어서 보내고 싶었다. 아래처럼 말이다. 


위 도식에서 Action클래스는 target/selector를 관리하고 실행해주는 역할을 한다.

뭔가 더 복잡해보인다. 하지만 이 방법의 장점은 target/selector로 두개의 변수를 관리하는게 아니라 이것을 하나로 묶은 Action클래스의 객체만 관리하면 된다는 것이다. 

Action내에 있는 target/selector를 실행하기 위해 위 도식에서 [_tempSuccessAction run]과 같이 하면 된다. 이것을 조금더 확장해서 생각하면 [_tempSuccess runWithData:data] 처럼 할 수 있고 스레드에도 대응시키기 위해 [_tempSuccess runInThreadWithWaitUntilDone:YES] 와 [_tempSuccess runInThreadWithData:data waitUntilDone:YES] 처럼 사용할 수 있도록 Action에 관련 메서드를 추가해도 된다. 이렇게 생각해보니 Action 클래스로 target/selector를 관리하는게 여러가지로 장점이 될 수 있다는 것을 알 수 있다. 

아래 코드는 Action 클래스의 header 정의 부분이다.

//

//  Action.h

//

//  Created by Yongho Ji on 10. 12. 7..

//  Copyright 2010 Wecon Communications. All rights reserved.

//


#import <Foundation/Foundation.h>


//target/selector 하나로 묶고 실행해주는 클래스 

@interface Action : NSObject 

{

@private

id _target; //selector 가지고 있는 클래스 객체 참조

SEL _selector; //target안에 정의된 selector 메서드 

}


//초기화 

-(id)initWithTarget:(id)target selector:(SEL)selector;


//target/selector 실행 

-(void)run;


//target/selector 실행하되 selector인자로 data 넘김 

-(void)runWithData:(id)data;


//(Thread안에서 호출)target/selector 실행.

-(void)runInThreadWithWaitUntilDone:(BOOL)waitUntilDone;


//(Thread안에서 호출)target/selector 실행하되 selector인자로 data 넘김 

-(void)runInThreadWithData:(id)data waitUntilDone:(BOOL)waitUntilDone;

@end



Thread내에서는 runInThread... 를 호출해주면 그만이다. 구현부를 보자.

//

//  Action.m

//

//  Created by Yongho Ji on 10. 12. 7..

//  Copyright 2010 Wecon Communications. All rights reserved.

//


#import "Action.h"



@implementation Action


-(void)dealloc 

{

[super dealloc];

//[_target release];

}


-(id)initWithTarget:(id)target selector:(SEL)selector 

{

if (self = [super init]) 

{

//[target retain];

//[_target release];

_target = target; //retain하지 않고 assign 한다. 왜냐하면 retain하면 메모리 누수를 야기시킬 있으므로 

_selector = selector;

}

return self;

}


-(void)run 

{

@try {

if (_target != nil && _selector != nil

{

[_target performSelector:_selector];

}

}

@catch (NSException * e) {

NSLog(@"%@",e);

}

}


-(void)runWithData:(id)data 

{

@try {

if (_target != nil && _selector != nil

{

@try {

[_target performSelector:_selector withObject:data];

}

@catch (NSException * e) {

[_target performSelector:_selector];

}

}

}

@catch (NSException * e) {

NSLog(@"%@",e);

}

}


-(void)runInThreadWithWaitUntilDone:(BOOL)waitUntilDone 

{

@try {

if (_target != nil && _selector != nil

{

[_target performSelectorOnMainThread:_selector withObject:nil waitUntilDone:waitUntilDone];

}

}

@catch (NSException * e) {

NSLog(@"%@",e);

}

}


-(void)runInThreadWithData:(id)data waitUntilDone:(BOOL)waitUntilDone

{

@try {

if (_target != nil && _selector != nil

{

@try {

[_target performSelectorOnMainThread:_selector withObject:data waitUntilDone:waitUntilDone];

}

@catch (NSException * e) {

[_target performSelectorOnMainThread:_selector withObject:nil waitUntilDone:waitUntilDone];

}

}

}

@catch (NSException * e) {

NSLog(@"%@",e);

}

}



@end



사용방법은 이미 설명했다. 간단한 아이디어지만 좀 더 확장해서 생각해보면 Action 클래스에 추가할 수 있는 것들이 많을 것 같다.


비슷한 이유에서 통지대신 이벤트를 만들어 사용한다.
NSNotification 대신 Event 사용하기 http://blog.jidolstar.com/721

글쓴이 : 지돌스타(http://blog.jidolstar.com/742


iOS 어플상에서 기기의 종류를 알아올 수 있는 코드이다.

#include <sys/sysctl.h>    // sysctlbyname 의 사용을 위해

// 모델 정보 보기 - 상세히
+ (NSString *) platform {
    size_t size;
    sysctlbyname("hw.machine", NULL, &size, NULL, 0);
    char *machine = malloc(size);
    sysctlbyname("hw.machine", machine, &size, NULL, 0);
    /*
     Possible values:
     "i386" = iPhone Simulator
     "iPhone1,1" = iPhone 1G
     "iPhone1,2" = iPhone 3G
     "iPhone2,1" = iPhone 3GS
     "iPhone3,1" = iPhone 4
     "iPod1,1"   = iPod touch 1G
     "iPod2,1"   = iPod touch 2G
     "iPod3,1"   = iPod touch 3G
     */
    NSString *platform = [NSString stringWithUTF8String:machine];
    free(machine);
    
    return platform;
}

출처 : http://cafe.naver.com/mcbugi/81305


Objective-C에서 retain/release 메커니즘을 이해하고 익숙해지는데 오랜 시간이 걸렸다. 이해 자체는 빠를지언정 적절히 쓰는 건 많이 어려운 것 같다.

retain/release/autorelease 를 쓰는 이유는 사실 많이 공개되어 있다. 하지만 retain을 하면 안되는 경우에 대해 다루는 글이 많지 않은 것 같다.

delegate는 기본적으로 retain이 아닌 assign을 한다. 만약 ViewController가 어떤 객체의 delegate대상으로 설정될때 retain을 해버리면 어떤 객체가 delegate를 release해주지 않는 이상 ViewController는 dealloc이 호출되지 않는 문제가 발생한다. 그러면 자연스럽게 메모리 릭(memory leak)이 발생한다. 그렇기 때문에 delegate는 assign을 해주는 것이다.

이런 경우도 있다. UITableViewCell을 커스터마이징처리한 CustomTableViewCell이 있다고 하자. 여기에 UITableViewController를 확장한 MyTableViewController가 이 CustomTableViewCell을 사용한다고 가정하자. 보통 같으면 사용에 문제가 없지만 만약 CustomTableViewCell에서 MyTableViewCeontroller의 메서드를 호출할 수 있게 하기 위해 MyTableViewController 객체 자신(self)과 그 메서드(@selector(myMethod:))로 CustomTableViewCell에서 참조하게 할 수 있다. 이때 MyTableViewController의 참조를 retain해줘버리면 나중에 MyTableViewController이 dealloc 메서드가 호출될 시점을 읽어버릴 가능성이 100%가 된다. 왜냐하면 CustomTableViewCell에서 retain처리된 MyTableViewController의 객체를 다시 release할 수 있는 명확한 메커니즘을 추가하기 힘들기 때문이다. 그래서 이런 경우에도 retain이 아닌 assign을 사용함이 옳다.

조금 다르지만 NSTimer를 이용할때도 비슷한 경험을 할 수 있다. [NSTimer scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:] 메서드를 통해 target과 selector를 넘겨주는데 이때 내부적으로 NSTimer는 자기 자신과 target을 retain시켜주는 것 같다. 그래서 [timer invalidate] 호출해주지 않으면 target의 dealloc이 절대 호출되지 않는다. 역시 이 경우에도 메모리 릭이 발생한다. 그러므로  NSTimer를 시작하고 나서 멈추는 시점은 dealloc 내에서 하면 안되며 UIViewController의 경우 viewWillDisappear나 viewDidDisappear와 같은 메서드에서 invalidate를 호출해줘서 정상적으로 UIViewController의 객체의 dealloc 메소드가 호출되도록 해주어야 한다.

이외에도 굉장히 많은 경우도 메모리 릭은 항상 존재할 수 있다. 그래서 보통 일반적으로 통용되는 규칙을 잘알고 그대로 하는 것이 좋고 그렇게 하는 이유도 이해하면 크게 도움된다.

결국 위의 예제는 여기서 제공해주는 일종의 팁정도로만 생각하고 요점은 항상 dealloc 메서드가 호출되는지 확인하는 자세가 필요하다. 그렇게 되면 최소한 메모리 릭이 발생되는 최초 근본원인은 찾아낼 수 있을 것이다. 

종류가 다른 하나의 팁으로 정확한 원인은 모르겠지만 NSZombieEnabled를 사용 하면 EXC_BAD_ACCESS가 발생하지 않는데, 그것만 없애면  EXC_BAD_ACCESS가 발생하는 경우에는 dealloc 부분에서 디버깅을 해보면 어떤 객체에서 문제가 발생하는지 찾을 수 있다. 이 에러는 보통 과도한 release를 하는 경우 발생하는 것인데, 아무리 봐도 문제점을 찾을 수 없는 경우였다. 이런 경우 dealloc 메서드 내에 [super dealloc]를 실행문의 맨 뒤에 호출하도록 하면 문제가 없어지기도 한다.

그림이나 코드없이 말로만 줄줄 써서 이해가 잘 안갈 수도 있다. 결국 중요한 것은 dealloc이 제대로 호출되는지 항상 체크하라는게 여기서 말하고 싶은것이다.

글쓴이 : 지돌스타(http://blog.jidolstar.com/735)
아이폰 개발시작한지 3~4개월 되어가면서 조금 감각을 익히기 시작했습니다. 
어플도 그동안 3개 만들어 올려보고요.  

물론 사운드, 그래픽을 고급스럽게 다뤄보지는 못했지만 기본 UI기반으로 개발할때도 워낙 이슈가 많다보니 학습과 개발을 병행하는게 정말 쉽지 않더군요.... 

각설하고요 ㅎㅎ

여기에 소개할 것은 UIImageView에 원격 이미지를 비동기로 로드할 수 있도록 하는 기능과 이미지 캐쉬 기능을 추가한 소스를 공개하려고 합니다.(소스 분석은 주석을 참고 ^^)

아이폰 어플 개발하면 UIImageView를 매우 많이 사용할 겁니다.  번들이미지의 경우야 어짜피 문제 없지만 원격이미지를 로드할때는  몇가지 이슈가 발생합니다.

1. 원격이미지를 매번 로드하는 것은 네트워크 부하를 일으키며 특히나 3G사용자들에게 치명적이 될 수 있다.
2. 테이블에 원격이미지를 붙이는 경우에 동기적으로 이미지 로드하는 경우 멈춤 현상을 일으킬 수 있다. 
3. 비동기적으로 이미지를 로드하더라도 한번에 로드할 수 있는 이미지를 제어하지 않으면 어플의 전체적인 퍼포먼스가 죽는다.
4. TableView에서 TableViewCell는 캐쉬처리되어 재사용된다. 그러므로 거기에 붙은 UIImageView도 재사용하게 되는데 스크롤을 빨리 넘기는 경우 기존에 로드 요청한 이미지가 하나의 UIImageView에 계속 적재되는 현상이 발생할 수 있다.

위 이슈에서 1, 2는 금방 이해갈 갈겁니다. 3번의 경우 1,2번의 처리가 잘되었다고 해도, 한 화면에 5개 이상의 이미지를 비동기적으로 로드한다는 것은 스레드를 5개 이상 생성해서 처리한다는 의미와 같습니다. 어플 하나에 스레드를 너무 돌리면 별로 좋지 못하기 때문에 동시에 로드할 수 있는 이미지를 제어해주어야 합니다. 1~2개 정도로요. 
4번 이슈의 경우 예전에 문씨님이 올려주신 [문씨의 강좌]멀티스레딩2<NSOperation>에 올린 글에 소개된 소스의 경우에 발생합니다. 저는 여기에 이미지 캐쉬기능만 넣어서 실제로 썼습니다. 하지만 TableView에서 문제가 발생했습니다. 분명 NSOperation을 이용한 이미지 로드는 2,3 문제를 해결해줍니다. 하지만 TableViewCell을 재사용되기 때문에 빠른 스크롤을 하는 경우 거기에 붙은 UIImageView 하나에  지속적으로 다른 이미지가 붙도록 요청이 되어 이미지가 광고롤링되는 현상마냥 보이는 경우가 발생합니다. 

1,2,3,4 번의 이슈를 모두 해결하고자 간단하게 클래스를 제작했습니다.

사용하는 방법은 너무도 간단합니다. 그저 UIImageView를 붙히고 (IB에서든 코드상이든) #import "UIImageView+AsyncAndCache.h"를  넣습니다. 그 다음 아래 UIImageView 카테고리 4개 함수중 하나를 쓰시면 됩니다.  UIImageView *imageView = [[UIImageView alloc]init]; 하신뒤 [imageView setImageURLString:@"이미지 원격 경로"]; 형태로 쓰시면 됩니다. 

UIImageView를 카테고리로 만들었으므로 기존 UIImageView 기능은 그대로 사용할 수 있겠고요.

UIImageView 카테고리 외에 내부적으로 2개의 클래스가 정의되어 있습니다. 이는 개발자가 직접 제어하지 않고 위 4개 이슈를 해결하기 위해 내부적으로 사용되는 클래스입니다. 소스 분석을 원하신다면 주석을 달아두었으니 참고하시면 되겠습니다.

아래는 header만 올려놓습니다. 구현부는 첨부파일을 참고하세요.

uiimageview asyncandcache.zip


ARC환경에서는 아래것을 사용하면됩니다.(오래된 글이라... 개선여지가 많습니다. )

UIImageView AsyncAndCache.zip



//

//  UIImageView+AsyncAndCache.h

//

//  Created by Yongho Ji on 10. 12. 3..

//  Copyright 2010 Wecon Communications. All rights reserved.

//


#import <UIKit/UIKit.h>


@class AsyncAndCacheImageOperator;

@class AsyncAndCacheImageOperatorManager;


//////////////////////////////////////////////////////////////

//

// UIImageView 대한 카테고리 

// 카테고리는 테이블뷰에 적용된 Cell안에 UIImageView에서 활용하면 좋다.

// UIImageView 주요 기능은 다음과 같다

//

// 1. 이미지 비동기 로드 

//   이미지를 비동기로 로드해서 화면에 이미지가 뜨는데 버벅거림을 없앤다.

// 2. 이미지 캐쉬기능 

//   이미 로드한 이미지는 cache 디렉토리에 캐쉬해서 나중에 반복 요청시 

//   로컬에 저장된 캐쉬 이미지를 로드해서 네트워크 부하를 없애준다.

// 3. 반복적인 이미지 요청에 대한 로드부하 최소화 

//    같은 UIImageView 중복으로 로드 요청한다면 마지막에 요청한 이미지가 적용되도록 한다.

//    그뿐 아니라 수십번 반복해서 요청하더라도 무조건 이미지 로드 요청하지 않고 되도록이면 

//   마지막 이미지를 로드요청하여 네트워크 부하를 줄여준다

// 

// 개선해야할 사항 

//

// 1. 캐쉬기능 강제삭제기능 

// 2. 지정된 시간이 지난 캐쉬 이미지 자동 삭제기능 

// 3. 캐쉬사용여부 결정기능  

// 

//////////////////////////////////////////////////////////////


@interface UIImageView (AsyncAndCache)


//String형태의 이미지 URL 초기화

-(id)initWithURLString:(NSString*)url;


//NSURL 형태의 이미지 URL 초기화 

-(id)initWithURL:(NSURL*)url;


//String형태의 이미지 URL 셋팅 

-(void)setImageURLString:(NSString*)url;


//NSURL 형태의 이미지 URL 셋팅 

-(void)setImageURL:(NSURL*)url;


//동시에 로드할 이미지 최대수  

+(void)setMaxAsyncCount:(NSUInteger)count;


@end


//////////////////////////////////////////////////////////////

//

// 한개의 ImageView 대한 오퍼레이터이다.

// 비동기적으로 로드하는 것을 지원하며 더불어 캐쉬기능까지 지닌다.

// 개발자가 클래스를 직접 사용하지 않는다.

// 클래스는 AsyncAndCacheImageOperatorManager 클래스에서 동작/관리한다.

//

//////////////////////////////////////////////////////////////

@interface AsyncAndCacheImageOperator : NSObject

{

NSURL *_url; //로드할 이미지의 URL 정보 

UIImageView *_imageView; //이미지를 적용할 View

BOOL _canceled; //이미지 적용을 막는다. UIImageView 재사용시 나중에 로드되더라도 이게 YES이면 적용하지 못하도록 해서 사용자들로 하여금 잘못된 이미지가 로드되는 것을 방지 한다.

id _loadCompleteTarget; //이미지 로드가 완료되었을때 호출할 target

SEL _loadCompleteSelector; //이미지 로드를 완료했을때 호출할 selector

}


@property (readonly) UIImageView *imageView;


//초기화 함수 

- (id)initWithURL:(NSURL*)url imageView:(UIImageView*)imageView;


//스레드 적용 함수 

- (void)main;


//이미지 적용 취소

//main 메서드가 실행중일때 스레드자체는 중단시킬 없지만 imageView 로드한 image 적용하는 것은 방지시킨다.

- (void)cancel;


//이미지 로드 완료후 호출할 target/selector 적용 

- (void)setLoadCompleteWithTarget:(id)target selector:(SEL)selctor;

@end


//////////////////////////////////////////////////////////////

//

// ImageView정보를 담은 여러개의 오퍼레이터(AsyncAndCacheImageOperator클래스 객체) 관리한다.

// 개발자가 클래스를 직접 사용하지 않는다.

// setMaxAsyncCount 이용해 한번에 로드할 있는 이미지 갯수를 설정할 있다.

// 중요한 것은 동일한 UIImageView 대해서 다른 이미지 로드 요청이 있는 경우 

// 마지막에 요청한 이미지가 붙도록 하며, 같은 UIImageView 이미지 로드 대기중인 경우에는 

// 이전 UIImageView 대한 Operator 삭제함으로써 부적절한 로드로 인한 네트워크 부하를 최소화 해준다.

//

//////////////////////////////////////////////////////////////

@interface AsyncAndCacheImageOperatorManager : NSObject

{

@private

NSUInteger _maxAsyncCount; //동시에 비동기적으로 로드할 이미지 갯수 

NSUInteger _currentAscynCount; //현재 비동기적으로 로드하고 있는 이미지 갯수 

NSMutableArray *_standByImageOperators; //대기중인 Image Operator

NSMutableArray *_loadImageOperators; //로드중인 Image Operator 

}


//한번에 로드할 있는 이미지 갯수(스레드 최대 갯수)

-(void)setMaxAsyncCount:(NSUInteger)count;


//오퍼레이터 추가 

-(void)addImageOperator:(AsyncAndCacheImageOperator*)imageOperator;


@end



제 소스는 많은 테스트는 거치지 못했습니다. 그러므로 여기 개발자 분들께서 필요하시다면 제 소스를 분석하고 수정하면서 개선해주셨으면 합니다.  

----------------------------
수정사항 1
http://cafe.naver.com/mcbugi/95095 에도 이 글을 올렸습니다.
역시 소스가 공개되니 많은 분들이 테스트도 해주시고 좋네요. 만약 이미지 경로가 image.php?ggg=465.jpg로 되어 있다면 ?ggg=465.jpg 부분을 제대로 가져오지 못하는 버그가 있더군요. [_url path]로 되어 있는 부분을 [_url absoluteString]으로 하면 괜찮다고 합니다. 수정해서 쓰세요. 

수정사항 2
초기에 이미지가 붙어 있는데 주어진 이미지 경로에 이미지를 못불러오는 경우 image = nil이 되기 때문에 초기 이미지를 지우는 부분으로 이상하시다는 분도 계셨습니다. 이 부분은 아래처럼 처리하세요.

if (_canceled==NO) 
{
// 이 부분 추가
if (image)
_imageView.image = image;
}

if (_loadCompleteTarget!=nil) 
{
[_loadCompleteTarget performSelectorOnMainThread:_loadCompleteSelector withObject:self waitUntilDone:YES];
}


수정사항 3
이미지가 제대로 로딩이 안되는 버그가 있는데 메인스레드 관련 문제 때문입니다. 아래처럼 수정하세요.

if (_canceled==NO) {

    dispatch_sync( dispatch_get_main_queue(), ^{

        _imageView.image = image;

        [_imageView performSelector:@selector(layoutIfNeeded) withObject:nil];

    });

}


글쓴이 : 지돌스타(http://blog.jidolstar.com/723)


#ifdef ~ #else ~ #endif 
#define 

위 구문은 C, C++ 계열을 경험한 분들이라면 익숙한 내용일겁니다.
Xcode상에서 이것을 적절하게 활용하는 방법을 간단히 소개합니다.

커스텀 로그출력 

#ifdef USE_LOG

#define NSLog( s, ... ) NSLog(@"%s(%d) %@", __FUNCTION__, __LINE__, [NSString stringWithFormat:(s), ##__VA_ARGS__])

#else

#define NSLog( s, ... )

#endif


위 코드처럼 사용한다면 USE_LOG가 정의되어 있으면 그 안에 있는 #ifdef~ #else 안에 정의된 #define 매크로가 사용되어집니다.  
즉 NSLog()사용시 커스터마이징된 NSLOG()로 출력해줍니다. USE_LOG가 정의되어 있지 않다면 #else ~ #endif내의 #define 매크로가 사용되어 지며  NSLog()를 사용한 모든 코드는 전부 무의미하게 만듭니다. 

이런 형태로 정의한 이유는 개발할때는 분명 상세한 로그가 필요하지만 배포시에는 필요없기 때문입니다. 개발시에 따로 커스터마이징된 NSLog를 사용하는 이유는 디버깅시에 좀더 상세한 로깅이 필요하고 NSLog()를 코딩하느라 힘빼지 않고도 단순히 원하는 내용만 삽입해도 어느 함수에서 실행된 건지, 몇번째 라인인지 등의 내용도 함께 출력해줄 수 있습니다. 

위 코드는 *.pch 에 넣어두시면 작성된 모든 코드에 적용됩니다. 꽤 유용하겠죠?


로컬,원격 접속 제어

#ifdef LOCAL 

#define _DEFAULT_URL @"http://localhost:8080"

#else

#define _DEFAULT_URL @"http://gateway.jidolstar.com"

#endif


위 방식의 경우 배포/개발에 상관없이 로컬테스트, 원격테스트를 각각 진행하고 싶을때 유용합니다. 가령, 일반 Debug시에는 로컬에서 테스트 해야하므로 LOCAL이 정의되어 있어야 합니다. 하지만 앱스토어에 올리는 경우라면 정의될 필요가 없는겁니다. Debug시더라도 Debug Local, Debug Remote로 나누어 관리할 필요도 있고 또는 배포라도 테스트 배포를 위해 Adhoc을 사용하는 경우는 Local, Remote로 나눠서 배포할 필요도 있을겁니다. 이럴때 이 방법은 유용합니다. 이 코드 또한 *.pch에 넣어두시면 됩니다.


사용방법
위 2가지 코드의 의미와 활용에 대해서는 언급했습니다. 하지만 구체적으로 어떻게 XCode상에 설정해야 저 코드를 사용할 수 있는지  설명하지 않았습니다.  

초반 프로젝트에 대한 Info에서 Build에는 Debug와 Release 설정만 있을겁니다.  Adhoc및 AppStore로 배포하기 위해서 이 설정을 두개 더 늘려 Debug, Release, Distribution Adhoc, Distribution AppStore 를 만들겁니다. 사실 위에서 언급한 내용이 없다면 이 정도로만 가져가셔도 테스트, 배포는 문제없습니다만...  로컬/원격 테스트를 빈번하게 진행해야한다던가 하는 경우라면 이야기가 좀 달라집니다. 위 코드에 필요한 #define을 매번 주석을 처리했다 풀었다 하는 것도 꽤 귀찮거든요. 

그래서 저는 Debug 를 하나더 카피해서 Debug Remote를 만들고 기존 Debug는 Local로 만듭니다. 
Distribution Adhoc도 Distribution Adhoc Local, Distribution Adhoc Remote 로 만듭니다. 하지만 Distribution AppStore는 로컬테스트는 진행하지 않으므로 그대로 둡니다.

그런다음 (맥 사파리에서는 네이버카페 글편집기가 이미지를 첨부할 수 없네요.)
Debug Local 설정을 선택후  Preprocess Macro 를 찾아 USE_LOG=1 LOCAL=1 을 입력합니다. 
Debug Remote 설정에는 Preprocess Macro에 USE_LOG 만 넣습니다.
Distribution Adhoc Local 설정에는 LOCAL=1 을 넣습니다. 
Distribution Adhoc Remote 와 Distribution AppStore 설정에는 아무것도 할필요가 없습니다.

이렇게 한다음 원하는 설정을 이용해 컴파일하는 것만으로 로그를 출력할지 말지, 로컬접속인지 원격접속인지 구분할 수 있게 됩니다. 

알고보면 진짜 별거 아닌데.... 이런 팁 하나하나가 생산성을 극대화 시키기 때문에 소개합니다.
좋은 팁 있으면 많이들 알려주세요. ^^

글쓴이 : 지돌스타(http://blog.jidolstar.com/722)
개인적으로 Flex, ActionScript 3.0 기반에서 개발을 오래도록 해와서 그런지 아이폰의 통지(Notification) 사용법이 꽤 낫설더군요.
그래서 ActionScript 3.0에서 사용되는 Event기반과 비슷하게 한번 만들어 봤습니다.

Event와 Notification은 비슷합니다.  하지만 사용하는 인터페이스가 불편함이 느껴졌습니다.

간단히 개념 설명이 들어가면... 
제가 만든 Event는 다음과 같은 용어를 씁니다.
1. 이벤트 송출자 ( Event Dispatcher)
2. 이벤트 (Event)
3. 이벤트 청취자 (Event Listener) 

이벤트는 데이터입니다. 이벤트가 발생(송출)할때 이벤트명,이벤트송출자,이벤트데이타를 담는 역할을 합니다. 이벤트는 이벤트 송출자가 만들고 이벤트 청취자가 받습니다.

이벤트 송출자는 이벤트를 만들어 보내는 역할을 합니다. 보낼때 이벤트를 송출하는 객체, 이벤트명, 그리고.. NSObject를 확장한 데이터를 담은 객체를 하나로 묶은 이벤트를 만들어주고 등록된 이벤트 청취자중 같은 이벤트명으로 등록된 청취자에게만 이벤트를 전달합니다. 

이벤트 청취자는 송출된 이벤트를 받습니다. 이벤트 청취자는 target/selector 를 지칭하며 청취자 등록시 함께 등록하는 이벤트명에 따라 실행됩니다. 즉, 이벤트 송출시 보내지는 이벤트명과 등록된 청취자의 이벤트 명이 같아야합니다. 반대로 이벤트 명이 다르면 절대 호출되지 않습니다. 


다음은 만들어진 소스의 header 코드입니다. 

//

//  Event.h

//

//  Created by Yongho Ji on 10. 10. 25..

//  Copyright 2010 Wecon Communications. All rights reserved.

//

// 이벤트를 다룬다. 내부적으로 Notification 기능을 한번 감싼형태이다

// 이렇게 만드는 이유는 호스트코드에서 쉽게 사용할 있게 하기 위함이다.

//


#import <Foundation/Foundation.h>


//=================================================

//

// 이벤트 

// 1. 내부 생성클래스이다

// 2. 개발자가 직접 생성하지 않는다.

// 3. 개발자는 등록된 이벤트 청취자로부터 이벤트 객체를 받게 되어 있다

//

//=================================================



@interface Event: NSObject

{

@private

id _dispatcher; // 이벤트 송출자

id _data; // 이벤트 데이타 

NSString *_eventName; // 이벤트  

}


@property (nonatomic, readonly) id dispatcher;

@property (nonatomic, readonly) id data;

@property (nonatomic, readonly) NSString *eventName;


@end



//=================================================

//

// 이벤트 청취자 

// 1. 내부 클래스이며 외부 노출되지 않는다.

// 2. 이벤트 청취 목록을 관리하기 위한 클래스이다

//

//=================================================

@interface EventListener : NSObject

{

@private

id _listener; //이벤트 청취자 

SEL _selector; //이벤트 청취자의 selector

NSString *_eventName; //이벤트 이름 

}


@property (readonly) id listener;

@property (readonly) SEL selector;

@property (readonly) NSString* eventName;


-(id)initWithListener:(id)listener selector:(SEL)selector eventName:(NSString*)eventName;

@end


//=================================================

//

// 이벤트 센터

// 1. 싱글톤 클래스이다.

// 2. [EventCenter defaultCenter] 접근해서 사용한다.

// 3. 이벤트 청취자 등록은 [[EventCenter defaultCenter]add:self selector:@selector(mySelector:) eventName:@"MY_EVENT"] 형태로 한다.

// 4. 이벤트 청취자 삭제는 3가지가 있다

//   - 1개의 이벤트 명에 대한 청취자 삭제 [[EventCenter defaultCenter] remove:self selector:@selector(mySelector:) eventName:@"MY_EVENT"] 형태로 한다.

// - 여러개의 이벤트 명에 대한 청취자 삭제 [[EventCenter defaultCenter] remove:self selector:@selector(mySelector:)] 형태로 한다.

// - 모든 이벤트 청취자 삭제 [[EventCenter defaultCenter] remove:self] 형태로 한다.

// 5. 이벤트 발송은 2가지 방법이 있다.

// - 데이터가 미포함된 이벤트 : [[EventCenter defaultCenter] dispatch:self eventName:@"MY_EVENT"] 

//   - 데이터가 포함된 이벤트 : [[EventCenter defaultCenter] dispatch:self eventName:@"MY_EVENT" data:myData]

//   - 데이터는 NSObject 확장한 것이라면 어떤 것이든 보낼 있다

//   - 이벤트가 발송되면 해당 이벤트 이름으로 등록된 모든 이벤트 청취자를 실행한다.

// 6. 이벤트 청취자는 listener selector 한묶음으로 본다. selector -(void)mySelector:(Event*)event; 또는 -(void)mySelector; 형태로 만들면 되겠다

//   , 인자로 Event객체가 오면 속성값에 dispatcher(이벤트를 발생한 ), eventName(이벤트명), data(이벤트시 보낸 데이타) 참조할 있다.

// 7. UIViewController기반에서 이벤트 등록과 삭제는 반드시 각각 viewWillAppear, viewWillDisappear에서 하도록 한다. 왜냐하면 등록된 이벤트 청취자 관리를 못하면 중복 호출이 있다.

//=================================================

@interface EventCenter : NSObject {

NSMutableArray *eventListeners;

}


//이벤트 기본 Center. 싱글톤 처리 

+(EventCenter*)defaultCenter;

//이벤트 청취자 등록 (중복된 등록은 무시됨)

-(void)add:(id)listener selector:(SEL)selector eventName:(NSString*)eventName;

//이벤트 청취자 삭제. selector, eventName 상관없이 같은 listener 등록되어 있으면 모두 삭제. 이미 삭제되어 있다면 무시

-(void)remove:(id)listener;

//이벤트 청취자 삭제. eventName 상관없이 같은 listener, selector 등록되어 있으면 모두 삭제. 이미 삭제되어 있다면 무시

-(void)remove:(id)listener selector:(SEL)selector;

//이벤트 청취자 삭제. listner, selector, eventName 모두 같아야 삭제된다. 이미 삭제되어 있다면 무시.

-(void)remove:(id)listener selector:(SEL)selector eventName:(NSString*)eventName;

//이벤트 송출. 데이터 미포함 

-(void)dispatch:(id)dispatcher eventName:(NSString*)eventName;

//이벤트 송출. 데이터 포함

-(void)dispatch:(id)dispatcher eventName:(NSString*)eventName data:(id)data;

@end


총 3개의 클래스로 구성되어 있으나 이벤트 송출자 등록, 이벤트 청취자 등록, 이벤트 송출은 모두 EventCenter 클래스가 관장하며, EventCenter는 싱글톤 처리되어 [[Event defaultCenter] ...] 형태로 접근이 가능합니다.

내부적으로 NSNotification을 한번 감싸서 통지를 이벤트로 추상화처리 한 것입니다. 

아마도 Objective-C의 NSNotification 또는 ActionScript의 Event를 사용해보신 경험이 있는 분은 별다른 설명없이도 이 소스를 그냥 가져다가 쓸 수 있습니다.

사용법은 주석으로 간단하게 적어두었습니다. 주의사항도 꼭 확인하시고 사용하시면 됩니다.

개선사항이나 버그가 있다면 댓글 부탁드리겠습니다. ^^

소스는 첨부합니다.


----------------------------------------------
사용법을 문의하시는 분들이 계시네요.  소스도 공개했구 주석처리 다했는데 ^^;;
기본적으로 Notification을 이해하시면 Event도 이해할 수 있습니다. 
그래도 사용하시는데 어려움을 겪으시는 것 같아 간단하게 예제를 들어보지요.

1. 이벤트명 정의 
#define EVENT_WRITE @"eventWrite"

2. 이벤트 발생 
DispatchClass 안에서 아래처럼 코딩합니다.

[[EventCenter defaultCenter]dispatch:self eventName:EVENT_WRITE data:postData];

3. 이벤트 청취 메소드 정의 
ListnerClass 내부에서 아래처럼 정의합니다.

-(void)_myMethod:(Event*)event
{
 PostData *postData = (PostData*)event.data;
 NSLog(@"eventName=%@,  dispatcher=%@",event.eventName, event.dispatcher);
}

4. 이벤트 청취자 등록 
ListenerClass 내부에서 아래처럼 정의합니다. 단 정의시에는 viewDidLoad가 아닌 viewWillAppear나 viewDidAppear등에서 하시는게 메모리 관리가 가능합니다.
[[EventCenter defaultCenter]add:self selector:@selector(_myMethod:) eventName:EVENT_WRITE];

5. 이벤트 청취자 삭제 
ListenerClass 내부에서 아래처럼 정의합니다. 단! dealloc에서는 하지 마세요. 왜냐하면 등록한 listener 내부적으로 retain되기 때문입니다. viewWillAppear등에 add 하셨다면 viewWillDisappear에서 하시면 됩니다. add로 등록하지 않아도 삭제한다고 문제되지 않으니 메모리 관리를 위해서 꼭!!! remove는 호출하셔야 합니다.
[[EventCenter defaultCenter]remove:self selector:@selector(_myMethod) eventName:EVENT_WRITE];

삭제방법은 총 3가지죠. 위 코드는 이벤트에 대한 등록한 청취자 1개만 삭제할때 씁니다. 
하지만 ViewController가 더이상 쓸모가 없어진다면 
[[EventCenter defaultCenter]remove:self] 만 하셔도 self에 정의된 모든 이벤트 청취자를 삭제해줍니다. 



이상입니다. 더 개선할 사항에 대한 아이디어가 있다면 언제든지 답글주세요. ^^

글쓴이 : 지돌스타(http://blog.jidolstar.com/721)

+ Recent posts