關(guān)于Dispatch Source
Dispatch Source是GCD中的一種基本數(shù)據(jù)類型,從字面意思可稱其為調(diào)度源,它用于處理特定的系統(tǒng)底層事件,即:當(dāng)一些特定的系統(tǒng)底層事件發(fā)生時,調(diào)度源會捕捉到這些事件,然后可以做相應(yīng)的邏輯處理。
Dispatch Source可用來監(jiān)聽以下幾類事件:
- Timer Dispatch Source:定時調(diào)度源。
- Signal Dispatch Source:監(jiān)聽UNIX信號調(diào)度源,比如監(jiān)聽代表掛起指令的SIGSTOP信號。
- Descriptor Dispatch Source:監(jiān)聽文件相關(guān)操作和Socket相關(guān)操作的調(diào)度源。
- Process Dispatch Source:監(jiān)聽進(jìn)程相關(guān)狀態(tài)的調(diào)度源。
- Mach port Dispatch Source:監(jiān)聽Mach相關(guān)事件的調(diào)度源。
- Custom Dispatch Source:監(jiān)聽自定義事件的調(diào)度源。
使用Dispatch Source時,通常是先指定一個希望監(jiān)聽的系統(tǒng)事件類型,再指定一個捕獲到事件后進(jìn)行邏輯處理的閉包或者函數(shù)作為回調(diào)函數(shù),然后再指定一個該回調(diào)函數(shù)執(zhí)行的Dispatch Queue即可。當(dāng)監(jiān)聽到指定的系統(tǒng)事件發(fā)生時,Dispatch Source會將已指定的回調(diào)函數(shù)作為一個任務(wù)放入指定的隊列中執(zhí)行,也就是說當(dāng)監(jiān)聽到系統(tǒng)事件后就會觸發(fā)一個任務(wù),并自動將其加入隊列執(zhí)行。
這里與通常的手動添加任務(wù)的模式不同,一旦將Diaptach Source與Dispatch Queue關(guān)聯(lián)后,只要監(jiān)聽到系統(tǒng)事件,Dispatch Source就會自動將任務(wù)(回調(diào)函數(shù))添加到關(guān)聯(lián)的隊列中,直到我們調(diào)用函數(shù)取消監(jiān)聽。
為了保證監(jiān)聽到事件后回調(diào)函數(shù)能夠都到執(zhí)行,已關(guān)聯(lián)的Dispatch Queue會被Diaptach Source強引用。
有些時候回調(diào)函數(shù)執(zhí)行的時間較長,在這段時間內(nèi)Dispatch Source又監(jiān)聽到多個系統(tǒng)事件,理論上就會形成事件積壓,但好在Dispatch Source有很好的機制解決這個問題,當(dāng)有多個事件積壓時會根據(jù)事件類型,將它們進(jìn)行關(guān)聯(lián)和結(jié)合,形成一個新的事件。
Dispatch Source類型
Dispatch Source一共可以監(jiān)聽六類事件,根據(jù)同類事件的不同操作,Dispatch Source分為11個類型:
- DISPATCH_SOURCE_TYPE_DATA_ADD:屬于自定義事件,可以通過dispatch_source_get_data函數(shù)獲取事件變量數(shù)據(jù),在我們自定義的方法中可以調(diào)用dispatch_source_merge_data函數(shù)向Dispatch Source設(shè)置數(shù)據(jù),下文中會有詳細(xì)的演示。
- DISPATCH_SOURCE_TYPE_DATA_OR:屬于自定義事件,用法同上面的類型一樣。
- DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口發(fā)送事件。
- DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
- DISPATCH_SOURCE_TYPE_PROC:與進(jìn)程相關(guān)的事件。
- DISPATCH_SOURCE_TYPE_READ:讀文件事件。
- DISPATCH_SOURCE_TYPE_WRITE:寫文件事件。
- DISPATCH_SOURCE_TYPE_VNODE:文件屬性更改事件。
- DISPATCH_SOURCE_TYPE_SIGNAL:接收信號事件。
- DISPATCH_SOURCE_TYPE_TIMER:定時器事件。
- DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:內(nèi)存壓力事件。
創(chuàng)建Dispatch Source
使用dispatch_source_create函數(shù)創(chuàng)建Dispatch Source,函數(shù)原型如下:
dispatch_source_t dispatch_source_create(dispatch_source_type_t type,
uintptr_t handle,
unsigned long mask,
dispatch_queue_t _Nullable queue);
dispatch_source_create函數(shù)有四個參數(shù):
- type:指定Dispatch Source類型,共有11個類型,特定的類型監(jiān)聽特定的事件。
- handle:取決于要監(jiān)聽的事件類型,比如如果是監(jiān)聽Mach端口相關(guān)的事件,那么該參數(shù)就是mach_port_t類型的Mach端口號,如果是監(jiān)聽事件變量數(shù)據(jù)類型的事件那么該參數(shù)就不需要,設(shè)置為0就可以了。
- mask:取決于要監(jiān)聽的事件類型,比如如果是監(jiān)聽文件屬性更改的事件,那么該參數(shù)就標(biāo)識文件的哪個屬性,比如DISPATCH_VNODE_RENAME
。 - queue:設(shè)置回調(diào)函數(shù)所在的隊列。
<h1 id = "jumpid1">設(shè)置事件處理器
前文中提到過,當(dāng)Dispatch Source監(jiān)聽到事件時會調(diào)用指定的回調(diào)函數(shù)或閉包,該回調(diào)函數(shù)或閉包就是Dispatch Source的事件處理器。我們可以使用dispatch_source_set_event_handler或dispatch_source_set_event_handler_f函數(shù)給創(chuàng)建好的Dispatch Source設(shè)置處理器,前者是設(shè)置閉包形式的處理器,后者是設(shè)置函數(shù)形式的處理器:
既然是事件處理器,那么肯定需要獲取一些Dispatch Source的信息,GCD提供了三個在處理器中獲取Dispatch Source相關(guān)信息的函數(shù),比如handle、mask。而且針對不同類型的Dispatch Source,這三個函數(shù)返回數(shù)據(jù)的值和類型都會不一樣,下面來看看這三個函數(shù):
- dispatch_source_get_handle:這個函數(shù)用于獲取在創(chuàng)建Dispatch Source時設(shè)置的第二個參數(shù)handle。
- 如果是讀寫文件的Dispatch Source,返回的就是描述符。
- 如果是信號類型的Dispatch Source,返回的是int類型的信號數(shù)。
- 如果是進(jìn)程類型的Dispatch Source,返回的是pid_t類型的進(jìn)程id。
- 如果是Mach端口類型的Dispatch Source,返回的是mach_port_t類型的Mach端口。
- dispatch_source_get_data:該函數(shù)用于獲取Dispatch Source監(jiān)聽到事件的相關(guān)數(shù)據(jù)。
- 如果是讀文件類型的Dispatch Source,返回的是讀到文件內(nèi)容的字節(jié)數(shù)。
- 如果是寫文件類型的Dispatch Source,返回的是文件是否可寫的標(biāo)識符,正數(shù)表示可寫,負(fù)數(shù)表示不可寫。
- 如果是監(jiān)聽文件屬性更改類型的Dispatch Source,返回的是監(jiān)聽到的有更改的文件屬性,用常量表示,比如DISPATCH_VNODE_RENAME等。
- 如果是進(jìn)程類型的Dispatch Source,返回監(jiān)聽到的進(jìn)程狀態(tài),用常量表示,比如DISPATCH_PROC_EXIT等。
- 如果是Mach端口類型的Dispatch Source,返回Mach端口的狀態(tài),用常量表示,比如DISPATCH_MACH_SEND_DEAD等。
- 如果是自定義事件類型的Dispatch Source,返回使用dispatch_source_merge_data函數(shù)設(shè)置的數(shù)據(jù)。
- dispatch_source_get_mask:該函數(shù)用于獲取在創(chuàng)建Dispatch Source時設(shè)置的第三個參數(shù)mask。在進(jìn)程類型,文件屬性更改類型,Mach端口類型的Dispatch Source下該函數(shù)返回的結(jié)果與dispatch_source_get_data一樣。
設(shè)置取消處理器
取消處理器就是當(dāng)Dispatch Source被釋放時用來處理一些后續(xù)事情,比如關(guān)閉文件描述符或者釋放Mach端口等。我們可以使用dispatch_source_set_cancel_handler函數(shù)或者dispatch_source_set_cancel_handler_f函數(shù)給Dispatch Source注冊取消處理器。
更改目標(biāo)隊列
在上文中,我們說過可以使用dispatch_source_create函數(shù)創(chuàng)建Dispatch Source,并且在創(chuàng)建時會指定回調(diào)函數(shù)執(zhí)行的隊列,那么如果事后想更改隊列,比如說想更改隊列的優(yōu)先級,這時我們可以使用dispatch_set_target_queue函數(shù)實現(xiàn)。
這里需要注意的是,如果在更改目標(biāo)隊列時,Dispatch Source已經(jīng)監(jiān)聽到相關(guān)事件,并且回調(diào)函數(shù)已經(jīng)在之前的隊列中執(zhí)行了,那么會一直在舊的隊列中執(zhí)行完成,不會轉(zhuǎn)移到新的隊列中去。
恢復(fù)與暫停Dispatch Source
暫停Dispatch Source使用dispatch_suspend函數(shù);恢復(fù)Dispatch Source使用dispatch_resume函數(shù)。
需要注意的是,因為Dispatch Source創(chuàng)建之后,需要進(jìn)行一些配置,比如設(shè)置事件處理器等,所以剛創(chuàng)建好的Dispatch Source是處于暫停狀態(tài)的,因此使用時需要用dispatch_resume函數(shù)將其啟動。
廢棄Dispatch Source
如果我們不再需要使用某個Dispatch Source時,可以使用dispatch_source_cancel函數(shù)廢除,該函數(shù)只有一個參數(shù),那就是目標(biāo)Dispatch Source。
關(guān)聯(lián)用戶數(shù)據(jù)
在設(shè)置事件處理器中已提到獲取Dispatch Source信息的幾個函數(shù),但是對于自定義事件類型的Dispatch Source,dispatch_source_merge_data函數(shù)設(shè)置的數(shù)據(jù)為unsigned long類型,因而通過dispatch_source_get_data獲取的數(shù)據(jù)也只支持unsigned long類型,嚴(yán)重影響了自定義類型事件的應(yīng)用范圍。
對于這一點,可以通過給Dispatch Source關(guān)聯(lián)用戶數(shù)據(jù)的來解決:
void dispatch_set_context(dispatch_object_t object, void *context);
這個函數(shù)可以給自定義事件類型的Dispatch Source關(guān)聯(lián)任何我們需要的數(shù)據(jù),當(dāng)執(zhí)行事件處理器時,調(diào)用dispatch_get_context就能獲取最近一次關(guān)聯(lián)的數(shù)據(jù)。
注意,如果關(guān)聯(lián)了用戶數(shù)據(jù),那么不再需要這個數(shù)據(jù)時就應(yīng)當(dāng)及時釋放。
Dispatch Source實踐
定時器
使用定時器時需要調(diào)用 dispatch_source_set_timer函數(shù)來配置定時器,這個函數(shù)有四個參數(shù):
source:待配置的定時器類型的 Dispatch Source
start:控制定時器第一次觸發(fā)的時刻。參數(shù)類型是 dispatch_time_t,這是一個opaque類型,我們不能直接操作它。我們得需要 dispatch_time 和 dispatch_walltime 函數(shù)來創(chuàng)建它們。另外,常量 DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER 通常很有用。
interval:觸發(fā)間隔
leeway:定時器進(jìn)度,單位納秒;如果設(shè)為0,系統(tǒng)只是最大程度滿足精度需求。精度越高功耗越大。
- (void)timerDispatchSource
{
dispatch_source_t timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, dispatch_get_global_queue(0, 0));
if (timerSource)
{
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC); //1, the timer dispatch source uses the default system clock to determine when to fire. However, the default clock does not advance while the computer is asleep.
// dispatch_time_t startTime = dispatch_walltime(NULL, 0 * NSEC_PER_SEC); //2 the timer dispatch source tracks its firing time to the wall clock time
NSString *desc = timerSource.description;
dispatch_source_set_timer(timerSource, startTime, 1 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timerSource, ^{
static NSInteger i = 0;
++i;
NSLog(@"Timer %@ Task: %ld",desc,i);
// NSLog(@"Timer %@ Task: %ld",timerSource,i);
});
dispatch_source_set_cancel_handler(timerSource, ^{
// NSLog(@"Timer:%@ canceled",timerSource);
NSLog(@"Timer:%@ canceled",desc);
});
dispatch_resume(timerSource);
}
_myTimerSource = timerSource; ///< 必須要保存,除非在hander中引用timerSource,否則出了作用域,Timer就會被釋放
}
NSTimer 與 GCD Timer比較
NSTimer
- 依賴NSRunloop
- 容易導(dǎo)致內(nèi)存泄漏
- NSTimer的創(chuàng)建與撤銷必須在同一個線程操作、 performSelector的創(chuàng)建與撤銷必須在同一個線程操作
GCD Timer
- 可以被當(dāng)做對象放入數(shù)組或字典中
- GCD Timer必須強引用,否則出了棧就會失效,這種失效不會觸發(fā)取消處理器
- GCD Timer精度可控
- 如果使用dispatch_walltime來設(shè)置定時器的起始時間,定時器默認(rèn)使用walltime來觸發(fā)定時器;如果使用dispatch_time來設(shè)置定時器的起始時間,定時器默認(rèn)使用系統(tǒng)時鐘來觸發(fā)定時器,然而當(dāng)計算機休眠時,系統(tǒng)時鐘也是休眠的。對于時間間隔比較大的定時器,使用dispatch_walltime來設(shè)置定時器的起始時間
監(jiān)聽信號
- (void)signalDispatchSource
{
signal(SIGCHLD, SIG_IGN);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t signalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGCHLD, 0, queue);
if (signalSource)
{
dispatch_source_set_event_handler(signalSource, ^{
static NSInteger i = 0;
++i;
NSLog(@"Signal Detected: %ld",i);
});
dispatch_source_set_cancel_handler(signalSource, ^{
NSLog(@"Signal canceled");
});
dispatch_resume(signalSource);
}
_mySignalSource = signalSource; // 不能省,原因同定時器
}
注意
* SIGILL, SIGBUS, SIGSEGV不能監(jiān)聽
* 只是監(jiān)聽信號,并不處理信號
讀寫文件
- (void)writeDispatchSource
{
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *fileName = [filePath stringByAppendingString:@"/test.txt"];
int fd = open([fileName UTF8String], O_WRONLY | O_CREAT | O_TRUNC,
(S_IRUSR | S_IWUSR | S_ISUID | S_ISGID));
NSLog(@"Write fd:%d",fd);
if (fd == -1)
return ;
fcntl(fd, F_SETFL); // Block during the write.
dispatch_source_t writeSource = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE,fd, 0, queue);
dispatch_source_set_event_handler(writeSource, ^{
size_t bufferSize = 100;
void *buffer = malloc(bufferSize);
static NSString *content = @"Write Data Action: ";
content = [content stringByAppendingString:@"=New info="];
NSString *writeContent = [content stringByAppendingString:@"\n"];
void *string = [writeContent UTF8String];
size_t actual = strlen(string);
memcpy(buffer, string, actual);
write(fd, buffer, actual);
NSLog(@"Write to file Finished");
free(buffer);
// Cancel and release the dispatch source when done.
// dispatch_source_cancel(writeSource);
dispatch_suspend(writeSource); //不能省,否則只要文件可寫,寫操作會一直進(jìn)行,直到磁盤滿,本例中,只要超過buffer容量就會崩潰
// close(fd); //會崩潰
});
dispatch_source_set_cancel_handler(writeSource, ^{
NSLog(@"Write to file Canceled");
close(fd);
});
if (!writeSource)
{
close(fd);
return;
}
_myWriteSource = writeSource;
}
- (void)readDataDispatchSource
{
if (_myReadSource)
{
dispatch_source_cancel(_myReadSource);
}
NSString *filePath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *fileName = [filePath stringByAppendingString:@"/test.txt"];
// Prepare the file for reading.
int fd = open([fileName UTF8String], O_RDONLY);
NSLog(@"read fd:%d",fd);
if (fd == -1)
return ;
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, dispatch_get_main_queue());
if (!readSource)
{
close(fd);
return ;
}
// Install the event handler
//只要文件寫入了新內(nèi)容,就會自動讀入新內(nèi)容
dispatch_source_set_event_handler(readSource, ^{
long estimated = dispatch_source_get_data(readSource);
NSLog(@"Read From File, estimated length: %ld",estimated);
if (estimated < 0)
{
NSLog(@"Read Error:");
dispatch_source_cancel(readSource); //如果文件發(fā)生了截短,事件處理器會一直不停地重復(fù)
}
// Read the data into a text buffer.
char *buffer = (char *)malloc(estimated);
if (buffer)
{
ssize_t actual = read(fd, buffer, (estimated));
NSLog(@"Read From File, actual length: %ld",actual);
NSLog(@"Readed Data: \n%s",buffer);
// Boolean done = MyProcessFileData(buffer, actual); // Process the data.
// Release the buffer when done.
free(buffer);
// If there is no more data, cancel the source.
// if (done)
// dispatch_source_cancel(readSource);
}
});
// Install the cancellation handler
dispatch_source_set_cancel_handler(readSource, ^{
NSLog(@"Read from file Canceled");
close(fd);
});
// Start reading the file.
dispatch_resume(readSource);
_myReadSource = readSource; //can be omitted
}
注意:
* 確保非阻塞方式進(jìn)行讀寫,否則當(dāng)讀寫出錯的時候,會導(dǎo)致線程阻塞
自定義事件
以更新progressView進(jìn)度為例,定時器每0.05觸發(fā)一次,隨機更新progressView的進(jìn)度
- (void)customDispatchSourceForProgressView
{
static dispatch_source_t timerSource = nil;
static dispatch_source_t source = nil;
_progressView.progress = 0;
if (timerSource)
{
dispatch_source_cancel(timerSource);
timerSource = nil;
source = nil;
}
else
{
source =dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
dispatch_resume(source);
dispatch_source_set_event_handler(source, ^{
// 方法1,獲取dispatch_source_merge_data傳入的值,只支持unsigned long類型
// 如果出現(xiàn)合并,那么得到的值是合并的那幾次提交傳入的值的累加結(jié)果
unsigned long data = dispatch_source_get_data(source);
CGFloat accumulate = ((CGFloat)data)/100;
CGFloat progress = _progressView.progress;
progress += accumulate;
//方法2,獲取關(guān)聯(lián)數(shù)據(jù)
// char *c = dispatch_get_context(source);
// NSLog(@"%c",*c);
// NSNumber *num = (__bridge NSNumber *)(dispatch_get_context(source));
// NSLog(@"%@",num);
_progressView.progress = progress;
if (progress >= 1)
{
timerSource = nil;
source = nil;
}
});
dispatch_async(dispatch_get_global_queue(0, 0), ^
{
timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, dispatch_get_global_queue(0, 0));
if (timerSource)
{
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 0 * NSEC_PER_SEC);
dispatch_source_set_timer(timerSource, startTime, 0.05 * NSEC_PER_SEC, 0);
dispatch_source_set_event_handler(timerSource, ^{
//關(guān)聯(lián)用戶自定義信息
int i = rand()%5;
// char c = i + 65;
// dispatch_set_context(source, &c);
// NSNumber *num = @(i);
// dispatch_set_context(source, (__bridge void * _Nullable)(num));
dispatch_source_merge_data(source, i);
});
dispatch_resume(timerSource);
}
});
}
}