일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- contentprovider
- BLOCK
- Push
- HTTP
- 안드로이드 개발 레벨업 교과서
- xcode
- 배포
- AccountManager
- ios
- Android O
- 트위터
- In-app Billing
- Unchecked Exception
- unreal
- GCM
- gradle
- 페이스북
- Android O Preview
- service
- Android
- Google Cloud Messasging
- signing
- NSURLConnection
- Activity 수명 주기
- 데이터 공유
- 카카오톡
- android studio
- ios9
- 다른 앱에서 열기
- Google Cloud Messasing
- Today
- Total
노블의 개발이야기
[iOS] GCD(Grand Central Dispatch) 사용하기 본문
GCD(Grand Central Dispatch)란, C언어로 되어 있는 스레드 관리 기술로 iOS4 부터 지원하고 있다.
또한 GCD와 같은 시점에 등장한 블럭 코딩 기반으로 기본에 사용하던 NSThread, NSOperation 보다 쉽게 스레드 응용 기술을 구현할 수 있도록 지원해준다.
1. 디스패치 큐(Dispatch Queues)
실행할 작업을 저장하는 큐로 시리얼 디스패치 큐와 콘커런트 디스패치 큐로 나눌 수 있다.
- 시리얼 디스패치 큐 (Serial Dispatch Queues)
큐에 추가된 순서 안에서 선입선출(FIFO) 방식으로 작업을 실행한다.
또한, 큐에 있는 작업 하나를 실행시킨 후에 실행이 끝날 때까지 큐에 있는 다른 작업들은 기다리고 있다.
즉, 스레드 하나로 순차 처리를 하고 있다.
- 콘커런터 디스패치 큐 (Concurrent Dispatch Queue)
시리얼 디스패치 큐와는 다르게 실행 중인 작업을 기다리지 않고 큐에 있는 다른 작업들을 동시에 별로의 스레드에 수행시킨다.
동시에 실행되는 작업 수는 시스템 상태에 따라 달라지며, 이것을 판단 및 관리는 XNU 커널이 알아서 해준다.
2. 디스패치 큐 얻기
디스패치 큐를 얻는 방법으로는 새로운 큐를 만드는 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 커널에 의하여 새로운 스레드에 작업을 수행시킨다.
// 새로운 시리얼 디스패치 큐를 생성한다
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_DEFAULT, 0);
dispatch_queue_create의 첫 번째 인자는 큐에서 작업을 실행할 때 생성되는 스레드의 이름 역할로 디버깅 또는 크래쉬 로그를 볼 때 식별 용도로 사용된다.
두 번째 인자는 큐의 형태를 잡는 것으로 목적에 따라 위에서 설명한 두 가지 중 하나를 입력한다.
글로벌 디스패치 큐는 메인 디스패치 큐와는 다르게 하나가 아니라 네 개의 큐가 존재한다.
나뉜 기준은 작업의 우선순위이며 종류는 High, Default, Low, Background가 있다.
추가할 작업의 적절한 우선순위를 택해서 dispatch_get_global_queue의 첫 번째 인자에 넣어준다.
두 번째 인자는 사용하지 않는 예약 인자로 무조건 0을 입력해준다.
3. 디스패치 큐 메모리 관리
디스패치 큐도 MRC(Manual Reference Counting) 환경에서 메모리 관리를 해줘야 한다.
#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. 디스패치 큐에 작업 추가하기
작업 추가에는 디스패치 큐에 비동기 또는 동기로 추가하는 방법이 있다.
// 비동기로 작업을 추가 및 수행한다
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
// 동기로 작업을 추가 및 수행한다
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
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
시리얼 디스패치 큐를 생성하여 비동기로 다섯 개의 작업을 추가했다.
결과는 순차적으로 실행되었다.
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
콘커런트 디스패치 큐를 생성해서 비동기로 다섯 개의 작업을 추가했다.
결과는 비순차적으로 실행되었다.
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"); });
[실행 결과]
Call 1
Call 2
Call 3
Call 4
Call 5
컨커런트 디스패치 큐로 생성하여 동기로 다섯 개의 작업을 추가했다.
결과는 순차적으로 실행되었다.
5. 디스패치 큐 제어하기
// 디스패치 큐의 우선순위 설정
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
첫 번째 인자에 dispatch_time_t 타입의 시간을 설정한다.
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"); });
[실행 결과]
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초 후에 큐에 추가되는 것이지 정확하게 2초 후에 실행되는 것이 아니란 것이다.
typedef void (^blk_t)(void);
blk_t call = ^{
NSMutableArray *array = [[NSMutableArray alloc] init];
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(); });
[실행 결과]
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
위 예제는 2초 후에 수행되는게 아니라는 것을 보여주기 위해 작업의 크기를 늘렸다.
시간이 많이 지체된 것을 확인 할 수 있다.
6. 디스패치 큐 우선순위
// 디스패치 큐의 우선순위 설정
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
첫 번째 인자는 우선순위를 변경할 대상(사용자가 생성한 큐만 유효하다) 큐
두 번째 인자는 우선순위를 참조할 네 가지 글로벌 큐 중 하나이다.
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_HIGH, 0);
dispatch_set_target_queue(queue1, globalQueue1);
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
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"); });
[실행 결과]
Call 2
Call 4
Call 5
Call 1
Call 3
queue1은 queue2보다 우선순위가 높게 설정되어 있기 때문에 위와 같은 결과가 나온다.
즉, 실행에 있어서 queue1의 작업이 먼저 실행된다.
7. 디스패치 그룹
그룹은 연관성 있는 작업들을 묶어서 식별하기 위해 또는 일련의 작업들이 수행을 완료했는지를 알기 위해 사용되는 개념이다.
(그룹 역시 큐와 마찬가지로 MRC 환경에서 별도로 메모리 관리를 해줘야 한다)
// 그룹을 만든다
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);
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
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
위 예제는 작업들이 동시에 수행되는 콘커런트 디스패치 큐에서 수행이 모두 완료되었는지 알고 싶은 작업을 묶어서 큐에 추가하여 작업이 완료되면 Finished!가 출력된다.
typedef void (^blk_t)(void);
blk_t call = ^{
NSMutableArray *array = [[NSMutableArray alloc] init];
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_DEFAULT, 0);
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"); });
});
위 예제는 dispatch_group_wait 에 대한 예제이다.
글로벌 디스패치 큐에 작업을 넣는데 dispatch_group_wait을 만난 시점에서 그룹의 작업이 모두 끝날 때까지 dispatch_group_wait 이후의 작업 추가 코드를 수행하지 못한다.
이는 dispatch_group_wait의 두 번째 인자에서 지정한 시간만큼 dispatch_group_wait을 호출한 스레드를 멈추게하기 때문이다.
또한, dispatch_group_wait은 리턴 값이 있는데 0일 경우 그룹에 있는 작업들이 모두 완료된 상태인 것이다.
1 이상의 값은 그 만큼의 작업이 아직 수행 중이거나 대기 중이라는 것이다.
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
typedef void (^blk_t)(void (^callback)(void));
blk_t call = ^(void (^callback)(void)){
NSMutableArray *array = [[NSMutableArray alloc] init];
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!"); });
위 예제는 dispatch_group_async를 거치지 않고 동기적으로 그룹에 새로운 작업을 추가했음과 이를 수행했음을 명시적으로 알리는 dispatch_group_enter와 dispatch_group_leave의 예이다.
즉, call(~~~) 블럭 함수를 호출해서 함수가 끝나기까지의 일들도 하나의 작업으로 취급하게 된 것이다.
코드를 돌려보면 enter~leave로 감싼 부분이 끝날 때까지 Finished!가 뜨지 않는 것을 볼 수 있다.
** group의 작업들을 잠시 멈추고 다른 작업을 수행후 다시 group의 작업을 수행하는 듯한 느낌을 준다(?)
7. 반복 작업 추가
// 반복적인 작업을 한 번에 추가할 수 있도록 해준다
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
첫 번째 인자에 들어간 횟수만큼 큐에 작업을 추가한다는 의미이다.
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!");
[실행 결과]
Call 4
Call 3
Call 1
Call 2
Call 5
Finished!
반복적인 작업을 위해 dispatch_async를 여러번 호출했지만, dispatch_apply를 이용하면 한 번의 호출로 동일한 결과를 볼 수 있다.
뿐만 아니라 dispatch_apply는 동기 호출로 반복 횟수 만큼의 작업이 모두 끝날 때가지 기다린다.
8. 레이스 컨디션(동시 실행) 방지
콘커런트 디스패치 큐에서 동시에 실행(레이스 컨디션이 발생할 우려가 있기 때문에) 되서는 안될 작업을 위해 다른 작업들을 기다리게 하는 dispatch_barrier_async 에 대해 살펴보자
// 다른 작업들을 기다리게(이 작업과 동시에 실행되지 못 하도록) 하는 작업을 추가한다
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);
typedef void (^blk_t)(void);
blk_t call = ^{
NSMutableArray *array = [[NSMutableArray alloc] init];
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"); });
위 예제는 콘커런트 디스패치 큐에 다수의 작업을 추가하였고 그 중 하나는 dispatch_barrier_async를 사용해 처리될 때까지 다른 작업들을 기다리게 하는 작업이다.
예제를 실행했을 때 Call 7이 이전의 콘커런트 디스패치 큐 예와는 다르게 다른 작업을 실행하지 않고 Call 7의 작업이 끝날때까지 다른 작업이 실행하지 않는 것을 볼 수 있다.
dispatch_barrier_async는 하나의 작업을 블럭시키는 용도로 쓰이지만, 이 작업이 크면 클수록 효율이 떨어지는 면이 있다.
좀 더 작업이 작은 크기의 단위로 블럭을 시킬 방법이 필요하다. 바로 세마포어이다.
9. 세마포어
// 세마포어를 생성한다
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);
NSMutableArray *array = [[NSMutableArray alloc] init];
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);
});
}
크기 1을 가지는 세마포어를 생성하고 array에 요소를 추가하는 부분(작업보다 작은 크기를)을 블럭 상태로 만들어 레이스 컨디션을 방지하고 있다. (세마포어 역시 큐, 그룹과 마찬가지로 MRC 환경에서 별도로 메모리 관리를 해줘야 한다.)
10. 큐 정지/재개
// 큐에서 작업을 실행하지 못하도록 정지시킨다
void dispatch_suspend(dispatch_object_t object);
// 정지된 큐를 재개 시킨다
void dispatch_resume(dispatch_object_t object);
dispatch_suspend는 큐 안에 있는 실행 대기 중인 작업에만 해당하며, 이미 실행 중인 작업에 대해서는 효력을 갖지 못한다. 또한, 정지된 상태여도 작업 추가는 계속 받을 수 있다.
11. 싱글톤 (Singleton)
// 한 번만 실행할 수 있는 작업을 수행한다.
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
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이 찍힌다.
[참고]
'iOS' 카테고리의 다른 글
[iOS] UIWebView 웹페이지 너비 맞추기 (0) | 2015.05.14 |
---|---|
[iOS] XML Parser (0) | 2015.05.06 |
[IOS] ARC - Toll-Free Bridging (348) | 2015.04.23 |
[iOS] 네트워크 상태 체크 (0) | 2015.04.23 |
[iOS] NSString에 URL Encoding과 URL Decoding 메소드 추가하기 (0) | 2015.04.20 |