macOS內核與用戶態(tài)進程的通信前面已經(jīng)講解過了,用戶態(tài)進程間通信較為簡單,Apple官方推薦使用XPC機制。XPC有底層的C API,也有封裝后的上層的objc和swift API,可以實現(xiàn)像調用自身進程的接口一樣調用對端的暴露接口。這里以swift為例說明XPC的代碼實現(xiàn)。開源項目NuwaStone用戶態(tài)進程間通信均基于XPC實現(xiàn),更多代碼細節(jié)可參考該項目。
對外接口及類定義
使用XPC需新建兩個協(xié)議,作為自身進程和對端進程的通信接口,然后新建一個處理XPC連接請求的類,注意需繼承NSObject,以便實現(xiàn)XPC系統(tǒng)調用所需的代理接口。注意,為了方便XPCConnection類是自身進程與對端進程共用的,但部分方法是不能共用的,詳細見注釋。
// 客戶端進程協(xié)議
@objc protocol ClientXPCProtocol {
}
// 服務端進程協(xié)議
@objc protocol ServerXPCProtocol {
func connectResponse(_ handler: @escaping (Bool) -> Void)
}
// 連接請求處理類
class XPCConnection: NSObject {
static let shared = XPCConnection()
var listener: NSXPCListener?
// 連接處理對象
var connection: NSXPCConnection?
private func getMachServiceName(from bundle: Bundle) -> String {
let clientKeys = bundle.object(forInfoDictionaryKey: ClientName) as? [String: Any]
let machServiceName = clientKeys?[MachServiceKey] as? String
return machServiceName ?? ""
}
// 僅服務端進程調用
func startListener() {
let newListener = NSXPCListener(machServiceName: "your service name")
newListener.delegate = self
newListener.resume()
listener = newListener
Logger(.Info, "Start XPC listener successfully.")
}
// 僅客戶端進程調用
func connectToServer(bundle: Bundle, delegate: ClientXPCProtocol, handler: @escaping (Bool) -> Void) {
guard connection == nil else {
Logger(.Info, "Client already connected.")
handler(true)
return
}
guard getMachServiceName(from: bundle) == ClientBundle else {
handler(false)
return
}
let newConnection = NSXPCConnection(machServiceName: DaemonBundle)
newConnection.exportedObject = delegate
newConnection.exportedInterface = NSXPCInterface(with: ClientXPCProtocol.self)
newConnection.remoteObjectInterface = NSXPCInterface(with: DaemonXPCProtocol.self)
connection = newConnection
newConnection.resume()
let proxy = newConnection.remoteObjectProxyWithErrorHandler { error in
Logger(.Error, "Failed to connect with error [\(error)]")
self.connection?.invalidate()
self.connection = nil
handler(false)
} as? DaemonXPCProtocol
proxy?.connectResponse(handler)
}
}
XPC的代碼實現(xiàn)是較為簡單的,startListener函數(shù)首先需創(chuàng)建連接所用的Mach端口,網(wǎng)絡資源在Mac內核基本抽象為Mach端口資源,然后設置處理連接請求的代理為自身類,后面需編寫請求處理代碼以校驗連接并建立連接。connectToServer函數(shù)首先判斷是否連接已建立,然后簡單校驗了一下APP包名。創(chuàng)建連接對象時需設置自身暴露接口和遠程調用接口,設置完成后所調用的connectResponse為連接測試函數(shù)。在調用遠程接口前均需獲取remoteObjectProxy遠程對象代理,這里需進行錯誤處理,如果出錯則表示連接斷開。
XPC連接請求系統(tǒng)調用代理接口
startListener函數(shù)中設置處理連接請求的代理為自身類,所以類需要實現(xiàn)代理方法,代理方法的實現(xiàn)很簡單,僅需實現(xiàn)下面一種方法。函數(shù)內的實現(xiàn)看起來與connectToServer差不多,不過多了錯誤處理機制。建立連接前可以進行簽名校驗以增強安全性。這里的invalidationHandler被系統(tǒng)調用時表明連接正常斷開,interruptionHandler被系統(tǒng)調用時表明連接意外斷開,如果有斷開重連要求可以加在這里。
extension XPCConnection: NSXPCListenerDelegate {
func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
newConnection.exportedObject = self
newConnection.exportedInterface = NSXPCInterface(with: DaemonXPCProtocol.self)
newConnection.remoteObjectInterface = NSXPCInterface(with: ClientXPCProtocol.self)
newConnection.invalidationHandler = {
self.connection = nil
Logger(.Info, "Client disconnected.")
}
newConnection.interruptionHandler = {
self.connection = nil
Logger(.Info, "Client interrupted.")
}
connection = newConnection
newConnection.resume()
return true
}
服務端對外暴露接口
暴露接口根據(jù)自己的項目需要自行設計實現(xiàn)就好,但要注意這些接口是無返回值的,畢竟是遠程調用,基本也不會需要返回值。這里使用@escaping修飾代碼塊,表示代碼塊的調用可能在函數(shù)返回后。
extension XPCConnection: DaemonXPCProtocol {
func connectResponse(_ handler: @escaping (Bool) -> Void) {
Logger(.Info, "Client connected.")
handler(true)
}
}