From 3ff76f04ec7c9a0263112530c1b517539ba632ab Mon Sep 17 00:00:00 2001 From: Lily Vulcano Date: Wed, 27 May 2020 10:07:47 -0700 Subject: [PATCH 1/6] Add SPI for sources and observers to RunLoop These are for XCTest use only for now. --- Sources/Foundation/RunLoop.swift | 165 +++++++++++++++++++++++++++++-- 1 file changed, 159 insertions(+), 6 deletions(-) diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index ca6bf1c254..2bf13f6934 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -208,12 +208,165 @@ extension RunLoop { } } -// SPI for XCTest -#if os(Windows) +// These exist as SPI for XCTest for now. Do not rely on their contracts or continued existence. + extension RunLoop { - public func _stop() { - CFRunLoopStop(getCFRunLoop()) - } + @available(*, deprecated, message: "For XCTest use only.") + public func _stop() { + CFRunLoopStop(getCFRunLoop()) + } + + @available(*, deprecated, message: "For XCTest use only.") + public func _observe(_ activities: _Activities, in mode: RunLoop.Mode = .default, repeats: Bool = true, order: Int = 0, handler: @escaping (_Activity) -> Void) -> _Observer { + let observer = _Observer(activities: activities, repeats: repeats, order: order, handler: handler) + CFRunLoopAddObserver(self.getCFRunLoop(), observer.cfObserver, mode._cfStringUniquingKnown) + return observer + } + + @available(*, deprecated, message: "For XCTest use only.") + public func _observe(_ activity: _Activity, in mode: RunLoop.Mode = .default, repeats: Bool = true, order: Int = 0, handler: @escaping (_Activity) -> Void) -> _Observer { + return _observe(_Activities(activity), in: mode, repeats: repeats, order: order, handler: handler) + } + + @available(*, deprecated, message: "For XCTest use only.") + func _add(_ source: _Source, forMode mode: RunLoop.Mode) { + CFRunLoopAddSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown) + } } -#endif +extension RunLoop { + @available(*, deprecated, message: "For XCTest use only.") + public enum _Activity: UInt { + // These must match CFRunLoopActivity. + case entry = 0 + case beforeTimers = 1 + case beforeSources = 2 + case beforeWaiting = 32 + case afterWaiting = 64 + case exit = 128 + } + + @available(*, deprecated, message: "For XCTest use only.") + public struct _Activities: OptionSet { + public var rawValue: UInt + public init(rawValue: UInt) { + self.rawValue = rawValue + } + + public init(_ activity: _Activity) { + self.rawValue = activity.rawValue + } + + public static let entry = _Activities(rawValue: _Activity.entry.rawValue) + public static let beforeTimers = _Activities(rawValue: _Activity.beforeTimers.rawValue) + public static let beforeSources = _Activities(rawValue: _Activity.beforeSources.rawValue) + public static let beforeWaiting = _Activities(rawValue: _Activity.beforeWaiting.rawValue) + public static let afterWaiting = _Activities(rawValue: _Activity.afterWaiting.rawValue) + public static let exit = _Activities(rawValue: _Activity.exit.rawValue) + public static let allActivities = _Activities(rawValue: 0x0FFFFFFF) + } + + @available(*, deprecated, message: "For XCTest use only.") + public class _Observer { + fileprivate let cfObserver: CFRunLoopObserver + + fileprivate init(activities: _Activities, repeats: Bool, order: Int, handler: @escaping (_Activity) -> Void) { + self.cfObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorSystemDefault, CFOptionFlags(activities.rawValue), repeats, CFIndex(order), { (cfObserver, cfActivity) in + guard let activity = _Activity(rawValue: UInt(cfActivity.rawValue)) else { return } + handler(activity) + }) + } + + public func invalidate() { + CFRunLoopObserverInvalidate(cfObserver) + } + + var order: Int { + Int(CFRunLoopObserverGetOrder(cfObserver)) + } + + var isValid: Bool { + CFRunLoopObserverIsValid(cfObserver) + } + } + + @available(*, deprecated, message: "For XCTest use only.") + open class _Source: NSObject { + fileprivate var cfSource: CFRunLoopSource! + + public init(order: Int = 0) { + super.init() + + var context = CFRunLoopSourceContext( + version: 0, + info: Unmanaged.passUnretained(self).toOpaque(), + retain: nil, + release: nil, + copyDescription: { (info) -> Unmanaged? in + let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue() + return .passRetained(String(describing: me)._cfObject) + }, + equal: { (infoA, infoB) -> DarwinBoolean in + let a = Unmanaged<_Source>.fromOpaque(infoA!).takeUnretainedValue() + let b = Unmanaged<_Source>.fromOpaque(infoB!).takeUnretainedValue() + return a == b ? true : false + }, + hash: { (info) -> CFHashCode in + let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue() + return CFHashCode(me.hashValue) + }, + schedule: { (info, cfRunLoop, cfRunLoopMode) in + let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue() + var mode: RunLoop.Mode = .default + if let cfRunLoopMode = cfRunLoopMode { + mode = RunLoop.Mode(rawValue: cfRunLoopMode._swiftObject) + } + + me.didSchedule(in: mode) + }, + cancel: { (info, cfRunLoop, cfRunLoopMode) in + let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue() + var mode: RunLoop.Mode = .default + if let cfRunLoopMode = cfRunLoopMode { + mode = RunLoop.Mode(rawValue: cfRunLoopMode._swiftObject) + } + + me.didCancel(in: mode) + }, + perform: { (info) in + let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue() + me.perform() + }) + + self.cfSource = CFRunLoopSourceCreate(kCFAllocatorSystemDefault, CFIndex(order), &context) + } + + open func didSchedule(in mode: RunLoop.Mode) { + // Override me. + } + + open func didCancel(in mode: RunLoop.Mode) { + // Override me. + } + + open func perform() { + // Override me. + } + + open func invalidate() { + CFRunLoopSourceInvalidate(cfSource) + } + + var order: Int { + Int(CFRunLoopSourceGetOrder(cfSource)) + } + + var isValid: Bool { + CFRunLoopSourceIsValid(cfSource) + } + + open func signal() { + CFRunLoopSourceSignal(cfSource) + } + } +} From f0126a31e1d2099b852265a5868a85abd026f72f Mon Sep 17 00:00:00 2001 From: Lily Vulcano Date: Wed, 27 May 2020 10:12:06 -0700 Subject: [PATCH 2/6] Corrections: - Members should match the publicness/openness of their containers - Add a warning to getCFRunLoop() to warn porters that it's not going to be around forever. --- Sources/Foundation/RunLoop.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index 2bf13f6934..1c0ee52124 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -78,6 +78,7 @@ open class RunLoop: NSObject { } } + @available(*, deprecated, message: "Directly accessing the run loop may cause your code to not become portable in the future.") open func getCFRunLoop() -> CFRunLoop { return _cfRunLoop } @@ -229,7 +230,7 @@ extension RunLoop { } @available(*, deprecated, message: "For XCTest use only.") - func _add(_ source: _Source, forMode mode: RunLoop.Mode) { + public func _add(_ source: _Source, forMode mode: RunLoop.Mode) { CFRunLoopAddSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown) } } @@ -281,11 +282,11 @@ extension RunLoop { CFRunLoopObserverInvalidate(cfObserver) } - var order: Int { + public var order: Int { Int(CFRunLoopObserverGetOrder(cfObserver)) } - var isValid: Bool { + public var isValid: Bool { CFRunLoopObserverIsValid(cfObserver) } } @@ -357,11 +358,11 @@ extension RunLoop { CFRunLoopSourceInvalidate(cfSource) } - var order: Int { + open var order: Int { Int(CFRunLoopSourceGetOrder(cfSource)) } - var isValid: Bool { + open var isValid: Bool { CFRunLoopSourceIsValid(cfSource) } From a8cd65cf1330219bfb095a2b42658e84cd11c9ea Mon Sep 17 00:00:00 2001 From: Lily Vulcano Date: Wed, 27 May 2020 10:14:58 -0700 Subject: [PATCH 3/6] For completeness, add _remove(_Source, for: Mode) --- Sources/Foundation/RunLoop.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index 1c0ee52124..9761bfdc75 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -233,6 +233,11 @@ extension RunLoop { public func _add(_ source: _Source, forMode mode: RunLoop.Mode) { CFRunLoopAddSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown) } + + @available(*, deprecated, message: "For XCTest use only.") + open func remove(_ source: _Source, for mode: RunLoop.Mode) { + CFRunLoopRemoveSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown) + } } extension RunLoop { From 840aac40abc1936459c8be2503f0c1b3b7823172 Mon Sep 17 00:00:00 2001 From: Lily Vulcano Date: Wed, 27 May 2020 14:06:33 -0700 Subject: [PATCH 4/6] =?UTF-8?q?Underscore=20=5Fremove(=E2=80=A6)=20to=20av?= =?UTF-8?q?oid=20polluting=20our=20potential=20API=20surface.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/Foundation/RunLoop.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index 9761bfdc75..ae5b293fbf 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -235,7 +235,7 @@ extension RunLoop { } @available(*, deprecated, message: "For XCTest use only.") - open func remove(_ source: _Source, for mode: RunLoop.Mode) { + open func _remove(_ source: _Source, for mode: RunLoop.Mode) { CFRunLoopRemoveSource(_cfRunLoop, source.cfSource, mode._cfStringUniquingKnown) } } From b202a8d584f1fc420cccaf407c7f67ea147c0019 Mon Sep 17 00:00:00 2001 From: Lily Vulcano Date: Wed, 27 May 2020 14:11:04 -0700 Subject: [PATCH 5/6] Invalidate CF sources and observers as their wrappers deallocate. --- Sources/Foundation/RunLoop.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index ae5b293fbf..d9ac5967f5 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -294,6 +294,10 @@ extension RunLoop { public var isValid: Bool { CFRunLoopObserverIsValid(cfObserver) } + + deinit { + invalidate() + } } @available(*, deprecated, message: "For XCTest use only.") @@ -374,5 +378,9 @@ extension RunLoop { open func signal() { CFRunLoopSourceSignal(cfSource) } + + deinit { + invalidate() + } } } From 3062f14dc57e2cedaa37065cd27a1b8a48189e5d Mon Sep 17 00:00:00 2001 From: Aura Lily Vulcano Date: Thu, 28 May 2020 09:23:55 -0700 Subject: [PATCH 6/6] Do not mention DarwinBoolean --- Sources/Foundation/RunLoop.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Foundation/RunLoop.swift b/Sources/Foundation/RunLoop.swift index d9ac5967f5..05cfa14946 100644 --- a/Sources/Foundation/RunLoop.swift +++ b/Sources/Foundation/RunLoop.swift @@ -316,7 +316,7 @@ extension RunLoop { let me = Unmanaged<_Source>.fromOpaque(info!).takeUnretainedValue() return .passRetained(String(describing: me)._cfObject) }, - equal: { (infoA, infoB) -> DarwinBoolean in + equal: { (infoA, infoB) in let a = Unmanaged<_Source>.fromOpaque(infoA!).takeUnretainedValue() let b = Unmanaged<_Source>.fromOpaque(infoB!).takeUnretainedValue() return a == b ? true : false