외규장각 도서 환수 모금 캠페인
BLOG main image
분류 전체보기 (45)
컴퓨팅환경 (18)
프로그래밍 (18)
놀이 (2)
잡담 (7)
«   2009/03   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        
10,203 Visitors up to today!
Today 4 hit, Yesterday 28 hit
daisy rss
meet me at me2DAY
나눔글꼴 내려받기
tistory
'2009/03'에 해당되는 글 2건
2009/03/13 17:24

NSFileHandle이나 NSURLConnection은 Async I/O를 위해 내부적으로는 쓰레드를 사용하지만, I/O 이벤트가 발생했을 때 delegate를 호출해주는 편리한 인터페이스를 제공하고 있다. 다만, delegate 객체는 진행중인 I/O에 대한 상태관리(?)를 해야한다. 상태관리라니 뭔가 거창하게 해야하는 건 아니고, 현재까지 수행해야할 또는 완료된 I/O 데이터를 가지고 있어야 한다든지, 오류가 발생했을 때 대처해야 한다던지... 뭐, 그런 것들...

문제는 이러한 I/O가 동시 다발적 또는 산발적으로 마구 발생하는 경우는 일일이 다루기가 귀찮아질 수 있다. 그래서, NSOperationQueue를 이용해 보기로 했다. 다음 예제는 NSOperation의 서브클래스로, NSURLConnnection을 이용하여 서버에서 데이터를 다운받아 넘기는 간단한 것이다.

@interface URLDownload : NSOperation
{
    NSURLConnection *connection;
    NSMutableData   *data;

    BOOL             executing;
    BOOL             finished;

    id               delegate;
    SEL              selector;
}

/*
 * delegate method의 signature는 다음과 같다.
 *  - 리턴타입:    void
 *  - 첫번째 인자: download operation 객체 (URLDownload *)
 *  - 두번째 인자: 다운받은 데이터 객체 (NSData *)
 *  - 세번째 인자: 에러 객체 (NSError *)
 */
- (id)initWithRequest:(NSURLRequest *)aRequest delegate:(id)aDelegate selector:(SEL)aSelector;

@end


@implementation URLDownload

- (id)initWithRequest:(NSURLRequest *)aRequest delegate:(id)aDelegate selector:(SEL)aSelector
{
    self = [super init];

    if (self)
    {
        connection = [[NSURLConnection alloc] initWithRequest:aRequest delegate:self startImmediately:NO];
        delegate   = aDelegate;
        selector   = aSelector;
    }

    return self;
}

- (void)dealloc
{
    [connection release];
    [data release];
    [super dealloc];
}


#pragma mark overrides for concurrent operation

/*
 * concurrent operation을 위해서는 다음의 메소드를 오버라이드해야 한다.
 *  - isConcurrent
 *  - isExecuting
 *  - isFinished
 *  - start
 */

- (BOOL)isConcurrent
{
    return YES;
}

- (BOOL)isExecuting
{
    return executing;
}

- (BOOL)isFinished
{
    return finished;
}

- (void)start
{
    if ([self isCancelled])
    {
        [self willChangeValueForKey:@"isFinished"];
        finished = YES;
        [self didChangeValueForKey:@"isFinished"];
    }
    else
    {
        [self willChangeValueForKey:@"isExecuting"];
        executing = YES;
        [self didChangeValueForKey:@"isExecuting"];

        [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [connection start];
    }
}


#pragma mark others

/*
 * cancel 메소드를 오버라이드하여 NSURLConnection의 수행을 cancel시키고 operation을 종료한다.
 */
- (void)cancel
{
    [super cancel];
    [connection cancel];

    if ([self isExecuting])
    {
        /*
         * NSOperationQueue의 cancelAllOperations에 의해 cancel될 때
         * [self stop] 을 하면 아직 cancel되지 않은 operation들이 수행을 시작할 수 있기 때문에
         * performSelector:withObject:afterDelay: 메소드를 이용하여 stop시킨다.
         */
        [self performSelector:@selector(stop) withObject:nil afterDelay:0];
    }
}

/*
 * executing과 finished property를 종료 상태로 바꿔주기 위한 편의성 메소드
 */
- (void)stop
{
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];
    executing = NO;
    finished  = YES;
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}


#pragma mark NSURLConnectionDelegate

/*
 * 응답이 들어오면 데이터를 저장하기 위한 공간을 생성한다.
 */
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    long long length = [response expectedContentLength];

    if (length == NSURLResponseUnknownLength)
    {
        length = 0;
    }

    data = [[NSMutableData alloc] initWithCapacity:length];
}

/*
 * 데이터가 들어오면 저장해 놓는다.
 */
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)receivedData
{
    [data appendData:receivedData];
}

/*
 * 오류가 발생하면 nil data와 함께 오류를 delegate에 전달하고 operation을 종료한다.
 */
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSData       *nilData;
    NSInvocation *invocation;

    if (![self isCancelled])
    {
        nilData    = nil;
        invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:"v@:@@@"]];

        [invocation setTarget:delegate];
        [invocation setSelector:selector];
        [invocation setArgument:&self atIndex:2];
        [invocation setArgument:&nilData atIndex:3];
        [invocation setArgument:&error atIndex:4];

        [invocation invoke];
    }

    [self stop];
}

/*
 * 데이터 로딩이 끝나면 데이터를 delegate에 전달하고 operation을 종료한다.
 */
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSError      *nilError;
    NSInvocation *invocation;

    if (![self isCancelled])
    {
        nilError   = nil;
        invocation = [NSInvocation invocationWithMethodSignature:[NSMethodSignature signatureWithObjCTypes:"v@:@@@"]];

        [invocation setTarget:delegate];
        [invocation setSelector:selector];
        [invocation setArgument:&self atIndex:2];
        [invocation setArgument:&data atIndex:3];
        [invocation setArgument:&nilError atIndex:4];

        [invocation invoke];
    }

    [self stop];
}

@end
사용은 다음과 같이 한다.
- (NSOperationQueue *)operationQueue
{
    return operationQueue; // NSOperationQueue를 적절하게 미리 만들어 두어야 한다.
}

- (void)downloadDataWithURL:(NSURL *)aURL
{
    URLDownload *download;

    download = [[URLDownload alloc] initWithRequest:[NSURLRequent requestWithURL:aURL] delegate:self selector:@selector(urlDownload:data:error:)];
    [[self operationQueue] addOperation:download];
    [download release];
}

/*
 * 다운로드가 끝나면 이 메소드가 호출된다.
 * 오류가 발생했을 때 data는 nil이 들어온다.
 */
- (void)urlDownload:(URLDownload *)aDownload data:(NSData *)data error:(NSError *)error
{
    if (data)
    {
        // 다운로드 완료
    }
    else
    {
        NSLog(@"download error: %@", error);
    }
}

 

이 글은 스프링노트에서 작성되었습니다.

2009/03/13 15:55

NULL이라함은 보통 "값이 없음"을 말한다.

C언어에서 NULL은 사실 그냥 0으로 정의되어 있는데, 포인터변수에 NULL을 사용하여 레퍼런스하는 대상이 없다는 의미로 사용한다. Objective-C에서는 객체 포인터 타입인 id에서 사용하기 위해 따로 nil을 정의하고 있다. nil도 사실 0으로 정의되어 있기 때문에 이넘이나 그넘이나 같은 넘이다.

Objective-C가 C나 C++과 다른 점 중에 하나는 이 nil이 단순히 레퍼런스하는 객체가 없다는 의미 외에도 "nil object"라고까지 표현할만큼 특별한 기능(?)을 발휘하고 있다는 점이다. 바로 어떠한 메세지도 받아들이고(respond) 결과로 자기 자신을 리턴한다는 것이다.(자세한 내용은 여기참조) 즉, 다음과 같은 코드도 런타임에서 어떠한 에러도 발생하지 않고 잘 실행된다. 물론 아무것도 하지 않지만.

[nil killYourself];

Java나 Smalltalk도 예외를 발생시키지만, Objective-C는 그렇지 않다. 이런 특별한 기능(?)이 복잡한 예외처리를 필요로 하는 경우나 디버깅할 때 귀찮을 경우도 있지만, 코드를 단순화시키기 쉽고 잘 죽지 않는(허허.. 이거 좋은 건지...) 프로그램을 만들 수 있다... (응?)

그런데....

NSNull 이라는 것도 있다. 코코아의 컬렉션 객체들이 nil 즉, NULL 포인터를 저장할 수 없기 때문에 존재하는, nil은 아닌 Null 객체다. (헉.. 이건, 뭐...) 문제는 NSNull 객체는 nil이 가지고 있는 특별한 기능(?)이 없다는 것이다. 즉, 다음과 같은 코드는 시키는 대로 아주 잘 죽는다... 쩝.

[[NSNull null] killYourself];

그래서, nil과 NSNull 객체가 둘 다 들어올 수 있는 코드에서는 객체 포인터를 미리 잘 확인해봐야 한다. 다음과 같이...

if (anObj && ![anObj isKindOfClass:[NSNull class]])
{
    // 이제 anObj를 마음껏 사용한다.
}

매번 if문을 저렇게 쓰는 것이 싫다면 nil이나 NSNull 객체가 아닌지 확인하는 메소드를 다음과 같이 구현할 수 있겠다.

@interface NSObject (NullTesting)

- (BOOL)isNotNull;

@end

@implementation NSObject (NullTesting)

- (BOOL)isNotNull
{
    return [self isKindOfClass:[NSNull class]] ? NO : YES;
}

@end
또는, 이렇게.
@interface NSObject (NullTesting)

- (BOOL)isNotNull;

@end

@implementation NSObject (NullTesting)

- (BOOL)isNotNull
{
    return YES;
}

@end

@implementation NSNull (NullTesting)

- (BOOL)isNotNull
{
    return NO;
}

@end

그러면, 다음과 같이 if 문의 조건을 쉽게 쓸 수 있다.

if ([anObj isNotNull])
{
    // 이제 anObj를 마음껏 사용한다.
}

주의할 점은 isNull 메소드는 불가능하다. 왜냐하면 nil은 항상 nil(0)만 리턴하기 때문이다.

if문조차 싫다면 Objective-C의 message forwarding을 이용해서 NSNull이 nil 처럼 모든 메세지를 무시하도록 만들 수 있겠다.

@implementation NSNull (NilObject)

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    return [NSMethodSignature signatureWithObjCTypes:"@@:"];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    id ret = nil;
    [anInvocation setReturnValue:&ret];
}

@end

이제는 좀 마음이 편해질려나...

 

이 글은 스프링노트에서 작성되었습니다.

prev"" #1 next