먼저 기본적인 GCD의 기능에 대해서 알아보자.


GCD는 태스크를 저장하는 큐(디스패치큐)를 이용하여 동작하며 3종류의 큐가 정의 되어 있다.

첫번째 글에도 잠깐 언급하였지만 간단히 다시 한번 설명하자면,


직렬큐:태스크를 추가한순으로 실행되는 큐

dispatch_queue_create("jp.hateblo.shin.LABEL_NAME", NULL)


병렬큐:추가된태스크를 병렬로 실행하는 큐 (4개의 글로벌 병렬큐가 이미 준비되어있다.)

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)


메인큐:추가된태스크가 메인스레드에서 실행되는큐. ( UI작업은 이큐에서 동작해야함.)

큐의 취득은 dispatch_get_main_queue()


각각의 큐는 코드도 서로 다르지만 사용하는방법은 같다.


태스크에 등록할 때에는dispatch_async 나 dispatch_sync의 두가지 방법이 있다. 태스크의 등록할 시점에 동기로 실행할지 비동기일지를 지정한다.


직렬큐라면..

#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 1);

        });

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 2);

        });

        // 여기서 queue 쌓인 태스크가 전부 끝날때까지 메인스레드는 대기한다.

        dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:1];

            NSLog(@"Done: %d", 3);

        });

    }

    return 0;

}


실행 결과출력.

2015-03-17 16:05:50.864 TestApp3[5693:1456773] Done: 3

2015-03-17 16:05:50.864 TestApp3[5693:1457020] Done: 1

2015-03-17 16:05:50.864 TestApp3[5693:1457019] Done: 2




병렬큐를 구현한다면..

#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 1);

        });

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 2);

        });

        dispatch_sync(queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"@Done: %d", 3);

        });

        // 여기서 queue 병렬처리 하고 샆다면..

    }

    return 0;

}


dispatch_sync를 사용함으로써 큐에 쌓인 태스크가 종료될때까지 대기하므로 메인스레드에서 별도의 처리를 할 수 없게된다.

이런경우에는 디스패치 그룹을 사용하면 된다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        dispatch_group_t group = dispatch_group_create();

        

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 1);

        });

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 2);

        });

        dispatch_group_async(group, queue, ^{

            [NSThread sleepForTimeInterval:3];

            NSLog(@"Done: %d", 3);

        });

        

        // 이지점에서 쌓인큐를 종료됨과 동시에 호출된다.

        // dispatch_group_wait 포함 되지 않는다.

        dispatch_group_notify(group, queue, ^{

            NSLog(@"All tasks are done!");

        });

        

        NSLog(@"do something...");

        [NSThread sleepForTimeInterval:1];

        NSLog(@"waiting...");

        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

        // 여기서 약간 기다려주지 안흥면 notify처리전에 프로그램의 끝나버리므로..약간 딜레이를

        [NSThread sleepForTimeInterval:1];

    }

    return 0;

}



2015-03-17 16:06:22.744 TestApp3[5717:1461092] do something...

2015-03-17 16:06:23.749 TestApp3[5717:1461092] waiting...

2015-03-17 16:06:25.744 TestApp3[5717:1461228] Done: 3

2015-03-17 16:06:25.744 TestApp3[5717:1461229] Done: 2

2015-03-17 16:06:25.744 TestApp3[5717:1461232] Done: 1

2015-03-17 16:06:25.744 TestApp3[5717:1461232] All tasks are done!





dispatch_group_async를 태스크에 등록을 하면 필요할 경우에 dispatch_group_wait로 큐에 쌓인 태스크가 종료되는것을 기다릴 수 있다.

dispatch_group_notify은 옵션이지만 종료처리에 이용할 수 있다.


디스패치와 세마포어


GCD는 세마포어를 이용할 수도 있다. (세마포어란 전글에도 설명했듯이 배타제어를 위한 방법중 하나이다.)

물론 직렬큐를 잘만 이용한다면 크리티컬섹션을 이용한 세마포어가 필요없겠지만 어쨋든 직력큐와 같은 효과를 낼 수 있다.


#import <Foundation/Foundation.h>


int main(int argc, char* argv[])

{

    @autoreleasepool {

        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); //열쇠갯수

        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

        

        dispatch_async(queue, ^{

            [NSThread sleepForTimeInterval:2]; // do something

            NSLog(@"Finish!");

            dispatch_semaphore_signal(semaphore); //열쇠갯수 늘림

        });

        

        [NSThread sleepForTimeInterval:1]; // do something

        NSLog(@"waiting...");

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); //열쇠반환될때 까지 기다림

        

    }

    return 0;

}





2015-03-17 16:06:59.677 TestApp3[5746:1465642] waiting...

2015-03-17 16:07:00.677 TestApp3[5746:1465821] Finish!



GCD에 대해선 언급한 것 이외에도 너무나도 많은 기능들이 있으니 애플문서를 참고하면 될듯하다. 











Posted by 악당잰 트랙백 0 : 댓글 4

댓글을 달아 주세요

  1. addr | edit/del | reply BlogIcon 魔術士マジク 2015.03.20 23:47

    검은건 글씨요 흰건 바탕페이지라....ㅠㅠ

    • addr | edit/del 악당잰 2015.03.21 12:56

      에이 ~ 다 알면서~

  2. addr | edit/del | reply 김기봉 2015.03.24 14:46

    병렬큐에서
    dispatch_async(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"@Done: %d", 2);
    });
    dispatch_sync(queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"@Done: %d", 3);
    });

    에서 dispatch_sync랑 dispatch_async를 다르게 준건 무슨 의도로 주신거예요?

  3. addr | edit/del | reply BlogIcon 악당잰 2015.03.24 17:30 신고

    마지막에 싱크를 안해주면 메소드가 끝나버리니까 확인이 안되잖어. 그래서 동기로 3초준거지

iOS5부터인가 나왔던 기능으로 알고 있는데 block코딩과 더불어 생소한 문법 중 한놈이다.

굳이 써야되는 필요성을 느끼지 못한놈인지라 넘겨두고 있었는데 최근 비동기처리가 필요한 플젝을 하면서

어쩔수 없이 조금 공부하게 되었다.

정말 간단하게 구현되고 편리하므로 아마도 자주 써줘야겠다.

어쨌든 내가 아는한도내에서 두가지 정도 메모해 둔다.


첫번째 제일 자주 쓰는 구문. 이건 싱글톤 처리할때 쓰는 패턴이다. 다른방법도 있지만(인스턴스가 nil인지 아닌지 비교해서 분기해주는...)

어쩐지 이게 더 멋있어 보인다. 

쓰는 방법은 dispatch_once_t 라는 변수를 선언해주고 dispatch_once라는 메소드에 초기화 처리를 해주면 메소드 이름처럼 

딱 한번만 실행한다. 그러니 결과적으로 싱글톤이 됨.


+ (AVAudioPlayerManager*)sharedAVAudioPlayerManager {

    static dispatch_once_t pred = 0;

    __strong static id _vrObject = nil;

    dispatch_once(&pred, ^{

        _vrObject = [[self alloc] init];

    });

    return _vrObject;

}


두번째로 비동기를 위해 스레드 처리할 경우. 예를들어 메인스레드에서 시간이 걸리는 처리를 해버리면 

당연히 화면이 멈추는 현상이 나타난다. 왜냐면 화면처리는 메인스레드에서 처리되므로.. 

바꾸어 말하면 시간 걸리는건 메인스레드에서 처리하지 않는것이다. 다만 화면상태를 바꾸고 싶을땐 메인스레드에서 처리

해야 하므로 그 부분만 메인스레드로 넘기는 것이 포인트.


1.일반큐와 메인큐를 각각 선언한다. 

2.스레드를 사용하기 위해 dispatch_async 라는 메소드를 선언하고 일반큐를 파라메터로 넣는다.

3. 다음 처리로직안에 화면제어를 할 필요가 있는 처리에 대해선 메인큐(메인스레드)로 넘겨준다.


정말 간단하다. 백문이불여일견! 직접 소스를 보자.


dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    dispatch_async(globalQueue, ^{

    

        //시간이 걸리는 처리를 한다고 치자.

        int count = 0, i;

        for(i = 0; i < 10000; i++){

            NSLog(@"count = %d", count);

            count++;

        }

        

        //진행상태를 화면에 표시해주기 위해 메인스레드에 화면변경로직을 코딩.

        dispatch_async(mainQueue, ^{

            [self updateProcress:[self logStringWithCount:count]];

        });

        

        //또 시간이 걸리는 처리가 있다고 한다면...

        for(i = 0; i < 10000; i++){

            NSLog(@"count = %d", count);

            count++;

        }


        //끝났을 경우의 처리를 해준다. 여기서도 메인스레드로 처리해줌.

        dispatch_async(mainQueue, ^{

            [self updateProcress:[self logStringWithCount:count]];

            [_indicatorView stopAnimating];

            _startButton.hidden = NO;

        });

    });


Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요