도래울

[OS] GCD(Grand Central Dispatch) 사용하기. 본문

개발/iOS

[OS] GCD(Grand Central Dispatch) 사용하기.

도래울 2016. 11. 1. 15:39

GCD(Grand Central Dispatch)란, C언어로 되어 있는 스레드 관리 기술로 iOS4 부터 지원하고 있다. 또한, GCD와 같은 시점에 등장한 블럭 코딩 기반으로 기존에 사용하던 NSThread, NSOperation 보다 쉽게 스레드 응용 기술을 구현할 수 있도록 지원해준다.

  이 포스트는 Block 코딩, NSOperation, 멀티스레딩에 대해 이해가 있다는 가정하에 정리용으로 쓰였다. 또한, 모든 샘플 코드는 ARC 환경 및 Xcode5 환경에서 테스트 되었다.



1. 디스패치 큐(Dispatch Queues).

  실행할 작업을 저장하는 큐로 시리얼 디스패치 큐와 콘커런트 디스패치 큐로 나눌 수 있다.


- 시리일 디스패치 큐(Serial Dispatch Queue).

  큐에 추가된 순서 안에서 선입선출(FIFO) 방식으로 작업을 실행한다. 또한, 큐에 있는 작업 하나를 실행시킨 후에 실행이 끝날 때까지 큐에 있는 다른 작업들은 기다리고 있다. 즉, 스레드 하나로 순차 처리를 하고 있다.


- 콘커런트 디스패치 큐(Concurrent Dispatch Queue).

  시리얼 디스패치 큐와는 다르게 실행 중인 작업을 기다리지 않고 큐에 있는 다른 작업들을 동시에 별도의 스레드에 수행시킨다. 동시에 실행되는 작업 수는 시스템 상태에 따라 달라지며, 이것을 판단 및 관리는 XNU 커널이 알아서 해준다.



2. 디스패치 큐 얻기.

 디스패치 큐를 얻는 방법으로는 새로운 큐를 만드는 dispatch_queue_create 함수를 통하거나, 메인 스레드에 대한 디스패치 큐를 얻을 수 있는 dispatch_get_main_queue 매크로의 사용 또는 글로벌 디스패치 큐를 얻을 수 있는 dispatch_get_global_queue 함수를 통 얻을 수 있다.


:= dispatch_queue_create, dispatch_get_main_queue, dispatch_get_global_queue

// 새로운 디스패치 큐를 만든다

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);


// 메인 디스패치 큐를 얻는다

#define dispatch_get_main_queue() DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q)


// 글로벌 디스패치 큐를 얻는다

dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority, unsigned long flags);


   여기서 메인 디스패치 큐와 글로벌 디스패치 큐는 사용자가 만든 것이 아니라 시스템이 미리 만든 큐이다.

  메인 디스패치 큐는 작업을 수행하기 위해 메인 스레드를 이용하며, 시리얼 디스패치 큐에 속한다. 그리고 글로벌 디스패치 큐는 콘커런트 디스패치 큐로 XNU 커널에 의하여 새로운 스레드에 작업을 수행시킨다.

  아래는 예제이다.


// 예제 1


// 새로운 시리얼 디스패치 큐를 생성한다

dispatch_queue_t serialQueue = dispatch_queue_create("com.davin.serialqueue"DISPATCH_QUEUE_SERIAL);

    

// 새로운 콘커런트 디스패치 큐를 생성한다

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.davin.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);

    

// 메인 디스패치 큐를 얻어온다

dispatch_queue_t mainQueue = dispatch_get_main_queue();

    

// 글로벌 디스패치 큐를 얻어온다

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT0);


  dispatch_queue_create의 첫 번째 인자는 큐에서 작업을 실행할 때 생성되는 스레드의 이름 역할로 디버깅 또는 크래쉬 로그를 볼 때 식별 용도로 사용된다. 두 번째 인자는 큐의 형태를 잡는 것으로 목적에 따라 위에서 설명한 두 가지 중 하나를 입력한다.

  글로벌 디스패치 큐는 메인 디스패치 큐와는 다르게 하나가 아니라 네 개의 큐가 존재한다. 나뉜 기준은 작업의 우선순위이며 종류는 High, Default, Low, Background가 있다. 추가할 작업의 적절한 우선순위를 택해서 dispatch_get_global_queue의 첫 번째 인자에 넣어준다. 두 번째는 인자는 사용하지 않는 예약 인자로 무조건 0을 입력해준다.



3. 디스패치 큐 메모리 관리.

  디스패치 큐도 MRC(Manual Reference Counting) 환경에서는 메모리 관리를 해줘야 한다.


:= dispatch_release, dispatch_retain

#define dispatch_release(object) ({ dispatch_object_t _o = (object); _dispatch_object_validate(_o); [_o release]; })


#define dispatch_retain(object) ({ dispatch_object_t _o = (object); _dispatch_object_validate(_o); (void)[_o retain]; })



4. 디스패치 큐에 작업 추가하기.

 작업 추가에는 디스패치 큐에 비동기 또는 동기로 추가하는 방법이 있다.


:= dispatch_async, dispatch_sync

// 비동기로 작업을 추가 및 수행한다

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);


// 동기로 작업을 추가 및 수행한다

void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);


  아래 예제를 보자.


// 예제 2


dispatch_queue_t queue = dispatch_queue_create("com.davin.queue"NULL);

dispatch_async(queue, ^{ NSLog(@"Call 1"); });

dispatch_async(queue, ^{ NSLog(@"Call 2"); });

dispatch_async(queue, ^{ NSLog(@"Call 3"); });

dispatch_async(queue, ^{ NSLog(@"Call 4"); });

dispatch_async(queue, ^{ NSLog(@"Call 5"); });

  

  시리얼 디스패치 큐를 생성했고, 비동기로 다섯 개의 작업을 추가했다. 결과는 순차적으로 실행되어 아래와 같다.


Call 1

Call 2

Call 3

Call 4

Call 5


  다음 예제를 보자.


// 예제 3

        

dispatch_queue_t queue = dispatch_queue_create("com.davin.queue"DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{ NSLog(@"Call 1"); });

dispatch_async(queue, ^{ NSLog(@"Call 2"); });

dispatch_async(queue, ^{ NSLog(@"Call 3"); });

dispatch_async(queue, ^{ NSLog(@"Call 4"); });

dispatch_async(queue, ^{ NSLog(@"Call 5"); });


  이번에는 콘커런트 디스패치 큐를 생성해서 비동기로 다섯 개의 작업을 추가했다. 결과는 동시에 실행됐기 때문에 매번 다르다.


Call 2

Call 5

Call 3

Call 1

Call 4


  다음 예제를 보자.


// 예제 4

        

dispatch_queue_t queue = dispatch_queue_create("com.davin.queue"DISPATCH_QUEUE_CONCURRENT);

dispatch_sync(queue, ^{ NSLog(@"Call 1"); });

dispatch_sync(queue, ^{ NSLog(@"Call 2"); });

dispatch_sync(queue, ^{ NSLog(@"Call 3"); });

dispatch_sync(queue, ^{ NSLog(@"Call 4"); });

dispatch_sync(queue, ^{ NSLog(@"Call 5"); });


  예제 3과 비슷하지만, 동기로 작업 추가를 했다. 결과는 예제 2와 같은 걸 볼 수 있다.


Call 1

Call 2

Call 3

Call 4

Call 5



5. 디스패치 큐 제어하기.

  여기서 끝나면 GCD의 이점은 블럭 코딩에 의한 편리함 밖에 없지 않느냐 싶을 수도 있지만, 지금부터가 시작이다.


  먼저, 지정된 시간이 지난 후에 디스패치 큐에 작업을 추가하는 dispatch_after를 보자.


:= dispatch_after

// 디스패치 큐의 우선순위 설정

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);


  첫 번째 인자에서 설정한 시간이 지난 후에 두 번째 인자에 들어간 큐에 비동기로 작업을 추가한다. 예제를 보자.


// 예제 5

        

dispatch_queue_t queue = dispatch_queue_create("com.davin.queue"DISPATCH_QUEUE_SERIAL);


double delayInSeconds = 2.0;

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));

dispatch_after(popTime, queue, ^{  NSLog(@"Call 1"); });

dispatch_async(queue, ^{ NSLog(@"Call 2"); });

dispatch_async(queue, ^{ NSLog(@"Call 3"); });

dispatch_async(queue, ^{ NSLog(@"Call 4"); });

dispatch_async(queue, ^{ NSLog(@"Call 5"); });


  2초 후에 콘커런트 디스패치 큐에 작업이 추가되도록 했다. 결과는 아래와 같다.


23:49:53:039 Call 2

23:49:53:039 Call 3

23:49:53:039 Call 4

23:49:53:039 Call 5

23:49:55:039 Call 1


  여기서 주의해야 할 점은 2초 후에 큐에 추가되는 것이지 정확하게 2초 후 실행되는 것이 아니란 것이다.

  위 예제는 동시에 작업을 수행하는 콘커런트 큐이기 때문에 결과가 딱 떨어지는 것처럼 보이지만, 시스템 상태에 따라 동시에 수행할 수 있는 작업 수가 달라지기 때문에 항상 같을 수는 없다. 시리얼 디스패치 큐라면 더욱더 시간이 불일치하다. 다음 예제를 보자.


// 예제 6

        

typedef void (^blk_t)(void);

blk_t call = ^{

    NSMutableArray *array = [[NSMutableArray allocinit];

    for ( long i = 0; i < 5000000; i++ ) {

        [array addObject:[NSNumber numberWithLong:i]];

    }

};


double delayInSeconds = 2.0;

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));

dispatch_after(popTime, dispatch_get_main_queue(), ^{  NSLog(@"Call 1"); });

dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Call 2"); call(); });

dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Call 3"); call(); });

dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Call 4"); call(); });

dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"Call 5"); call(); });


  2초 후에 수행되는게 아니란 걸 보여주기 위해 작업의 크기를 늘렸다. 결과는 아래와 같다.


00:01:35:607 Call 2

00:01:37:351 Call 3

00:01:38:915 Call 4

00:01:40:452 Call 5

00:01:46:291 Call 1


  시간이 많이 지체된 것을 확인할 수 있다.


  다음으로, 사용자에 의해 생성된 디스패치 큐의 우선순위는 글로벌 큐의 Default 큐와 우선순위가 같다. 그리고 이 우선순위를 바꾸기 위해서는 아래와 같은 함수를 사용하면 된다.


:= dispatch_set_target_queue

// 디스패치 큐의 우선순위 설정

void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);


  첫 번째 인자는 우선순위를 변경할 대상(사용자가 생성한 큐만 유효하다) 큐이고, 두 번째 인자는 우선순위를 참조할 네 가지 글로벌 큐 중 하나이다.

  왜 이게 필요한지 의아할 수도 있지만, 예제를 먼저 보자.


// 예제 7

        

dispatch_queue_t queue1 = dispatch_queue_create("com.davin.queue1"NULL);

dispatch_queue_t queue2 = dispatch_queue_create("com.davin.queue2"NULL);


dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH0);

dispatch_set_target_queue(queue1, globalQueue1);

dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW0);

dispatch_set_target_queue(queue2, globalQueue2);

        

dispatch_async(queue2, ^{ NSLog(@"Call 1"); });

dispatch_async(queue1, ^{ NSLog(@"Call 2"); });

dispatch_async(queue2, ^{ NSLog(@"Call 3"); });

dispatch_async(queue1, ^{ NSLog(@"Call 4"); });

dispatch_async(queue1, ^{ NSLog(@"Call 5"); });


  예제 7의 결과는 아래와 같다.


Call 2

Call 4

Call 5

Call 1

Call 3


  queue1은 queue2보다 우선순위가 높게 설정되어 있기 때문에 위와 같은 결과가 나온다. 즉, 실행에 있어 queue1의 작업이 먼저 실행되는 것이다.


  다음은 디스패치 그룹에 대한 것이다. 그룹은 연관성 있는 작업들을 묶어서 식별하기 위해 또는 일련의 작업들이 수행을 완료했는지를 알기 위해 사용되는 개념이다(그룹 역시 큐와 마찬가지로 MRC 환경에서 별도로 메모리 관리를 해줘야한다).


:= dispatch_group_create, dispatch_group_async, dispatch_group_notify, dispatch_group_wait, dispatch_group_enter, dispatch_group_leave

// 그룹을 만든다

dispatch_group_t dispatch_group_create(void);


// 큐에 작업을 비동기로 추가하면서 그룹에도 추가한다

void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);


// 그룹에 속한 작업들이 모두 완료 됐을때 수행할 작업을 큐애 추가한다

// 큐에 추가되는 시점은 당연히 그룹의 모든 작업이 완료될 때이니 주의하자

void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);


// 이 코드를 실행한 스레드를 지정한 시간만큼 정지되도록 한다.

long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);


// 그룹 안에 새로운 작업의 추가를 알린다

void dispatch_group_enter(dispatch_group_t group);


// 추가된 작업이 완료 됐음을 알린다

void dispatch_group_leave(dispatch_group_t group);


  아래는 작업들이 동시에 수행되는 콘커런트 디스패치 큐에서 수행이 모두 완료됐는지 알고싶은 작업을 묶어 큐에 추가한 예이다.


// 예제 8


dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT0);


dispatch_group_async(group, queue, ^{ NSLog(@"Call 1"); });

dispatch_group_async(group, queue, ^{ NSLog(@"Call 2"); });

dispatch_group_async(group, queue, ^{ NSLog(@"Call 3"); });

dispatch_async(queue, ^{ NSLog(@"Call 4"); });

dispatch_group_notify(group, queue, ^{ NSLog(@"Finished!"); });

dispatch_async(queue, ^{ NSLog(@"Call 5"); });

dispatch_async(queue, ^{ NSLog(@"Call 6"); });


  결과는 아래와 같다. 작업들의 수행완료 여부를 손쉽게 알 수 있다.


Call 3

Call 1

Call 2

Call 4

Call 6

Call 5

Finished!

Call 7


  다음 예제를 보자.


// 예제 9

        

typedef void (^blk_t)(void);

blk_t call = ^{

    NSMutableArray *array = [[NSMutableArray allocinit];

    for ( long i = 0; i < 5000000; i++ ) {

        [array addObject:[NSNumber numberWithLong:i]];

    }

};


dispatch_queue_t queue = dispatch_queue_create("com.davin.testqueue"NULL);

dispatch_async(queue, ^{

    dispatch_group_t group = dispatch_group_create();

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT0);

    

    dispatch_async(globalQueue, ^{ NSLog(@"Call 1"); });

    dispatch_group_async(group, globalQueue, ^{ NSLog(@"Call 2"); call(); });

    dispatch_group_async(group, globalQueue, ^{ NSLog(@"Call 3"); call(); });

    dispatch_group_async(group, globalQueue, ^{ NSLog(@"Call 4"); call(); });

    

    NSLog(@"%ld"dispatch_group_wait(group, DISPATCH_TIME_FOREVER));;

    

    dispatch_async(globalQueue, ^{ NSLog(@"Call 5"); });

    dispatch_async(globalQueue, ^{ NSLog(@"Call 6"); });

    dispatch_async(globalQueue, ^{ NSLog(@"Call 7"); });

});


  예제 9는 dispatch_group_wait에 대한 예로 글로벌 디스패치 큐에 작업을 넣는데 dispatch_group_wait을 만난 시점에서 그룹의 작업들이 모두 끝날때까지 dispatch_group_wait 이후의 작업 추가 코드를 수행하지 못하는 것을 볼 수 있다.

  이것은 dispatch_group_wait의 두 번째 인자에서 지정한 시간만큼 dispatch_group_wait을 호출한 스레드를 멈추게하기 때문이다.

  또한, dispatch_group_wait은 리턴 값이 있는데 0일 경우 그룹에 있는 작업들이 모두 완료된 상태인 것이고 1 이상의 값은 그 만큼의 작업이 아직 수행 중이거나 대기 중이라는 것이다.

  디스패치 그룹 마지막 예제를 보자.


// 예제 10

        

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT0);


typedef void (^blk_t)(void (^callback)(void));

blk_t call = ^(void (^callback)(void)){

    NSMutableArray *array = [[NSMutableArray allocinit];

    for ( long i = 0; i < 5000000; i++ ) {

        [array addObject:[NSNumber numberWithLong:i]];

    }

    callback();

};


dispatch_group_async(group, globalQueue, ^{ NSLog(@"Call 1"); });

dispatch_group_async(group, globalQueue, ^{ NSLog(@"Call 2"); });

dispatch_group_enter(group);

call(^{

    dispatch_group_leave(group);

});

dispatch_group_async(group, globalQueue, ^{ NSLog(@"Call 3"); });

dispatch_group_notify(group, queue, ^{ NSLog(@"Finished!"); });


  예제 10은 dispatch_group_async를 걸치지 않고 동기적으로 그룹에 새로운 작업을 추가했음과 이를 수행했음을 명시적으로 알리는 dispatch_group_enter와 dispatch_group_leave의 예이다.

  즉, call(~~~) 블럭 함수를 호출해서 함수가 끝나기까지의 일들도 하나의 작업으로 취급하게 된 것이다. 코드를 돌려보면 enter~leave로 감싼 부분이 끝날때까지 Finished!가 뜨지 않는걸 볼 수 있다.


  여기까지 디스패치 그룹에 대한 함수 설명과 예제였다.


  이번에는 반복적인 작업을 한 번에 추가할 수 있는 함수를 보자.


:= dispatch_apply

// 반복적인 작업을 한 번에 추가할 수 있도록 해준다

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));


  첫 번째 인자에 들어간 횟수만큼 큐에 작업을 추가한다는 의미다.


// 예제 11

        

dispatch_queue_t queue = dispatch_queue_create("com.davin.testqueue"DISPATCH_QUEUE_CONCURRENT);

dispatch_apply(5, queue, ^(size_t index) {

    NSLog(@"Call %zu", index + 1);

});

NSLog(@"Finished!");


  예제 3에서 다섯 번의 로그를 찍기 위해 dispatch_async를 다섯 번 호출했다. 하지만, dispatch_apply를 이용하면 한 번의 호출로 동일한 결과를 볼 수 있다. 뿐만 아니라, dispatch_apply는 동기 호출로 반복 횟수 만큼의 작업이 모두 끝날때까지 기다린다.

  아래는 실행 결과이다.


Call 4

Call 3

Call 1

Call 2

Call 5

Finished!



  다음으로 볼 함수는 콘커런트 디스패치 큐에서 동시에 실행(레이스 컨디션이 발생할 우려가 있기 때문에)되서는 안될 작업을 위해 다른 작업들을 기다리게하는 dispatch_barrier_async에 대해 살펴보자.


:= dispatch_barrier_async, dispatch_barrier_sync

// 다른 작업들을 기다리게(이 작업과 동시에 실행되지 못 하도록) 하는 작업을 추가한다

void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);


  예제를 보자.


// 예제 12

        

typedef void (^blk_t)(void);

blk_t call = ^{

    NSMutableArray *array = [[NSMutableArray allocinit];

    for ( long i = 0; i < 5000000; i++ ) {

        [array addObject:[NSNumber numberWithLong:i]];

    }

};


dispatch_queue_t queue = dispatch_queue_create("com.davin.testqueue"DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{ NSLog(@"Call 1"); });

dispatch_async(queue, ^{ NSLog(@"Call 2"); });

dispatch_async(queue, ^{ NSLog(@"Call 3"); });

dispatch_async(queue, ^{ NSLog(@"Call 4"); });

dispatch_async(queue, ^{ NSLog(@"Call 5"); });

dispatch_async(queue, ^{ NSLog(@"Call 6"); });

dispatch_barrier_async(queue, ^{ NSLog(@"Call 7"); call(); });

dispatch_async(queue, ^{ NSLog(@"Call 8"); });

dispatch_async(queue, ^{ NSLog(@"Call 9"); });

dispatch_async(queue, ^{ NSLog(@"Call 10"); });

dispatch_async(queue, ^{ NSLog(@"Call 11"); });

dispatch_async(queue, ^{ NSLog(@"Call 12"); });

dispatch_async(queue, ^{ NSLog(@"Call 13"); });


  예제 12는 콘커런트 디스패치 큐에 다수의 작업을 추가했고 그 중 하나는 dispatch_barrier_async를 사용해 처리될 때까지 다른 작업들을 기다리게 하는 작업이다.

  예제를 실행했을 때 Call 7이 이전의 콘커런트 디스패치 큐 예와는 다르게 다른 작업을 실행하지 않고 Call 7의 작업이 끝날때까지 다른 작업을 실행하지 않는 것을 볼 수 있다.

  dispatch_barrier_async는 하나의 작업을 블럭시키는 용도로 쓰이지만, 이 작업이 크면 클수록 효율이 떨어지는 면이 있다. 좀 더 작업 보다 작은 크기의 단위로 블럭을 시킬 방법이 필요하다. 바로 세마포어다.


:= dispatch_semaphore_create, dispatch_semaphore_wait, dispatch_semaphore_signal

// 세마포어를 생성한다

dispatch_semaphore_t dispatch_semaphore_create(long value);


// 지정된 시간만큼 블럭을 건다

long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);


// 블럭을 해제 한다

long dispatch_semaphore_signal(dispatch_semaphore_t dsema);


  예제를 보자.


// 예제 13

        

NSMutableArray *array = [[NSMutableArray allocinit];


dispatch_queue_t queue = dispatch_queue_create("com.davin.testqueue"DISPATCH_QUEUE_CONCURRENT);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

for ( int i = 0; i < 100000; i++ )

{

    dispatch_async(queue, ^{

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

        [array addObject:[NSNull null]];

        dispatch_semaphore_signal(semaphore);

    });

}


  예제 13은 크기 1을 가지는 세마포어를 생성하고 array에 요소를 추가하는 부분(작업보다 작은 크기를)을 블럭 상태로 만들어 레이스 컨디션을 방지하고 있다(세마포어 역시 큐, 그룹과 마찬가지로 MRC 환경에서 별도로 메모리 관리를 해줘야 한다).


  지금까지 작업이 추가되는 시점이나 작업이 끝나는 시점 또는 실행되야 할 시점 그리고, 작업을 하나의 묶음으로 식별하는 방법을 알아봤다. 하지만 뭔가 부족하다고 느껴지지 않는가 제어라 하면 정지와 재개가 있어야 하지 않을까, 이번에는 디스패치 큐를 정지하거나 재개하는 방법을 알아보자.


:= dispatch_suspend, dispatch_resume

//  큐에서 작업을 실행하지 못하도록 정지시킨다

void dispatch_suspend(dispatch_object_t object);


// 정지된 큐를 재개 시킨다

void dispatch_resume(dispatch_object_t object);


  dispatch_suspend는 큐 안에 있는 실행 대기 중인 작업에만 해당하며, 이미 실행 중인 작업에 대해서는 효력을 갖지 못한다. 또한, 정지된 상태여도 작업 추가는 계속 받을 수 있다.



6. 특수한 작업 수행하기.

  개발을 하다 보면, 앱의 라이프사이클이 끝날 때까지 한 번만 실행되어야 할 부분이 존재한다. 예를 들자면, 싱글톤 생성이나 특정 값을 초기하는데 말이다. 직접 한 번만 실행되도록 구현하면 되지만 ARC 또는 멀티스레드의 영향을 받아 비정상적으로 동작할 우려가 있다. 이를 위해 GCD에서는 라이프사이클이 끝날 때까지 한 번만 실행되도록 도와주는 함수가 있다.


// 한 번만 실행할 수 있는 작업을 수행한다.

void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);


  아래는 예제이다.


// 예제 14

        

static dispatch_once_t once;


typedef void (^blk_t)(void);

blk_t call = ^(void){

    dispatch_once(&once, ^{

        NSLog(@"Call!");

    });

};


dispatch_queue_t queue = dispatch_queue_create("com.davin.testqueue"DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{ call(); });

dispatch_async(queue, ^{ call(); });

dispatch_async(queue, ^{ call(); });


  동시에 작업을 수행하는 콘커런트 디스패치 큐인데도 단 한 번만 Call!이 찍히는걸 볼 수 있다.



7. 마무리.

  여기까지 디스패치 큐의 종류를 이해하고, GCD에서 자주 사용하게 되는 함수에 대해 알아보고 테스트해 보았다. 다음에는 필자가 생각하기에 GCD의 고급 기능이라 생각되는 함수에 대해 포스트 할 것이다.



출처 : http://padgom.tistory.com/entry/iOS-%EA%B8%B0%EB%B3%B8-GCDGrand-Central-Dispatch-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0


Comments