forked from github/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathXPCService.swift
More file actions
153 lines (134 loc) · 4.13 KB
/
XPCService.swift
File metadata and controls
153 lines (134 loc) · 4.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
import Foundation
import Logger
@globalActor
public enum XPCServiceActor {
public actor TheActor {}
public static let shared = TheActor()
}
class XPCService {
enum Kind {
case machService(identifier: String)
case anonymous(endpoint: NSXPCListenerEndpoint)
}
let kind: Kind
let interface: NSXPCInterface
let logger: Logger
weak var delegate: XPCServiceDelegate?
@XPCServiceActor
private var isInvalidated = false
@XPCServiceActor
private lazy var _connection: InvalidatingConnection? = buildConnection()
@XPCServiceActor
var connection: NSXPCConnection? {
if isInvalidated { _connection = nil }
if _connection == nil { rebuildConnection() }
return _connection?.connection
}
init(
kind: Kind,
interface: NSXPCInterface,
logger: Logger,
delegate: XPCServiceDelegate? = nil
) {
self.kind = kind
self.interface = interface
self.logger = logger
self.delegate = delegate
}
@XPCServiceActor
private func buildConnection() -> InvalidatingConnection {
let connection = switch kind {
case let .machService(name):
NSXPCConnection(machServiceName: name)
case let .anonymous(endpoint):
NSXPCConnection(listenerEndpoint: endpoint)
}
connection.remoteObjectInterface = interface
connection.invalidationHandler = { [weak self] in
Task { [weak self] in
self?.markAsInvalidated()
await self?.delegate?.connectionDidInvalidate()
}
}
connection.interruptionHandler = { [weak self] in
self?.logger.info("XPCService interrupted")
Task { [weak self] in
await self?.delegate?.connectionDidInterrupt()
}
}
connection.resume()
return .init(connection)
}
@XPCServiceActor
private func markAsInvalidated() {
isInvalidated = true
}
@XPCServiceActor
private func rebuildConnection() {
_connection = buildConnection()
}
}
public protocol XPCServiceDelegate: AnyObject {
func connectionDidInvalidate() async
func connectionDidInterrupt() async
}
private class InvalidatingConnection {
let connection: NSXPCConnection
init(_ connection: NSXPCConnection) {
self.connection = connection
}
deinit {
connection.invalidationHandler = {}
connection.interruptionHandler = {}
connection.invalidate()
}
}
struct NoDataError: Error {}
struct AutoFinishContinuation<T> {
var continuation: AsyncThrowingStream<T, Error>.Continuation
func resume(_ value: T) {
continuation.yield(value)
continuation.finish()
}
func reject(_ error: Error) {
if (error as NSError).code == -100 {
continuation.finish(throwing: CancellationError())
} else {
continuation.finish(throwing: error)
}
}
}
@XPCServiceActor
func withXPCServiceConnected<T, P>(
connection: NSXPCConnection,
_ fn: @escaping (P, AutoFinishContinuation<T>) -> Void
) async throws -> T {
let stream: AsyncThrowingStream<T, Error> = AsyncThrowingStream { continuation in
let service = connection.remoteObjectProxyWithErrorHandler {
continuation.finish(throwing: $0)
} as! P
fn(service, .init(continuation: continuation))
}
for try await result in stream {
return result
}
throw XPCExtensionServiceError.failedToCreateXPCConnection
}
@XPCServiceActor
public func testXPCListenerEndpoint(_ endpoint: NSXPCListenerEndpoint) async -> Bool {
let connection = NSXPCConnection(listenerEndpoint: endpoint)
defer { connection.invalidate() }
let stream: AsyncThrowingStream<Void, Error> = AsyncThrowingStream { continuation in
_ = connection.remoteObjectProxyWithErrorHandler {
continuation.finish(throwing: $0)
}
continuation.yield(())
continuation.finish()
}
do {
try await stream.first(where: { _ in true })!
return true
} catch {
return false
}
}