macOS內(nèi)核與用戶態(tài)進(jìn)程的通信前面已經(jīng)講解過(guò)了,用戶態(tài)進(jìn)程間通信較為簡(jiǎn)單耗拓,Apple官方推薦使用XPC機(jī)制表悬。XPC有底層的C API炫贤,也有封裝后的上層的objc和swift API出吹,可以實(shí)現(xiàn)像調(diào)用自身進(jìn)程的接口一樣調(diào)用對(duì)端的暴露接口颤诀。這里以swift為例說(shuō)明XPC的代碼實(shí)現(xiàn)谜慌。開源項(xiàng)目NuwaStone用戶態(tài)進(jìn)程間通信均基于XPC實(shí)現(xiàn)然想,更多代碼細(xì)節(jié)可參考該項(xiàng)目。
對(duì)外接口及類定義
使用XPC需新建兩個(gè)協(xié)議欣范,作為自身進(jìn)程和對(duì)端進(jìn)程的通信接口变泄,然后新建一個(gè)處理XPC連接請(qǐng)求的類令哟,注意需繼承NSObject,以便實(shí)現(xiàn)XPC系統(tǒng)調(diào)用所需的代理接口妨蛹。注意屏富,為了方便XPCConnection類是自身進(jìn)程與對(duì)端進(jìn)程共用的,但部分方法是不能共用的蛙卤,詳細(xì)見注釋狠半。
// 客戶端進(jìn)程協(xié)議
@objc protocol ClientXPCProtocol {
}
// 服務(wù)端進(jìn)程協(xié)議
@objc protocol ServerXPCProtocol {
func connectResponse(_ handler: @escaping (Bool) -> Void)
}
// 連接請(qǐng)求處理類
class XPCConnection: NSObject {
static let shared = XPCConnection()
var listener: NSXPCListener?
// 連接處理對(duì)象
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 ?? ""
}
// 僅服務(wù)端進(jìn)程調(diào)用
func startListener() {
let newListener = NSXPCListener(machServiceName: "your service name")
newListener.delegate = self
newListener.resume()
listener = newListener
Logger(.Info, "Start XPC listener successfully.")
}
// 僅客戶端進(jìn)程調(diào)用
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的代碼實(shí)現(xiàn)是較為簡(jiǎn)單的,startListener函數(shù)首先需創(chuàng)建連接所用的Mach端口颤难,網(wǎng)絡(luò)資源在Mac內(nèi)核基本抽象為Mach端口資源神年,然后設(shè)置處理連接請(qǐng)求的代理為自身類,后面需編寫請(qǐng)求處理代碼以校驗(yàn)連接并建立連接行嗤。connectToServer函數(shù)首先判斷是否連接已建立已日,然后簡(jiǎn)單校驗(yàn)了一下APP包名。創(chuàng)建連接對(duì)象時(shí)需設(shè)置自身暴露接口和遠(yuǎn)程調(diào)用接口栅屏,設(shè)置完成后所調(diào)用的connectResponse為連接測(cè)試函數(shù)飘千。在調(diào)用遠(yuǎn)程接口前均需獲取remoteObjectProxy遠(yuǎn)程對(duì)象代理,這里需進(jìn)行錯(cuò)誤處理栈雳,如果出錯(cuò)則表示連接斷開占婉。
XPC連接請(qǐng)求系統(tǒng)調(diào)用代理接口
startListener函數(shù)中設(shè)置處理連接請(qǐng)求的代理為自身類,所以類需要實(shí)現(xiàn)代理方法甫恩,代理方法的實(shí)現(xiàn)很簡(jiǎn)單,僅需實(shí)現(xiàn)下面一種方法酌予。函數(shù)內(nèi)的實(shí)現(xiàn)看起來(lái)與connectToServer差不多磺箕,不過(guò)多了錯(cuò)誤處理機(jī)制。建立連接前可以進(jìn)行簽名校驗(yàn)以增強(qiáng)安全性抛虫。這里的invalidationHandler被系統(tǒng)調(diào)用時(shí)表明連接正常斷開松靡,interruptionHandler被系統(tǒng)調(diào)用時(shí)表明連接意外斷開,如果有斷開重連要求可以加在這里建椰。
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
}
服務(wù)端對(duì)外暴露接口
暴露接口根據(jù)自己的項(xiàng)目需要自行設(shè)計(jì)實(shí)現(xiàn)就好雕欺,但要注意這些接口是無(wú)返回值的,畢竟是遠(yuǎn)程調(diào)用棉姐,基本也不會(huì)需要返回值屠列。這里使用@escaping修飾代碼塊,表示代碼塊的調(diào)用可能在函數(shù)返回后伞矩。
extension XPCConnection: DaemonXPCProtocol {
func connectResponse(_ handler: @escaping (Bool) -> Void) {
Logger(.Info, "Client connected.")
handler(true)
}
}