現(xiàn)代系統(tǒng)通常提供異步接口,允許應(yīng)用向系統(tǒng)提交請求,然后在系統(tǒng)處理請求時(shí)應(yīng)用可以繼續(xù)處理自己的事情。Grand Central Dispatch
正是基于這個(gè)基本行為而設(shè)計(jì),允許你提交請求,并通過block
和dispatch queue
報(bào)告結(jié)果。
dispatch source
是基礎(chǔ)數(shù)據(jù)類型,協(xié)調(diào)特定底層系統(tǒng)事件的處理。Grand Central Dispatch
支持以下dispatch source
:
- Timer dispatch source:定期產(chǎn)生通知
- Signal dispatch source:UNIX信號到達(dá)時(shí)產(chǎn)生通知
- Descriptor dispatch source:各種文件和socket操作的通知
- 數(shù)據(jù)可讀
- 數(shù)據(jù)可寫
- 文件在文件系統(tǒng)中被刪除、移動(dòng)、重命名
- 文件元數(shù)據(jù)信息改變
Process dispatch source:進(jìn)程相關(guān)的事件通知
- 當(dāng)進(jìn)程退出時(shí)
- 當(dāng)進(jìn)程發(fā)起fork或exec等調(diào)用
- 信號被遞送到進(jìn)程
- Mach port dispatch source:Mach相關(guān)事件的通知
- Custom dispatch source:你自己定義并自己觸發(fā)
Dispatch source
替代了異步回調(diào)函數(shù),來處理系統(tǒng)相關(guān)的事件。當(dāng)你配置一個(gè)dispatch source
時(shí),你指定要監(jiān)測的事件、dispatch queue
、以及處理事件的代碼(block或函數(shù)
)。當(dāng)事件發(fā)生時(shí),dispatch source
會(huì)提交你的block
或函數(shù)到指定的queue
去執(zhí)行
和手工提交到queue
的任務(wù)不同,dispatch source
為應(yīng)用提供連續(xù)的事件源。除非你顯式地取消,dispatch source
會(huì)一直保留與dispatch queue
的關(guān)聯(lián)。只要相應(yīng)的事件發(fā)生,就會(huì)提交關(guān)聯(lián)的代碼到dispatch queue
去執(zhí)行。
為了防止事件積壓到dispatch queue
,dispatch source
實(shí)現(xiàn)了事件合并機(jī)制。如果新事件在上一個(gè)事件處理器出列并執(zhí)行之前到達(dá),dispatch source
會(huì)將新舊事件的數(shù)據(jù)合并。根據(jù)事件類型的不同,合并操作可能會(huì)替換舊事件,或者更新舊事件的信息。
創(chuàng)建Dispatch Source
創(chuàng)建dispatch source
需要同時(shí)創(chuàng)建事件源和dispatch source
本身。事件源是處理事件所需要的native數(shù)據(jù)結(jié)構(gòu),例如基于描述符的dispatch source
,你需要打開描述符;基于進(jìn)程的事件,你需要獲得目標(biāo)程序的進(jìn)程ID。
然后可以如下創(chuàng)建相應(yīng)的dispatch source:
使用
dispatch_source_create
函數(shù)創(chuàng)建dispatch source
配置dispatch source
:
為dispatch source
設(shè)置一個(gè)事件處理器
對于定時(shí)器源,使用 dispatch_source_set_timer 函數(shù)設(shè)置定時(shí)器信息
為dispatch source
賦予一個(gè)取消處理器(可選)調(diào)用 dispatch_resume
函數(shù)開始處理事件由于dispatch source
必須進(jìn)行額外的配置才能被使用,dispatch_source_create
函數(shù)返回的dispatch source
將處于掛起狀態(tài)。此時(shí)dispatch source
會(huì)接收事件,但是不會(huì)進(jìn)行處理。這時(shí)候你可以安裝事件處理器,并執(zhí)行額外的配置。
編寫和安裝一個(gè)事件處理器
你需要定義一個(gè)事件處理器來處理事件,可以是函數(shù)或block對象,并使用 dispatch_source_set_event_handler
或 dispatch_source_set_event_handler_f
安裝事件處理器。事件到達(dá)時(shí),dispatch source
會(huì)提交你的事件處理器到指定的dispatch queue
,由queue
執(zhí)行事件處理器。
事件處理器的代碼負(fù)責(zé)處理所有到達(dá)的事件。如果事件處理器已經(jīng)在queue
中并等待處理已經(jīng)到達(dá)的事件,如果此時(shí)又來了一個(gè)新事件,dispatch source
會(huì)合并這兩個(gè)事件。事件處理器通常只能看到最新事件的信息,不過某些類型的dispatch source
也能獲得已經(jīng)發(fā)生以及合并的事件信息。
如果事件處理器已經(jīng)開始執(zhí)行,一個(gè)或多個(gè)新事件到達(dá),dispatch source
會(huì)保留這些事件,直到前面的事件處理器完成執(zhí)行。然后以新事件再次提交處理器到queue
。
函數(shù)事件處理器有一個(gè)context
指針指向dispatch source
對象,沒有返回值。Block
事件處理器沒有參數(shù),也沒有返回值。
// Block-based event handler
void (^dispatch_block_t)(void)
// Function-based event handler
void (dispatch_function_t)(void )
在事件處理器中,你可以從dispatch source
中獲得事件的信息,函數(shù)處理器可以直接使用參數(shù)指針,Block
則必須自己捕獲到dispatch source
指針,一般block定義時(shí)會(huì)自動(dòng)捕獲到外部定義的所有變量。
disatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(source, ^{
// Get some data from the source variable, which is captured
// from the parent context.
size_t estimated = dispatch_source_get_data(source);
// Continue reading the descriptor…
});
dispatch_resume(source);
Block
捕獲外部變量允許更大的靈活性和動(dòng)態(tài)性。當(dāng)然,在Block
中這些變量默認(rèn)是只讀的,雖然可以使用__block
來修改捕獲的變量,但是你最好不要在事件處理器中這樣做。因?yàn)?code>Dispatch source異步執(zhí)行事件處理器,當(dāng)事件處理器修改原始外部變量時(shí),有可能這些變量已經(jīng)不存在了。
下面是事件處理器能夠獲得的事件信息:
- 函數(shù) 描述
dispatch_source_get_handle
這個(gè)函數(shù)返回dispatch source
管理的底層系統(tǒng)數(shù)據(jù)類型。
- 對于描述符
dispatch source
,函數(shù)返回一個(gè)int
,表示關(guān)聯(lián)的描述符 - 對于信號
dispatch source
,函數(shù)返回一個(gè)int
,表示最新事件的信號數(shù)值 - 對于進(jìn)程
dispatch source
,函數(shù)返回一個(gè)pid_t
數(shù)據(jù)結(jié)構(gòu),表示被監(jiān)控的進(jìn)程 - 對于
Mach port dispatch source
,函數(shù)返回一個(gè)mach_port_t
數(shù)據(jù)結(jié)構(gòu) - 對于其它
dispatch source
,函數(shù)返回的值未定義
dispatch_source_get_data
這個(gè)函數(shù)返回事件關(guān)聯(lián)的所有未決數(shù)據(jù)。 - 對于從文件中讀取數(shù)據(jù)的描述符
dispatch source
,這個(gè)函數(shù)返回可以讀取的字節(jié)數(shù) - 對于向文件中寫入數(shù)據(jù)的描述符
dispatch source
,如果可以寫入,則返回正數(shù)值 - 對于監(jiān)控文件系統(tǒng)活動(dòng)的描述符
dispatch source
,函數(shù)返回一個(gè)常量,表示發(fā)生的事件類型,參考dispatch_source_vnode_flags_t
枚舉類型 - 對于進(jìn)程
dispatch source
,函數(shù)返回一個(gè)常量,表示發(fā)生的事件類型,參考dispatch_source_proc_flags_t
枚舉類型 - 對于
Mach port dispatch source
,函數(shù)返回一個(gè)常量,表示發(fā)生的事件類型,參考dispatch_source_machport_flags_t
枚舉類型 - 對于自定義
dispatch source
,函數(shù)返回從現(xiàn)有數(shù)據(jù)創(chuàng)建的新數(shù)據(jù),以及傳遞給dispatch_source_merge_data
函數(shù)的新數(shù)據(jù)。
dispatch_source_get_mask
這個(gè)函數(shù)返回用來創(chuàng)建dispatch source的事件標(biāo)志 - 對于進(jìn)程
dispatch source
,函數(shù)返回dispatch source
接收到的事件掩碼,參考dispatch_source_proc_flags_t
枚舉類型 - 對于發(fā)送權(quán)利的
Mach port dispatch source
,函數(shù)返回期望事件的掩碼,參考dispatch_source_mach_send_flags_t
枚舉類型 - 對于自定義 “或” 的
dispatch source
,函數(shù)返回用來合并數(shù)據(jù)值的掩碼。
安裝一個(gè)取消處理器
取消處理器在dispatch soruce
釋放之前執(zhí)行清理工作。多數(shù)類型的dispatch source
不需要取消處理器,除非你對dispatch source
有自定義行為需要在釋放時(shí)執(zhí)行。但是使用描述符或Mach port
的dispatch source
必須設(shè)置取消處理器,用來關(guān)閉描述符或釋放Mach port
。否則可能導(dǎo)致微妙的bug
,這些結(jié)構(gòu)體會(huì)被系統(tǒng)其它部分或你的應(yīng)用在不經(jīng)意間重用。
你可以在任何時(shí)候安裝取消處理器,但通常我們在創(chuàng)建dispatch source
時(shí)就會(huì)安裝取消處理器。使用 dispatch_source_set_cancel_handler
或 dispatch_source_set_cancel_handler_f
函數(shù)來設(shè)置取消處理器。
下面取消處理器關(guān)閉描述符:
dispatch_source_set_cancel_handler(mySource, ^{
close(fd); // Close a file descriptor opened earlier.
});
修改目標(biāo)Queue
在創(chuàng)建dispatch source
時(shí)可以指定一個(gè)queue
,用來執(zhí)行事件處理器和取消處理器。不過你也可以使用 dispatch_set_target_queue
函數(shù)在任何時(shí)候修改目標(biāo)queue
。修改queue可以改變執(zhí)行dispatch source
事件的優(yōu)先級。
修改dispatch source
的目標(biāo)queue
是異步操作,dispatch source
會(huì)盡可能快地完成這個(gè)修改。如果事件處理器已經(jīng)進(jìn)入queue
并等待處理,它會(huì)繼續(xù)在原來的Queue
中執(zhí)行。隨后到達(dá)的所有事件的處理器都會(huì)在后面修改的queue
中執(zhí)行。
關(guān)聯(lián)自定義數(shù)據(jù)到dispatch source
和Grand Central Dispatch
的其它類型一樣,你可以使用 dispatch_set_context
函數(shù)關(guān)聯(lián)自定義數(shù)據(jù)到dispatch source
。使用context
指針存儲(chǔ)事件處理器需要的任何數(shù)據(jù)。如果你在context
指針中存儲(chǔ)了數(shù)據(jù),你就應(yīng)該安裝一個(gè)取消處理器,在dispatch source
不再需要時(shí)釋放這些context
自定義數(shù)據(jù)。
如果你使用block
實(shí)現(xiàn)事件處理器,你也可以捕獲本地變量,并在Block
中使用。雖然這樣也可以代替context
指針,但是你應(yīng)該明智地使用Block捕獲變量。因?yàn)?code>dispatch source長時(shí)間存在于應(yīng)用中,Block
捕獲指針變量時(shí)必須非常小心,因?yàn)橹羔樦赶虻臄?shù)據(jù)可能會(huì)被釋放,因此需要復(fù)制數(shù)據(jù)或retain
。不管使用哪種方法,你都應(yīng)該提供一個(gè)取消處理器,在最后釋放這些數(shù)據(jù)。
Dispatch Source的內(nèi)存管理
Dispatch Source
也是引用計(jì)數(shù)的數(shù)據(jù)類型,初始計(jì)數(shù)為1,可以使用dispatch_retain
和 dispatch_release
函數(shù)來增加和減少引用計(jì)數(shù)。引用計(jì)數(shù)到達(dá)0時(shí),系統(tǒng)自動(dòng)釋放dispatch source
數(shù)據(jù)結(jié)構(gòu)。
dispatch source
的所有權(quán)可以由dispatch source
內(nèi)部或外部進(jìn)行管理。外部所有權(quán)時(shí),另一個(gè)對象擁有dispatch source
,并負(fù)責(zé)在不需要時(shí)釋放它。內(nèi)部所有權(quán)時(shí),dispatch source
自己擁有自己,并負(fù)責(zé)在適當(dāng)?shù)臅r(shí)候釋放自己。雖然外部所有權(quán)很常用,當(dāng)你希望創(chuàng)建自主dispatch source
,并讓它自己管理自己的行為時(shí),可以使用內(nèi)部所有權(quán)。例如dispatch source
應(yīng)用單一全局事件時(shí),可以讓它自己處理該事件,并立即退出。
Dispatch Source示例
創(chuàng)建一個(gè)定時(shí)器
定時(shí)器dispatch source
定時(shí)產(chǎn)生事件,可以用來發(fā)起定時(shí)執(zhí)行的任務(wù),如游戲或其它圖形應(yīng)用,可以使用定時(shí)器來更新屏幕或動(dòng)畫。你也可以設(shè)置定時(shí)器,并在固定間隔事件中檢查服務(wù)器的新信息。
所有定時(shí)器dispatch source
都是間隔定時(shí)器,一旦創(chuàng)建,會(huì)按你指定的間隔定期遞送事件。你需要為定時(shí)器dispatch source
指定一個(gè)期望的定時(shí)器事件精度,也就是leeway值,讓系統(tǒng)能夠靈活地管理電源并喚醒內(nèi)核。例如系統(tǒng)可以使用leeway值來提前或延遲觸發(fā)定時(shí)器,使其更好地與其它系統(tǒng)事件結(jié)合。創(chuàng)建自己的定時(shí)器時(shí),你應(yīng)該盡量指定一個(gè)leeway
值。
就算你指定leeway
值為0,也不要期望定時(shí)器能夠按照精確的納秒來觸發(fā)事件。系統(tǒng)會(huì)盡可能地滿足你的需求,但是無法保證完全精確的觸發(fā)時(shí)間。
當(dāng)計(jì)算機(jī)睡眠時(shí),定時(shí)器dispatch source
會(huì)被掛起,稍后系統(tǒng)喚醒時(shí),定時(shí)器dispatch source
也會(huì)自動(dòng)喚醒。根據(jù)你提供的配置,暫停定時(shí)器可能會(huì)影響定時(shí)器下一次的觸發(fā)。如果定時(shí)器dispatch source
使用 dispatch_time
函數(shù)或DISPATCH_TIME_NOW
常量設(shè)置,定時(shí)器dispatch source
會(huì)使用系統(tǒng)默認(rèn)時(shí)鐘來確定何時(shí)觸發(fā),但是默認(rèn)時(shí)鐘在計(jì)算機(jī)睡眠時(shí)不會(huì)繼續(xù)。
如果你使用dispatch_walltime
函數(shù)來設(shè)置定時(shí)器dispatch source
,則定時(shí)器會(huì)根據(jù)掛鐘時(shí)間來跟蹤,這種定時(shí)器比較適合觸發(fā)間隔相對比較大的場合,可以防止定時(shí)器觸發(fā)間隔出現(xiàn)太大的誤差。
下面是定時(shí)器dispatch source
的一個(gè)例子,每30秒觸發(fā)一次,leeway
值為1,因?yàn)殚g隔相對較大,使用 dispatch_walltime
來創(chuàng)建定時(shí)器。定時(shí)器會(huì)立即觸發(fā)第一次,隨后每30秒觸發(fā)一次。MyPeriodicTask
和 MyStoreTimer
是自定義函數(shù),用于實(shí)現(xiàn)定時(shí)器的行為,并存儲(chǔ)定時(shí)器到應(yīng)用的數(shù)據(jù)結(jié)構(gòu)。
dispatch_source_t CreateDispatchTimer(uint64_t interval,
uint64_t leeway,
dispatch_queue_t queue,
dispatch_block_t block)
{
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
0, 0, queue);
if (timer)
{
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
void MyCreateTimer()
{
dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC,
1ull * NSEC_PER_SEC,
dispatch_get_main_queue(),
^{ MyPeriodicTask(); });
// Store it somewhere for later use.
if (aTimer)
{
MyStoreTimer(aTimer);
}
}
雖然定時(shí)器dispatch source
是接收時(shí)間事件的主要方法,你還可以使用其它選擇。如果想在指定時(shí)間間隔后執(zhí)行一個(gè)block
,可以使用 dispatch_after
或 dispatch_after_f
函數(shù)。這兩個(gè)函數(shù)非常類似于dispatch_async
,但是只允許你指定一個(gè)時(shí)間值,時(shí)間一到就自動(dòng)提交block
到queue
中執(zhí)行,時(shí)間值可以指定為相對或絕對時(shí)間。
從描述符中讀取數(shù)據(jù)
要從文件或socket中讀取數(shù)據(jù),需要打開文件或socket
,并創(chuàng)建一個(gè) DISPATCH_SOURCE_TYPE_READ
類型的dispatch source
。你指定的事件處理器必須能夠讀取和處理描述符中的內(nèi)容。對于文件,需要讀取文件數(shù)據(jù),并為應(yīng)用創(chuàng)建適當(dāng)?shù)臄?shù)據(jù)結(jié)構(gòu);對于網(wǎng)絡(luò)socket
,需要處理最新接收到的網(wǎng)絡(luò)數(shù)據(jù)。
讀取數(shù)據(jù)時(shí),你總是應(yīng)該配置描述符使用非阻塞操作,雖然你可以使用dispatch_source_get_data
函數(shù)查看當(dāng)前有多少數(shù)據(jù)可讀,但在你調(diào)用它和實(shí)際讀取數(shù)據(jù)之間,可用的數(shù)據(jù)數(shù)量可能會(huì)發(fā)生變化。如果底層文件被截?cái)啵虬l(fā)生網(wǎng)絡(luò)錯(cuò)誤,從描述符中讀取會(huì)阻塞當(dāng)前線程,停止在事件處理器中間并阻止dispatch queue
去執(zhí)行其它任務(wù)。對于串行queue
,這樣還可能會(huì)死鎖,即使是并發(fā)queue,也會(huì)減少queue
能夠執(zhí)行的任務(wù)數(shù)量。
下面例子配置dispatch source
從文件中讀取數(shù)據(jù),事件處理器讀取指定文件的全部內(nèi)容到緩沖區(qū),并調(diào)用一個(gè)自定義函數(shù)來處理這些數(shù)據(jù)。調(diào)用方可以使用返回的dispatch source
在讀取操作完成之后,來取消這個(gè)事件。為了確保dispatch queue
不會(huì)阻塞,這里使用了fcntl
函數(shù),配置文件描述符執(zhí)行非阻塞操作。dispatch source
安裝了取消處理器,確保最后關(guān)閉了文件描述符。
dispatch_source_t ProcessContentsOfFile(const char* filename)
{
// Prepare the file for reading.
int fd = open(filename, O_RDONLY);
if (fd == -1)
return NULL;
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)
{
close(fd);
return NULL;
}
// Install the event handler
dispatch_source_set_event_handler(readSource, ^{
size_t estimated = dispatch_source_get_data(readSource) + 1;
// Read the data into a text buffer.
char* buffer = (char*)malloc(estimated);
if (buffer)
{
ssize_t actual = read(fd, buffer, (estimated));
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, ^{close(fd);});
// Start reading the file.
dispatch_resume(readSource);
return readSource;
}
在這個(gè)例子中,自定義的 MyProcessFileData
函數(shù)確定讀取到足夠的數(shù)據(jù),返回YES
告訴dispatch source
讀取已經(jīng)完成,可以取消任務(wù)。通常讀取描述符的dispatch source
在還有數(shù)據(jù)可讀時(shí),會(huì)重復(fù)調(diào)度事件處理器。如果socket
連接關(guān)閉或到達(dá)文件末尾,dispatch source
自動(dòng)停止調(diào)度事件處理器。如果你自己確定不再需要dispatch source
,也可以手動(dòng)取消它。
向描述符寫入數(shù)據(jù)
向文件或socket
寫入數(shù)據(jù)非常類似于讀取數(shù)據(jù),配置描述符為寫入操作后,創(chuàng)建一個(gè)DISPATCH_SOURCE_TYPE_WRITE
類型的dispatch source
,創(chuàng)建好之后,系統(tǒng)會(huì)調(diào)用事件處理器,讓它開始向文件或socket寫入數(shù)據(jù)。當(dāng)你完成寫入后,使用 dispatch_source_cancel
函數(shù)取消dispatch source
。
寫入數(shù)據(jù)也應(yīng)該配置文件描述符使用非阻塞操作,雖然 dispatch_source_get_data
函數(shù)可以查看當(dāng)前有多少可用寫入空間,但這個(gè)值只是建議性的,而且在你執(zhí)行寫入操作時(shí)可能會(huì)發(fā)生變化。如果發(fā)生錯(cuò)誤,寫入數(shù)據(jù)到阻塞描述符,也會(huì)使事件處理器停止在執(zhí)行中途,并阻止dispatch queue
執(zhí)行其它任務(wù)。串行queue
會(huì)產(chǎn)生死鎖,并發(fā)queue
則會(huì)減少能夠執(zhí)行的任務(wù)數(shù)量。
下面是使用dispatch source
寫入數(shù)據(jù)到文件的例子,創(chuàng)建文件后,函數(shù)傳遞文件描述符到事件處理器。MyGetData
函數(shù)負(fù)責(zé)提供要寫入的數(shù)據(jù),在數(shù)據(jù)寫入到文件之后,事件處理器取消dispatch source
,阻止再次調(diào)用。此時(shí)dispatch source的擁有者需負(fù)責(zé)釋放dispatch source
。
dispatch_source_t WriteDataToFile(const char* filename)
{
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC,
(S_IRUSR | S_IWUSR | S_ISUID | S_ISGID));
if (fd == -1)
return NULL;
fcntl(fd, F_SETFL); // Block during the 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)
{
close(fd);
return NULL;
}
dispatch_source_set_event_handler(writeSource, ^{
size_t bufferSize = MyGetDataSize();
void* buffer = malloc(bufferSize);
size_t actual = MyGetData(buffer, bufferSize);
write(fd, buffer, actual);
free(buffer);
// Cancel and release the dispatch source when done.
dispatch_source_cancel(writeSource);
});
dispatch_source_set_cancel_handler(writeSource, ^{close(fd);});
dispatch_resume(writeSource);
return (writeSource);
}
監(jiān)控文件系統(tǒng)對象
如果需要監(jiān)控文件系統(tǒng)對象的變化,可以設(shè)置一個(gè) DISPATCH_SOURCE_TYPE_VNODE
類型的dispatch source
,你可以從這個(gè)dispatch source
中接收文件刪除、寫入、重命名等通知。你還可以得到文件的特定元數(shù)據(jù)信息變化通知。
在dispatch source
正在處理事件時(shí),dispatch source
中指定的文件描述符必須保持打開狀態(tài)。
下面例子監(jiān)控一個(gè)文件的文件名變化,并在文件名變化時(shí)執(zhí)行一些操作(自定義的 MyUpdateFileName
函數(shù))。由于文件描述符專門為dispatch source
打開,dispatch source
安裝了取消處理器來關(guān)閉文件描述符。這個(gè)例子中的文件描述符關(guān)聯(lián)到底層的文件系統(tǒng)對象,因此同一個(gè)dispatch source
可以用來檢測多次文件名變化。
dispatch_source_t MonitorNameChangesToFile(const char* filename)
{
int fd = open(filename, O_EVTONLY);
if (fd == -1)
return NULL;
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)
{
// Copy the filename for later use.
int length = strlen(filename);
char* newString = (char*)malloc(length + 1);
newString = strcpy(newString, filename);
dispatch_set_context(source, newString);
// Install the event handler to process the name change
dispatch_source_set_event_handler(source, ^{
const char* oldFilename = (char*)dispatch_get_context(source);
MyUpdateFileName(oldFilename, fd);
});
// Install a cancellation handler to free the descriptor
// and the stored string.
dispatch_source_set_cancel_handler(source, ^{
char* fileStr = (char*)dispatch_get_context(source);
free(fileStr);
close(fd);
});
// Start processing events.
dispatch_resume(source);
}
else
close(fd);
return source;
}
監(jiān)測信號
應(yīng)用可以接收許多不同類型的信號,如不可恢復(fù)的錯(cuò)誤(非法指令)、或重要信息的通知(如子進(jìn)程退出)。傳統(tǒng)編程中,應(yīng)用使用 sigaction
函數(shù)安裝信號處理器函數(shù),信號到達(dá)時(shí)同步處理信號。如果你只是想信號到達(dá)時(shí)得到通知,并不想實(shí)際地處理該信號,可以使用信號dispatch source
來異步處理信號。
信號dispatch source
不能替代 sigaction
函數(shù)提供的同步信號處理機(jī)制。同步信號處理器可以捕獲一個(gè)信號,并阻止它中止應(yīng)用。而信號dispatch source
只允許你監(jiān)測信號的到達(dá)。此外,你不能使用信號dispatch source
獲取所有類型的信號,如SIGILL, SIGBUS, SIGSEGV
信號。
由于信號dispatch source
在dispatch queue
中異步執(zhí)行,它沒有同步信號處理器的一些限制。例如信號dispatch source
的事件處理器可以調(diào)用任何函數(shù)。靈活性增大的代價(jià)是,信號到達(dá)和dispatch source
事件處理器被調(diào)用的延遲可能會(huì)增大。
下面例子配置信號dispatch source
來處理SIGHUP
信號,事件處理器調(diào)用 MyProcessSIGHUP
函數(shù),用來處理信號。
void InstallSignalHandler()
{
// Make sure the signal does not terminate the application.
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, ^{
MyProcessSIGHUP();
});
// Start processing signals
dispatch_resume(source);
}
}
監(jiān)控進(jìn)程
進(jìn)程dispatch source
可以監(jiān)控特定進(jìn)程的行為,并適當(dāng)?shù)仨憫?yīng)。父進(jìn)程可以使用dispatch source
來監(jiān)控自己創(chuàng)建的所有子進(jìn)程,例如監(jiān)控子進(jìn)程的死亡;類似地,子進(jìn)程也可以使用dispatch source
來監(jiān)控父進(jìn)程,例如在父進(jìn)程退出時(shí)自己也退出。
下面例子安裝了一個(gè)進(jìn)程dispatch source
,監(jiān)控父進(jìn)程的終止。當(dāng)父進(jìn)程退出時(shí),dispatch source
設(shè)置一些內(nèi)部狀態(tài)信息,告知子進(jìn)程自己應(yīng)該退出。MySetAppExitFlag
函數(shù)應(yīng)該設(shè)置一個(gè)適當(dāng)?shù)臉?biāo)志,允許子進(jìn)程終止。由于dispatch source
自主運(yùn)行,因此自己擁有自己,在程序關(guān)閉時(shí)會(huì)取消并釋放自己。
void MonitorParentProcess()
{
pid_t parentPID = getppid();
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, ^{
MySetAppExitFlag();
dispatch_source_cancel(source);
dispatch_release(source);
});
dispatch_resume(source);
}
}
取消一個(gè)Dispatch Source
除非你顯式地調(diào)用 dispatch_source_cancel
函數(shù),dispatch source
將一直保持活動(dòng),取消一個(gè)dispatch source
會(huì)停止遞送新事件,并且不能撤銷。因此你通常在取消dispatch source
后立即釋放它:
void RemoveDispatchSource(dispatch_source_t mySource)
{
dispatch_source_cancel(mySource);
dispatch_release(mySource);
}
取消一個(gè)dispatch source
是異步操作,調(diào)用 dispatch_source_cancel
之后,不會(huì)再有新的事件被處理,但是正在被dispatch source
處理的事件會(huì)繼續(xù)被處理完成。在處理完最后的事件之后,dispatch source
會(huì)執(zhí)行自己的取消處理器。
取消處理器是你最后的執(zhí)行機(jī)會(huì),在那里執(zhí)行內(nèi)存或資源的釋放工作。例如描述符或mach port
類型的dispatch source
,必須提供取消處理器,用來關(guān)閉描述符或mach port
掛起和繼續(xù)Dispatch Source
你可以使用 dispatch_suspend
和dispatch_resume
臨時(shí)地掛起和繼續(xù)dispatch source
的事件遞送。這兩個(gè)函數(shù)分別增加和減少dispatch
對象的掛起計(jì)數(shù)。因此,你必須每次dispatch_suspend
調(diào)用之后,都需要相應(yīng)的 dispatch_resume
才能繼續(xù)事件遞送。
掛起一個(gè)dispatch source
期間,發(fā)生的任何事件都會(huì)被累積,直到dispatch source
繼續(xù)。但是不會(huì)遞送所有事件,而是先合并到單一事件,然后再一次遞送。例如你監(jiān)控一個(gè)文件的文件名變化,就只會(huì)遞送最后一次的變化事件