Skip to content

Commit 2462fc4

Browse files
authored
Merge pull request #2807 from millenomi/runloop-spi
Add SPI for sources and observers to RunLoop
2 parents 01699b6 + 3062f14 commit 2462fc4

File tree

1 file changed

+173
-6
lines changed

1 file changed

+173
-6
lines changed

Sources/Foundation/RunLoop.swift

Lines changed: 173 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ open class RunLoop: NSObject {
7878
}
7979
}
8080

81+
@available(*, deprecated, message: "Directly accessing the run loop may cause your code to not become portable in the future.")
8182
open func getCFRunLoop() -> CFRunLoop {
8283
return _cfRunLoop
8384
}
@@ -208,12 +209,178 @@ extension RunLoop {
208209
}
209210
}
210211

211-
// SPI for XCTest
212-
#if os(Windows)
212+
// These exist as SPI for XCTest for now. Do not rely on their contracts or continued existence.
213+
213214
extension RunLoop {
214-
public func _stop() {
215-
CFRunLoopStop(getCFRunLoop())
216-
}
215+
@available(*, deprecated, message: "For XCTest use only.")
216+
public func _stop() {
217+
CFRunLoopStop(getCFRunLoop())
218+
}
219+
220+
@available(*, deprecated, message: "For XCTest use only.")
221+
public func _observe(_ activities: _Activities, in mode: RunLoop.Mode = .default, repeats: Bool = true, order: Int = 0, handler: @escaping (_Activity) -> Void) -> _Observer {
222+
let observer = _Observer(activities: activities, repeats: repeats, order: order, handler: handler)
223+
CFRunLoopAddObserver(self.getCFRunLoop(), observer.cfObserver, mode._cfStringUniquingKnown)
224+
return observer
225+
}
226+
227+
@available(*, deprecated, message: "For XCTest use only.")
228+
public func _observe(_ activity: _Activity, in mode: RunLoop.Mode = .default, repeats: Bool = true, order: Int = 0, handler: @escaping (_Activity) -> Void) -> _Observer {
229+
return _observe(_Activities(activity), in: mode, repeats: repeats, order: order, handler: handler)
230+
}
231+
232+
@available(*, deprecated, message: "For XCTest use only.")
233+
public func _add(_ source: _Source, forMode mode: RunLoop.Mode) {
234+
CFRunLoopAddSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown)
235+
}
236+
237+
@available(*, deprecated, message: "For XCTest use only.")
238+
open func _remove(_ source: _Source, for mode: RunLoop.Mode) {
239+
CFRunLoopRemoveSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown)
240+
}
217241
}
218-
#endif
219242

243+
extension RunLoop {
244+
@available(*, deprecated, message: "For XCTest use only.")
245+
public enum _Activity: UInt {
246+
// These must match CFRunLoopActivity.
247+
case entry = 0
248+
case beforeTimers = 1
249+
case beforeSources = 2
250+
case beforeWaiting = 32
251+
case afterWaiting = 64
252+
case exit = 128
253+
}
254+
255+
@available(*, deprecated, message: "For XCTest use only.")
256+
public struct _Activities: OptionSet {
257+
public var rawValue: UInt
258+
public init(rawValue: UInt) {
259+
self.rawValue = rawValue
260+
}
261+
262+
public init(_ activity: _Activity) {
263+
self.rawValue = activity.rawValue
264+
}
265+
266+
public static let entry = _Activities(rawValue: _Activity.entry.rawValue)
267+
public static let beforeTimers = _Activities(rawValue: _Activity.beforeTimers.rawValue)
268+
public static let beforeSources = _Activities(rawValue: _Activity.beforeSources.rawValue)
269+
public static let beforeWaiting = _Activities(rawValue: _Activity.beforeWaiting.rawValue)
270+
public static let afterWaiting = _Activities(rawValue: _Activity.afterWaiting.rawValue)
271+
public static let exit = _Activities(rawValue: _Activity.exit.rawValue)
272+
public static let allActivities = _Activities(rawValue: 0x0FFFFFFF)
273+
}
274+
275+
@available(*, deprecated, message: "For XCTest use only.")
276+
public class _Observer {
277+
fileprivate let cfObserver: CFRunLoopObserver
278+
279+
fileprivate init(activities: _Activities, repeats: Bool, order: Int, handler: @escaping (_Activity) -> Void) {
280+
self.cfObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorSystemDefault, CFOptionFlags(activities.rawValue), repeats, CFIndex(order), { (cfObserver, cfActivity) in
281+
guard let activity = _Activity(rawValue: UInt(cfActivity.rawValue)) else { return }
282+
handler(activity)
283+
})
284+
}
285+
286+
public func invalidate() {
287+
CFRunLoopObserverInvalidate(cfObserver)
288+
}
289+
290+
public var order: Int {
291+
Int(CFRunLoopObserverGetOrder(cfObserver))
292+
}
293+
294+
public var isValid: Bool {
295+
CFRunLoopObserverIsValid(cfObserver)
296+
}
297+
298+
deinit {
299+
invalidate()
300+
}
301+
}
302+
303+
@available(*, deprecated, message: "For XCTest use only.")
304+
open class _Source: NSObject {
305+
fileprivate var cfSource: CFRunLoopSource!
306+
307+
public init(order: Int = 0) {
308+
super.init()
309+
310+
var context = CFRunLoopSourceContext(
311+
version: 0,
312+
info: Unmanaged.passUnretained(self).toOpaque(),
313+
retain: nil,
314+
release: nil,
315+
copyDescription: { (info) -> Unmanaged<CFString>? in
316+
let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue()
317+
return .passRetained(String(describing: me)._cfObject)
318+
},
319+
equal: { (infoA, infoB) in
320+
let a = Unmanaged<_Source>.fromOpaque(infoA!).takeUnretainedValue()
321+
let b = Unmanaged<_Source>.fromOpaque(infoB!).takeUnretainedValue()
322+
return a == b ? true : false
323+
},
324+
hash: { (info) -> CFHashCode in
325+
let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue()
326+
return CFHashCode(me.hashValue)
327+
},
328+
schedule: { (info, cfRunLoop, cfRunLoopMode) in
329+
let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue()
330+
var mode: RunLoop.Mode = .default
331+
if let cfRunLoopMode = cfRunLoopMode {
332+
mode = RunLoop.Mode(rawValue: cfRunLoopMode._swiftObject)
333+
}
334+
335+
me.didSchedule(in: mode)
336+
},
337+
cancel: { (info, cfRunLoop, cfRunLoopMode) in
338+
let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue()
339+
var mode: RunLoop.Mode = .default
340+
if let cfRunLoopMode = cfRunLoopMode {
341+
mode = RunLoop.Mode(rawValue: cfRunLoopMode._swiftObject)
342+
}
343+
344+
me.didCancel(in: mode)
345+
},
346+
perform: { (info) in
347+
let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue()
348+
me.perform()
349+
})
350+
351+
self.cfSource = CFRunLoopSourceCreate(kCFAllocatorSystemDefault, CFIndex(order), &context)
352+
}
353+
354+
open func didSchedule(in mode: RunLoop.Mode) {
355+
// Override me.
356+
}
357+
358+
open func didCancel(in mode: RunLoop.Mode) {
359+
// Override me.
360+
}
361+
362+
open func perform() {
363+
// Override me.
364+
}
365+
366+
open func invalidate() {
367+
CFRunLoopSourceInvalidate(cfSource)
368+
}
369+
370+
open var order: Int {
371+
Int(CFRunLoopSourceGetOrder(cfSource))
372+
}
373+
374+
open var isValid: Bool {
375+
CFRunLoopSourceIsValid(cfSource)
376+
}
377+
378+
open func signal() {
379+
CFRunLoopSourceSignal(cfSource)
380+
}
381+
382+
deinit {
383+
invalidate()
384+
}
385+
}
386+
}

0 commit comments

Comments
 (0)