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
128 lines (110 loc) · 3.43 KB
/
XPCService.swift
File metadata and controls
128 lines (110 loc) · 3.43 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
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 })!
}