[iOS Study] 병렬 프로그래밍 가이드 ( dispatch source )
About dispatch source
-
dispatch source 는 low-level system event 를 처리하기 위한 data type 이다.
-
Timer dispatch source 는 주기적인 noti 를 만든다.
-
Signal dispatch source 는 UNIX signal 이 도착하면 noti 를 한다.
-
Descriptor source 는 file, socket base operation 에 따라 notify 를 한다.
( read, write 가능할 때, delete, moved, rename 되었을 때, meta info 가 변경되었을 떄 )
-
Process dispatch source 는 process 관련된 event 에 notify 한다.
( process 가 있을 때, fork 나 exec 가 수행되었을 때, process 에 signal 이 도착했을 때 )
-
Mach port dispatch source 는 Mach 관련된 event 에 noti 를 한다.
-
Custom dispatch source 는 당신이 define 하고 trigger 하는 것에 noti 한다.
-
특별 system event 에 대해 dispatch queue 를 등록해 놓고 감시를 수행할 수 있다.
해당 event 에 대해 block object 를 등록해놓으면, 해당 event 가 도착해면 해당 dispatch queue 에 등록한 block 을 전달한다.
-
dispatch source 는 cancel 할 때까지 계속 붙어있으면서 해당 task 가 계속 불린다.
-
만약 새로운 event 가 도착했는데, 이전에 도착한 event 에 대한 block 이 수행되지 않았다면 (backlogged)
dispatch source 는 새로운 event 와 이전 event 를 합쳐버린다.
event 종류에 따라 old event 를 제거하고 새로운 event 로 정보를 갱신할 수 있다.
물론 그냥 없애지는 않고, 기존에 왔던 signal 에 대한 것은 total signal count 등으로 알려주기는 한다.
Dispatch source 만들기
-
dispatch_source_create 함수를 통해 dispatch source 를 만든다.
event handler 를 dispatch source 에 등록한다.
( timer 의 경우 dispatch_source_set_timer 를 통해 등록한다. )
option 으로 cancellation handler 를 dispatch source 에 등록할 수 있다.
dispatch_resume 함수를 통해 event 에 대한 처리를 개시시킬 수 있다.
-
dispatch source 는 추가적인 config 가 필요하기 때문에
dispatch_source_create 는 dispatch source 를 suspended state 상태로 전달한다.
suspended 상태일 때는 dispatch source 가 event 를 받아도 이를 처리하지 않는다.
suspended 상태인 이유는 event handler 설정을 실제 event 가 도착하기 전에 하기 위함이다.
Event Handler 만들고 설정하기
-
dispatch source 를 만들 때는 event 를 처리한 event handler 를 만들어서 전달해야 한다.
event handler 는 function 이나 block object 이다.
dispatch_source_set_event_handler 나 dispatch_source_set_event_handler_f 를 통해 설정 가능하다.
-
만약 어떤 event processing 을 위해서 event handler 가 이미 queue 에 들어있는데 process 되지 않은 상태라면,
그 다음에 들어온 같은 종류의 event 는 통합이 된다.
event 의 종류에 따라 최근것만 바라볼 수도 있고, 통합된 다른 event 정보들을 볼 수도 있다.
만약 특정 event 에 대한 event handler 가 이미 수행중이라면,
queue 에 넣지 않고 해당 handler 의 기능이 끝나기를 기다렸다가 끝나면 queue 에 다시 넣는다.
-
function event handler 는 context pointer 만을 전달받는다.
이 context pointer 에는 dispatch source object 가 들어있다.
그리고 이 function 은 return value 가 없다.
-
block event handler 는 parameter 도 없고, return value 도 없다.
-
function 은 context pointer 를 전달받지만, block 은 그렇지 않아서 스스로 구해야 한다.
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
size_t estimated = dispatch_source_get_data(source);
// do something..
});
dispatch_resume(source);
-
block 이나 function 안에서 해당 source 에 대한 정보를 modify 할 수 있지만, 그렇게 하는 것은 권장되지 않는다.
event handler 실행이 async 로 발생하기 떄문에 위험하기 때문이다.
-
아래는 event handler 안에서 event 에 대한 정보를 얻기 위한 함수들이다.
dispatch_source_get_handle
dispatch source 가 관리하는 system data type 를 return 한다.
descriptor 는 descriptor 를 표시하는 int return
signal 은 가장 최근의 signalId의 number 를 표시하는 int return
process 는 감시되고 있는 process 의 pid_t 를 return.
mach port 는 mach_port_t 라는 data structure 를 return.
dispatch_source_get_data
해당 event 에 대해 pending data 를 return 한다.
descriptor 는 read 의 경우 read 가능한 byte 수, write 의 경우 write 가능한 공간 int.
그 외의 경우는 descriptor 에서 발생하는 event 의 dispatch_source_cnode_flags_t 형태의 enumerated type 이 return 된다.
process 의 경우 process 에 발생한 dispatch_source_proc_flags_t 형태의 enumerated type 이 return.
Mach port 의 경우 dispatch_source_machport_flags_t 형태의 enumerated type 이 return.
Custom source 의 경우에는 dispatch_source_merge_data 함수를 통해 전달된 data 가 전달된다.
dispatch_source_get_mask
dispatch source 를 생성하는데 사용된 event 의 flag 가 전달된다.
process 의 경우 dispatch_source_proc_flags_t 형태의 enumerated type 이 return.
mach port 의 경우 dispatch_source_mach_send_flags_t 형태의 enumerated type 이 return.
custom source 의 경우 data value 를 merge 하는데 사용된 mask 가 return.
Cancellation Handler 설치하기
-
cancellation handler 는 대부분 optional 이다.
하지만 descriptor 나 Mach port 의 경우는 descriptor 나 Mach port 의 release 를 위해서 반드시 제공해야 한다.
-
cancellation handler 는 아무때나 설치 가능하지만, 보통 dispatch source 를 만들 때 설치한다.
dispatch_source_set_cancel_handler 나 dispatch_source_set_cancel_handler_f 함수를 통해 수행한다.
dispatch_source_set_cancel_handler(mySource, ^{
close(fd);
});
Target Queue 변경하기
-
dispatch_set_target_queue 를 통해서 언제든 target queue 를 바꿀 수도 있다.
이 queue 를 바꾸는 것은 asnyc 로 작동한다.
dispatch source 는 가능한한 빨리 이 queue 를 바꾸려 하지만 호출시점에 바로 적용되지는 않는다.
만약 dispath source 가 이미 queue 가 되어 있다면 그 녀석은 기존에 설정된 queue 에서 수행되고,
새로 적용한 queue 는 다음부터 적용된다.
Dispatch source 에 custom data 묶기
-
다른 GCD(Grand Central Dispatch)와 같이 dispatch_set_context 함수를 통해 custom data 를 dispatch source 와 결부시킬 수 있다.
context pointer 에 어떤 정보든 저장시킬 수 있다.
-
context pointer 에 어떤 정보를 저장시키려면 항상 cancellation handler 를 동반하여,
cancellation handler 에서 적절히 data 를 release 시켜주어야 한다.
-
block 을 사용하는 경우에는 event handler 에서 local 정보를 참조할 수 있다.
이것이 context pointer 에 정보 저장하는 것의 사용성을 낮추지만, 잘 분별해서 사용해야 한다.
dispatch source 는 app 에서 long-lived 될 수 있기 때문에 pointer 를 참조하는 녀석들은 조심해서 써야 한다.
이 pointer 들은 언제 deallocated 될지 모르기 때문이다.
그래서 보통 copy & retain 하여 이런 변수를 차단하는 것이 좋다.
-
다른 dispatch queue 처럼 dispatch source 도 dispatch_retain 과 dispatch_release 를 통해 reference count 가 조정될 수 있다.
dispatch source 는 최초에 reference count 가 자동 1 이 된다.
release 가 제대로 되면 system 이 reference count 가 0 이 되는 순간 dispatch source 를 deallocate 한다.
Timer 만들기
-
timer dispatch source 는 시간 기반의 주기적 event 를 생성한다.
규칙적으로 무언가를 실행시키기 위해서 이 timer 를 사용할 수 있다.
보통 game 같은 곳에서 화면 갱신하거나 animation 등을 하기 위해 잘 사용한다.
주기적인 server update 등에도 사용한다.
-
timer 를 설정할 때 leeway 값을 잘 설정하면,
얼마나 정확하게 timer 를 수행할지, core wake up 을 어떻게 할지, power manage 를 어떻게 할지 등을 조정할 수 있다.
leeway 값( second )이 0이라고 해도 완전 정확한 시간에 timer 가 불리는 것은 아니다. best condition 으로 fire 한다.
-
단말이 sleep 모드에 들어가면 모든 timer dispatch source 는 suspend 된다.
단말이 wake up 하면 해당 timer dispatch source 는 자동으로 활성화된다.
-
dispatch_time 함수에 DISPATCH_TIME_NOW 상수를 전달하면,
dispatch source 는 기본 system clock 을 사용하여 fire time 을 조정한다.
그러나 system clock 은 단말이 sleep 상태일 때 정확하지 않을 수 있다.
반대로 dispatch_walltime 함수를 호출한다면,
timer dispatch source 는 wall clock time 을 기반으로 한다.
wall time 은 interval 이 상대적으로 큰 경우에 적합하다.
-
uint64_t interval = xx;
uint64_t leeway = xx;
dispatch_block_t block = ^{
// do something..
};
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue() );
if ( timer ){
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
-
주기적인 time event 를 받는 것이 아닌, 특정 시간 이후 한번이라는 조건으로 어떤 event 를 수행시키고 싶다면,
dispatch_after 나 dispatch_after_f 함수를 통해서 block 을 전달하면 된다.
이 때 time value 는 absolute 나 relative 어떤 것이든 될 수 있다.
Descriptor 에서 data 읽기
-
file 이나 socket 으로부터 data 를 읽기 위해서 dispatch source 를 만들 때 type 을 DISPATCH_SOURCE_TYPE_READ 로 설정해야 한다.
-
data 를 읽을 때 항상 descriptor 가 non-blocking operation 을 하도록 설정해야 한다.
dispatch_source_get_data 함수를 통해 얼마나 읽을 수 있는지 알 수 있지만, 이 값은 이 함수를 부르는 시점과 실제 값을 읽는 시점의 값이 다를 수 있다.
그래서 network error 가 난다거나 하는 예외상황에서 실행이 멈출 수 있다.
이는 성능장애로 이어지고, serial queue 에서는 deadlock 이 될 수 있다.
-
int fd = open(filename, O_RDONLY);
if ( fd != -1 ){
fcntl(fd, F_SETFL, O_NONBLOCK); // avoid blocking the read operation
}
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);
if ( readSource ){
dispatch_source_set_event_handler(readSource, ^{
size_t estimated = dispatch_source_get_data(readSource) + 1;
char* buffer = (char*)malloc(estimated);
if ( buffer ){
ssize_t actual = read(fd, buffer, estimated);
Boolean isProcessSuccess = doSomething(buffer, actual);
free(buffer);
if (isProcessSuccess){
dispatch_source_cancel(readSource);
}
}
});
dispatch_source_set_cancel_handler(readSource, ^{
close(fd);
});
dispatch_resume(readSource);
}
Descriptor 에 data 쓰기
-
descriptor 에서 read 하기와 비슷하다.
source type 은 DISPATCH_SOURCE_TYPE_WRITE 를 사용한다.
-
int fd = open( filaname, O_WRONLY | O_CREATE | O_TRUNC, (S_IRUSR | S_IWUSR | S_ISUID | S_ISGID ) );
if ( fd != -1 ){
fcntl(fd, F_SETFL, O_NONBLOCK); // non block during write
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, queue);
if ( writeSource ){
dispatch_source_set_event_handler(writeSource, ^{
size_t bufferSize = xx;
void* buffer = malloc(bufferSize);
size_t actual = xx;
write(fd, buffer, actual);
free(buffer);
dispatch_source_cancel(writeSource);
});
dispatch_source_set_cancel_handler(writeSource, ^{
close( fd );
});
dispatch_resume(writeSource);
}
}
File-system object 감시하기
-
type 은 DISPATCH_SOURCE_TYPE_VNODE 를 사용한다.
삭제되거나, 쓰여지거나, rename 되는 등의 파일에 변화가 있을 때 불린다.
meta info 가 바뀌는 경우에도 불린다.
-
int fd = open(filename, O_EVTONLY);
if ( fd != -1 ){
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, DISPATCH_VNODE_RENAME, queue);
if ( source ){
int length = strlen(filename);
char* newString = (char*)malloc(length + 1);
newString = strcpy(newString, filename);
dispatch_set_contet(source, newString);
dispatch_source_set_event_handler(source, ^{
const char* oldFilename = (char*)dispatch_get_context(source);
// do something;
});
dispatch_source_set_cancel_handler(source, ^{
char* fileStr = (char*)dispatch_get_context(source);
free(fileStr);
close(fd);
});
dispatch_resume(source);
}
}
Signal 감시하기
-
UNIX signal 은 복구할 수 없는 에러 시그널부터, 중요한 정보( 예를 들면 process exit ) 전달까지 여러가지 signal 을 보낸다.
-
전통적으로 app 은 sigaction 함수를 통해 signal handler 를 설정해왔다.
sigaction 은 그러나 sync action 이다.
-
signal dispatch source 는 SIGKILL, SIGBUS, SIGSEGV signal 은 감지 불가능하다.
-
sigaction 과 비교하여 signal dispatch source 는 자유도는 올라가지만 그만큼 latency 는 발생할 수 있다.
덧붙여 위에 언급한 signal 들은 전달받지 못한다.
sigaction 은 signal 들을 받아들여 app 이 종료되지 않도록 처리할 수 있지만,
signal dispatch source 는 그런 것은 할 수 없고, 감지만 할 수 있다.
-
signal(SIGHUP, SIG_IGN);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue);
if ( source ){
dispatch_source_set_event_handler(source, ^{
// do something;
});
dispatch_resume(source);
}
Process 모니터링 하기
-
pid_t parentPID = xx;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, parentPID, DISPATCH_PROC_EXIT, queue);
if ( source ){
dispatch_source_set_event_handler(source, ^{
// do something..
dispatch_source_cancel(source);
dispatch_release(source);
});
dispatch_resume(source);
}
'프로그래밍 놀이터 > iOS' 카테고리의 다른 글
[Effecitve Objective-C] #2 헤더에 헤더를 포함하는 것을 최소화하라 (0) | 2017.07.17 |
---|---|
[Effecitve Objective-C] #1 Objective-C 의 기원과 친숙해져라. (0) | 2017.07.16 |
[iOS Study] 병렬 프로그래밍 가이드 ( operation queue ) (0) | 2017.07.02 |
[iOS Study] 병렬 프로그래밍 가이드 ( 병렬 앱 디자인 ) (0) | 2017.07.01 |
[iOS] can't return type callbacks for 3 (0) | 2017.06.28 |
댓글