forked from intitni/CopilotForXcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathXPCService.swift
More file actions
146 lines (128 loc) · 4.02 KB
/
XPCService.swift
File metadata and controls
146 lines (128 loc) · 4.02 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
import Foundation
import Logger
@globalActor
public enum XPCServiceActor {
public actor TheActor {}
public static let shared = TheActor()
}
@XPCServiceActor
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?
private var isInvalidated = false
private lazy var _connection: InvalidatingConnection? = buildConnection()
var connection: NSXPCConnection? {
if isInvalidated { _connection = nil }
if _connection == nil { rebuildConnection() }
return _connection?.connection
}
nonisolated
init(
kind: Kind,
interface: NSXPCInterface,
logger: Logger,
delegate: XPCServiceDelegate? = nil
) {
self.kind = kind
self.interface = interface
self.logger = logger
self.delegate = delegate
}
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
self?.logger.info("XPCService Invalidated")
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)
}
private func markAsInvalidated() {
isInvalidated = true
}
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))
}
return try await stream.first(where: { _ in true })!
}
@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
}
}