1. 程式人生 > 實用技巧 >Objective C XPC 初步學習<一>

Objective C XPC 初步學習<一>

最近接觸的一個專案有用到xpc,對xpc還是很陌生,於是查詢資料學習一番,已經對概念跟使用有了一些基本瞭解,記錄學習下,這裡轉載一下一篇翻譯國外的xpc相關使用文件,下篇會寫一篇基本的建立使用教程。

原文 XPC

關於 XPC

XPC 是 OS X 下的一種 IPC (程序間通訊) 技術, 它實現了許可權隔離, 使得 App Sandbox 更加完備.

首先,XPC 更多關注的是實現功能某種的方式,通常採用其他方式同樣能夠實現。並沒有強調如果不使用 XPC,無法實現某些功能。

XPC 目的是提高 App 的安全性和穩定性。XPC 讓程序間通訊變得更容易,讓我們能夠相對容易地將 App 拆分成多個程序的模式。更進一步的是,XPC 幫我管理了這些程序的生命週期,當我們需要與子程序通訊的時候,子程序已經被 XPC 給執行起來了。

我們將使用在標頭檔案 NSXPCConnection.h 中宣告的 Foundation framework API,它是建立在標頭檔案 xpc/xpc.h 中宣告的原始 XPC API 之上的。XPC API 原本是純 C 實現的 API,很好地集成了 libdispatch(又名 GCD)。本文中我們將使用Foundation 中的類,它們可以讓我們使用 XPC 的幾乎全部功能(真實的表現了實際底層 C API 是如何工作的),同時與 C API 相比,Foundation API 使用起來會更加容易。

哪些地方用到了 XPC ?

Apple 在作業系統的各個部分廣泛使用了 XPC,很多系統 Framework 也利用了 XPC 來實現其功能。你可以在命令列執行如下搜尋命令:

% find /System/Library/Frameworks -name \*.xpc

結果顯示 Frameworks 目錄下有 55 個 XPC service(譯者注:在 Yosemite 下),範圍從 AddressBook 到 WebKit 等等。

如果在 /Applications 目錄下做同樣的搜尋,我們會發現從 iWork 套件到 Xcode,甚至是一些第三方應用程式都使用了 XPC。

Xcode 本身就是使用 XPC 的一個很好的例子:當你在 Xcode 中編輯 Swift 程式碼的時候,Xcode 就是通過 XPC 與 SourceKit 通訊的(譯者注:實際程序名應該是SourceKitService)。SourceKit 是主要負責原始碼解析,語法高亮,排版,自動完成等功能的 XPC service。更多詳情可以參考

JP Simard 的部落格.

其實 XPC 在 iOS 上應用的很廣泛 - 但是目前只有 Apple 能夠使用,第三方開發者還不能使用。

一個示例 App

讓我們來看一個簡單的示例:一個在 table view 中顯示多張圖片的 App。圖片是以 JPEG 格式從網路伺服器上下載下來的。

App看起來是這樣:

NSTableViewDataSource 會從 ImageSet 類載入圖片 ,像這樣:

func tableView(tableView: NSTableView!, viewForTableColumn tableColumn: NSTableColumn!, row: Int) -> NSView! {
    let cellView = tableView.makeViewWithIdentifier("Image", owner: self) as NSTableCellView
    var image: NSImage? = nil
    if let c = self.imageSet?.images.count {
        if row < c {
            image = self.imageSet?.images[row]
        }
    }
    cellView.imageView.image = image
    return cellView
}

ImageSet 類有一個簡單的屬性:

var images: NSImage![]

ImageLoader 類會非同步的填充這個圖片陣列。

不使用XPC

如果不使用XPC,我們可以這樣實現 ImageLoader 類來下載並解壓圖片:

class ImageLoader: NSObject {
    let session: NSURLSession

    init()  {
        let config = NSURLSessionConfiguration.defaultSessionConfiguration()
        session = NSURLSession(configuration: config)
    }

    func retrieveImage(atURL url: NSURL, completionHandler: (NSImage?)->Void) {
        let task = session.dataTaskWithURL(url) {
            maybeData, response, error in
            if let data: NSData = maybeData {
                dispatch_async(dispatch_get_global_queue(0, 0)) {
                    let source = CGImageSourceCreateWithData(data, nil).takeRetainedValue()
                    let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil).takeRetainedValue()
                    var size = CGSize(
                        width: CGFloat(CGImageGetWidth(cgImage)),
                        height: CGFloat(CGImageGetHeight(cgImage)))
                    let image = NSImage(CGImage: cgImage, size: size)
                    completionHandler(image)
                }
            }
        }
        task.resume()
    }
}

明確而且工作得很好。

錯誤隔離 (Fault Isolation) 和 許可權隔離 (Split Privileges)

我們的 App 做了三件不同的事情:從網際網路上下載資料,解碼為 JPEG,然後顯示。

如果把 App 拆分成三個獨立的程序,我們就能給每個程序單獨的許可權了;UI 程序並不需要訪問網路的許可權。圖片下載的程序的確需要訪問網路,但它不需要訪問檔案的許可權(它只是轉發資料,並不做儲存)。而將 JPEG 圖片解碼為 RGB 資料的程序既不需要訪問網路的許可權,也不需要訪問檔案的許可權。

通過這種方式,在我們的 App 中尋找安全漏洞的行為已經變得很困難了。另一個好處是,我們的 App 會變得更穩定;例如下載 service 因為 bug 導致的 crash 並不會影響 App 主程序的執行;而下載 service 會被重啟。

架構圖如下:

XPC 的使用十分靈活,我們還可以這樣設計:讓 App 直接和兩個 service 通訊,由 App 來負責 service 之間的資料互動。後面我們會看到 App 是如何找到 XPC services的。

迄今為止,大部分安全相關的 bug 都出現在解析不受信資料的過程當中,例如資料是我們從網際網路上接收到的,不受我們控制的。現實中 HTTP 協議和解析 JPEG 資料也需要處理這樣的問題,而通過這樣設計,我們將解析不受信資料的過程挪進了一個子程序,即一個 XPC service。

在 App 中使用 XPC Services

XPC service 由兩個部分組成:service 本身,以及與之通訊的程式碼。它們都很簡單而且相似,算是個好訊息。

在 Xcode 中有模板可以新增新的 XPC service target。 每個 service 都需要一個 bundle id,一個好的實踐是將其設定為 App 的 bundle id 的 subdomain(子域)。

在我們的例子中,App 的 bundle id 是 io.objc.Superfamous-Images,我們可以把下載 service 的 bundle id 設為 io.objc.Superfamous-Images.ImageDownloader

在 build 過程中,Xcode 會為 service target 建立一個獨立 bundle,這個 bundle 會被複制到 XPCServices目錄下,與 Resources 目錄平級。

當 App 將資料發給 io.objc.Superfamous-Images.ImageDownloader 這個 service 時,XPC 會自動啟動這個 service。

基於 XPC 的通訊基本都是非同步的。我們通過一個 App 和 service 都使用的 protocol 來進行定義。在我們的例子中:

@objc(ImageDownloaderProtocol) protocol ImageDownloaderProtocol {
    func downloadImage(atURL: NSURL!, withReply: (NSData?)->Void)
}

請注意 withReply: 這部分。它表明了訊息是如何通過非同步的方式回給呼叫方的。若返回的訊息帶有資料,需要將函式簽名最後一部分寫成:withReply: 並接受一個閉包引數的形式。

在我們的例子中,service 只提供了一個方法;但是我們可以在 protocol 裡定義多個方法。

App 到 service 的連線是通過建立 NSXPCConnection 物件來完成的,像這樣:

let connection = NSXPCConnection(serviceName: "io.objc.Superfamous-Images.ImageDownloader")
connection.remoteObjectInterface = NSXPCInterface(`protocol`: ImageDownloaderProtocol.self)
connection.resume()

我們可以把 connection 物件儲存為 self.imageDownloadConnection,這樣之後就可以像這樣和 service 進行通訊了:

let downloader = self.imageDownloadConnection.remoteObject as ImageDownloaderProtocol
downloader.downloadImageAtURL(url) {
    (data) in
    println("Got \(data.length) bytes.")
}

我們還應該給 connection 物件設定錯誤處理函式,像這樣:

let downloader = self.imageDownloadConnection.remoteObjectProxyWithErrorHandler {
        (error) in NSLog("remote proxy error: %@", error)
} as ImageDownloaderProtocol
downloader.downloadImageAtURL(url) {
    (data) in
    println("Got \(data.length) bytes.")
}

這就是 App 端的所有程式碼了。

監聽service請求

XPC service 通過 NSXPCListener 物件來監聽從 App 傳入的請求(譯者注:這是 NSXPCListenerDelegate中可選的方法)。listener 物件會給每個來自 App 的請求在 service 端建立對應的 connection 物件。

main.swift 中,我們可以這樣寫:

class ServiceDelegate : NSObject, NSXPCListenerDelegate {
    func listener(listener: NSXPCListener!, shouldAcceptNewConnection newConnection: NSXPCConnection!) -> Bool {
        newConnection.exportedInterface = NSXPCInterface(`protocol`: ImageDownloaderProtocol.self)
        var exportedObject = ImageDownloader()
        newConnection.exportedObject = exportedObject
        newConnection.resume()
        return true
    }
}

// Create the listener and run it by resuming:
let delegate = ServiceDelegate()
let listener = NSXPCListener.serviceListener()
listener.delegate = delegate;
listener.resume()

我們建立了一個全域性(相當於 C 或 Objective-C 中的 main 函式)的 NSXPCListener 物件,並設定了它的 delegate,這樣傳入連線就會呼叫我們的 delegate 方法了。我們需要給 connection 設定 App 端中同樣使用的 protocol。最後我們設定 ImageDownloader 例項,它實際上實現了介面:

class ImageDownloader : NSObject, ImageDownloaderProtocol {
    let session: NSURLSession

    init()  {
        let config = NSURLSessionConfiguration.defaultSessionConfiguration()
        session = NSURLSession(configuration: config)
    }

    func downloadImageAtURL(url: NSURL!, withReply: ((NSData!)->Void)!) {
        let task = session.dataTaskWithURL(url) {
            data, response, error in
            if let httpResponse = response as? NSHTTPURLResponse {
                switch (data, httpResponse) {
                case let (d, r) where (200 <= r.statusCode) && (r.statusCode <= 399):
                    withReply(d)
                default:
                    withReply(nil)
                }
            }
        }
        task.resume()
    }
}

值得注意的一個重要是,NSXPCListenerNSXPCConnection 預設是掛起 (suspended) 的。我們設定好後需要呼叫它們的 resume 方法來啟動。

GitHub上可以找到這個簡單的示例App。

監聽者 (Listener),連線 (Connection) 和匯出物件 (Exported Object)

在 App 端,我們有一個 connection 物件。每次將資料發給 service 時,我們需要呼叫 remoteObjectProxyWithErrorHandler 方法來建立一個遠端物件代理 (remote object proxy)。

而在service端,則多了一層。首先需要一個 listener,用來監聽來自 App 的傳入 connection。App 可以建立多個 connection,listener 會在 service 端建立相應的 connection 物件。每個 connection 物件都有唯一的 exported object,在 App 端,通過 remote object proxy 傳送的訊息就是給它的。

當 App 建立一個到 XPC service 的 connection 時,是 XPC 在管理這個 service 的生命週期,service 的啟動與停止都由 XPC runtime 完成,這對 App 來說是透明的。而且如果 service 因為某種原因 crash 了,也會透明地被重啟。

App 初始化 XPC connection 的時候,XPC service 並不會啟動,直到 App 實際傳送的第一條訊息到 remote object proxy 時才啟動。如果當前沒有未結束的響應,系統可能會因為記憶體壓力或者 XPC service 已經閒置了一段時間而停止這個 service。這種情況下,App 持有的 connection 物件任然有效,下次再使用這個 connection 物件的時候,XPC 系統會自動重啟對應的 XPC service。

如果 XPC service crash 了,它也會被透明地重啟,並且其對應的 connection 也會一直有效。但是如果 XPC service 是在接收訊息時 crash 了的話,App 需用重新發送該訊息才能接受到對應的響應。這就是為什麼要呼叫 remoteObjectProxyWithErrorHandler 方法來設定錯誤處理函數了。

這個方法接受一個閉包作為引數,在發生錯誤的時候被執行。XPC API 保證在錯誤處理裡的閉包或者是訊息響應裡的閉包之中,只有一個會被執行;如果訊息訊息響應裡的閉包被執行了,那麼錯誤處理的就不會被執行,反之亦然。這樣就使得資源清理變得容易了。

突然終止 (Sudden Termination)

XPC 是通過跟蹤那些是否有仍在處理請求來管理 service 的生命週期的,如果有請求正在執行,對應的 service 不會被停止。如果訊息請求的響應還沒有被髮送,則這個請求會被認為是正在執行的。對於那些沒有 reply 的處理程式的請求,只要方法體還在執行,這個請求就會被認為是正在執行的。

在某些情況下,我們可能想告訴 XPC 我們還有更多的工作要做,我們可以使用 NSProcessInfo 的 API 來實現這一點:

func disableAutomaticTermination(reason: String!)
func enableAutomaticTermination(reason: String!)

如果 XPC service 接受傳入請求並需要在後臺執行一些非同步操作,這些 API 就能派上用場了(即告訴系統不希望被突然終止)。某些情況下我們還可能需要調整我們的 QoS (服務質量)設定。

中斷 (Interruption) 和失效 (Invalidation)

XPC 的最常見的用法是 App 發訊息給它的 XPC service。XPC 允許非常靈活的設定。我們通過下文會了解到,connection 是雙向的,它可以是匿名監聽者 (anonymous listeners)。如果另一端消失了(因為 crash 或者是正常的程序終止),這時連線將很有可能變得無效。我們可以給 connection 物件設定失效處理函式,如果 XPC runtime 無法重新建立這個 connection,我們的失效處理函式將會被執行。

我們還可以給 connection 設定中斷處理程式,會在 connection 被中斷的時候會執行,儘管此時 connection 仍然是有效的。

NSXPCConnection中 對應的兩個屬性是:

var interruptionHandler: (() -> Void)!
var invalidationHandler: (() -> Void)!

雙向連線 (Bidirectional Connections)

一個經常被忽略而又有意思的事實是:connection 是雙向的。但是隻能通過 App 建立到 service 的初始連線。service 不能主動建立到 App 的連線(見下文的 service lookup)。一旦連線已經建好了,兩端都可以發起請求。

正如 service 端給 connection 物件設定了 exportedObject,App 端也可以這麼做。這樣可以讓 service 端通過 remoteObjectProxy 來和 App 的 exported object 進行通訊了。值得注意是,XPC service 由系統管理其生命週期,如果沒有未完成的請求,可能會被停止掉(參見上文的 Sudden Termination)。

服務查詢 (Service Lookup)

當我們連線到 XPC service 的時候,我們需要找到連線的另一端。對於使用私有 XPC service 的 App,XPC 會在 App 的 bundle 範圍內通過名字查詢。還有其他的方法來連線到 XPC,讓我們來看看所有的可能性。

XPC Service

假如 App 使用:

NSXPCConnection(serviceName: "io.objc.myapp.myservice")

XPC 會在 App 自己的名稱空間 (namespace) 查詢名為 io.objc.myapp.myservice 的service,這樣的 service 僅對當前 App 有效,其他 App 無法連線。XPC service bundle 要麼是位於 App 的 bundle 裡,要麼是在該 App 使用的 Framework 的 bundle 裡。

Mach Service

另一個選擇是使用:

NSXPCConnection(machServiceName: "io.objc.mymachservice", options: NSXPCConnectionOptions(0))

這會在當前使用者的登入會話 (login session) 中查詢名為 io.objc.mymachservice 的service。 我們可以在 /Library/LaunchAgents~/Library/LaunchAgents 目錄下安裝 launch agent,這些 launch agent 也以與 App 裡的 XPC service 幾乎相同的方式來提供 service。由於 launch agent 會在 per-login session 中啟動的,在同一個登入會話中執行的多個 App 可以和同一個 launch agent 進行通訊。

這種方法很有用,例如狀態列 (Status Bar) 中的 menu extra 程式(即右上角只有選單項的 App)需要和 UI App 進行通訊的時候。普通 App 和 menu extra 程式都可以和同一個 launch agent 進行通訊並互動資料。當你需要讓兩個以上的程序需要相互通訊,XPC 可以是一個非常優雅的方案。

假設我們要寫一個天氣類的 App,我們可以把天氣資料的抓取和解析做成 launch agent 方式的 XPC service。我們可以分別建立 menu extra 程式,普通 App,以及通知中心的 Widget 來顯示同樣的天氣資料。它們都可以通過 NSXPCConnection 和同一個 launch agent 進行通訊。

與 XPC service 相同,launch agent 的生命週期也可以完全由 XPC 掌控:按需啟動,閒置或者系統記憶體不足的時候停止。

匿名監聽者 (Anonymous Listeners) 和端點 (Endpoints)

XPC 有通過 connection 來傳遞被稱為 listener endpoints 的能力。這個概念一開始會讓人非常費解,但是它可以帶來更大的靈活性。

比如說我們有兩個 App,我們希望它們能夠過 XPC 來互相通訊,每個 App 都不知道其他 App 的存在,但它們都知道相同的一個(共享)launch agent。

這兩個 App 可以先連線到 launch agent。App A 建立一個被稱為 匿名監聽者 (anonymous listener) 的物件,並通過 XPC 傳送一個端點 (endpoint),並由匿名監聽者建立的物件給 launch agent。App B 可以通過 XPC 在同樣的 launch agent 中拿到這個 endpoint。這時,App B 就可以直接連線到這個匿名監聽者,即 App A。

在 App A 建立一個 anonymous listener:

let listener = NSXPCListener.anonymousListener()

類似於 XPC service 建立普通的 listener。然後從這個 listener 建立一個 endpoint:

let endpoint = listener.endpoint

這個 endpoint 可以通過 XPC 來傳遞(實現了 NSSecureCoding 協議 )。一旦 App B 獲取到這個 endpoint,它可以建立到 App A 的 listener 的一個 connection:

let connection = NSXPCConnection(listenerEndpoint: endpoint)

Privileged Mach Service

最後一個選擇是使用:

NSXPCConnection(machServiceName: "io.objc.mymachservice", options: .Privileged)

這種方式和 launch agent 非常類似,不同的是建立了到 launch daemon 的 connection。launch agent 程序是 per user 的,它們以使用者的身份執行在使用者的登入會話 (login session) 中。守護程序 (Daemon) 則是 per machine 的,即使當前多個使用者登入,一個 XPC daemon 也只有一個例項執行。

如果要執行 daemon 的話,有很多安全相關的問題需要考慮。雖然以 root 許可權執行 daemon 是可能的,但是最好是不要這麼這麼做。我們可能更希望它以一些獨特的使用者身份來執行。具體可以參考 TN2083 - Designing Secure Helpers and Daemons。大多數情況,我們並不需要 root 許可權。

檔案訪問 (File Access)

假設我們要建立一個 HTTP 檔案下載的 service。我們需要允許 service 能發起對外的網路連線請求。不太明顯的是,我們可以讓 service 下載寫入檔案而不需要訪問任何檔案。

它是如何做到的呢,首先我們在 App 中建立這個將被下載的檔案,然後給這個檔案建立一個檔案控制代碼 (file handle):

let fileURL = NSURL.fileURLWithPath("/some/path/we/want/to/download/to")
if NSData().writeToURL(fileURL, options:0, error:&error) {
    let maybeHandle = NSFileHandle.fileHandleForWritingToURL(url:fileURL, error:&error)
    if let handle = maybeHandle {
        self.startDownload(fromURL:someURL, toFileHandle: handle) {
            self.downloadComplete(url: someURL)
        }
    }
}


func startDownload(fromURL: NSURL, toFileHandle: NSFileHandlehandle, completionHandler: (NSURL)->Void) -> Void

然後將這個檔案控制代碼傳給 remote object proxy,實際上就是通過 XPC connection 傳給了 service,service 通過這個檔案控制代碼寫入內容,就可以儲存到實際的檔案中了。

同樣,我們可以在一個程序中開啟用於讀取資料的 NSFileHandle 物件,然後傳給另外一個程序,這樣就可以做到那個程序不需要直接訪問檔案也能讀取其內容了。

移動資料 (Moving Data)

雖然 XPC 非常高效,但是程序間訊息傳遞並不是免費的。如果你需要通過 XPC 傳遞大量的二進位制資料,你可以使用這些技巧。

正常情況下使用的 NSData 物件會在傳遞到另一端會被複制一份。對於較大的二進位制資料,更有效的方法是使用 記憶體對映 (memory-mapped) 資料。WWDC 2013 session 702 的slides 從 57 頁開始介紹瞭如何傳送大量資料

XPC 有個技巧,能夠保證資料在程序間傳遞不會被複制。訣竅就是利用 dispatch_data_tNSData 是 toll-free bridged 的。建立記憶體對映的 dispatch_data_t 例項與之配合,就可以高效的通過 XPC 來傳遞了。看上去是這樣:

let size: UInt = 8000
let buffer = mmap(nil, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0)
let ddata = dispatch_data_create(buffer, size, DISPATCH_TARGET_QUEUE_DEFAULT, _dispatch_data_destructor_munmap)
let data = ddata as NSData

除錯 (Debugging)

Xcode 支援通過 XPC 方式的程序間通訊的除錯。如果你在內嵌在 App 的私有 XPC service 裡設定一個斷點,偵錯程式將以你期望的方式在斷點處停下來。

請務必看看 Activity Tracing。這組 API 定義在在標頭檔案 os/activity.h 中,提供了一種能夠跨越上下文執行和程序邊界的傳遞方式,來檢視到底是什麼引起了所需要執行的行為。WWDC 2014 session 714, Fix Bugs Faster Using Activity Tracing,對此做了很好的介紹。

常見錯誤 (Common Mistakes)

一個最常見的錯誤是沒有呼叫 connection 或者 listener 的 resume 方法。記得它們建立後都是被掛起狀態。

如果 connection 無效,很大的可能是因為配置錯誤導致的。請檢查 bundle id 是不是和 service 名字相匹配,程式碼中是否指定了正確的 service 名字。

除錯守護程序 (Debugging Daemons)

除錯 daemon 會稍微複雜一些,但它仍然可以很好的工作。daemon會被 launchd 程序啟動。所以需要分兩部設定:在開發過程中,修改我們 daemon 的 launchd.plist,設定 WaitForDebugger 為true。然後在 Xcode 中,修改 daemon 的 scheme,在 scheme editor -> Run -> Info 頁下可以修改 Launch 方式,從 “Automatically” 改到 “Wait for executable to be launched.”

現在通過 Xcode 執行 daemon,daemon 不會被啟動,但是偵錯程式會一直等著直到它啟動為止。一旦 launchd 啟動了 daemon,偵錯程式會自動連線上,我們就可以開始幹活了。

Connection 的安全屬性

每個 NSXPCConnection 具有這些屬性

var auditSessionIdentifier: au_asid_t { get }
var processIdentifier: pid_t { get }
var effectiveUserIdentifier: uid_t { get }
var effectiveGroupIdentifier: gid_t { get }

來描述這個 connection。在 listener 端,如在 agent 或者 daemon 中,可以利用這些屬性來檢視誰在嘗試進行連線,可以基於這些屬性來決定是否允許這些連線。對於在 App bundle 裡的私有 XPC service,上面的屬性完全可以無視,因為只有當前 App 可以查詢到這個 service。

xpc_connection_create(3) 的 man page 中有一章 “Credentials”,介紹了一些使用這些 API 缺點,在使用時需要多加小心。

QoS 和 Boosts

在 OS X 10.10 中,Apple 提出了 Quality of Service (QoS) 概念。可以用來輔助調解如給 UI 較高優先順序,並降低後臺行為的優先順序。當 QoS 遇到 XPC service,事情就變得有趣了 - 想想 XPC service 一般是完成什麼樣的工作?

QoS 會跨程序傳送 (propagates),在大多數情況下我們都不需要擔心。當 UI 執行緒發起一個 XPC 呼叫時,service 會以 boosted QoS 來執行;但是如果 App 中的後臺執行緒發起 XPC 呼叫,這也會影響到 service 的 QoS,它會以較低的 QoS 來執行。

WWDC 2014 session 716, Power, Performance and Diagnostics ,介紹了很多關於 QoS 的內容。其中它就提到了如何使用 DISPATCH_BLOCK_DETACHED 來分離當前的QoS,即如何防止 QoS propagates。

所以當 XPC service 因為某些請求的副作用而開始一些不相關的工作時,必須確保它從 QoS 中分離

低階API (Lower-Level API)

NSXPCConnection 所有的 API 都是建立 C API 之上,可以在 xpc(3) man page 和子頁面中找到它的文件。

我們可以使用 C API 來為 App 建立 XPC service,只要兩端都使用 C API 就好。

在概念上 C API 和 Foundation 的 API 很相似(譯者注:實際上是 C API 在 10.7 中被率先引入),稍微令人困惑的一點是,C API 中一個 connection 可以同時做為一個接受傳入連線請求的 listener ,或者是到另一個程序的 connection。

事件流 (Event Streams)

目前只有 C API 提供的一個特性是,支援對於 IOKit events,BSD notifications,或者 Core Foundation 的distributed notifications 的 launch-on-demand(按需啟動)。這些在事件或者通知在 launch agent/daemons 也是可以使用的。

xpc_events(3) man page 中列出了這些事件流。通過 C API,可以相對簡單的實現一個當特定的硬體連線後按需啟動的一個後臺程序 (launch agent)。