Skip to content

Commit 10dfaf5

Browse files
authored
Merge pull request #2469 from millenomi/runloop-port
Parity: RunLoop.add(Port…)/.remove(Port…)
2 parents 482a27a + 38c82a7 commit 10dfaf5

File tree

2 files changed

+132
-14
lines changed

2 files changed

+132
-14
lines changed

Foundation/RunLoop.swift

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,66 @@ open class RunLoop: NSObject {
8585
}
8686

8787
open func add(_ timer: Timer, forMode mode: RunLoop.Mode) {
88-
CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer._cfObject, mode._cfStringUniquingKnown)
88+
CFRunLoopAddTimer(_cfRunLoop, timer._cfObject, mode._cfStringUniquingKnown)
8989
}
9090

91+
private let monitoredPortsWithModesLock = NSLock() // guards:
92+
private var monitoredPortsWithModes: [Port: Set<RunLoop.Mode>] = [:]
93+
private var monitoredPortObservers: [Port: NSObjectProtocol] = [:]
94+
9195
open func add(_ aPort: Port, forMode mode: RunLoop.Mode) {
92-
NSUnimplemented()
96+
var shouldSchedule = false
97+
monitoredPortsWithModesLock.synchronized {
98+
if monitoredPortsWithModes[aPort]?.contains(mode) != true {
99+
monitoredPortsWithModes[aPort, default: []].insert(mode)
100+
101+
let shouldStartMonitoring = monitoredPortObservers[aPort] == nil
102+
if shouldStartMonitoring {
103+
monitoredPortObservers[aPort] = NotificationCenter.default.addObserver(forName: Port.didBecomeInvalidNotification, object: aPort, queue: nil, using: { [weak self] (notification) in
104+
self?.portDidInvalidate(aPort)
105+
})
106+
}
107+
108+
shouldSchedule = true
109+
}
110+
}
111+
112+
if shouldSchedule {
113+
aPort.schedule(in: self, forMode: mode)
114+
}
115+
}
116+
117+
private func portDidInvalidate(_ aPort: Port) {
118+
monitoredPortsWithModesLock.synchronized {
119+
if let observer = monitoredPortObservers.removeValue(forKey: aPort) {
120+
NotificationCenter.default.removeObserver(observer)
121+
}
122+
monitoredPortsWithModes.removeValue(forKey: aPort)
123+
}
93124
}
94125

95126
open func remove(_ aPort: Port, forMode mode: RunLoop.Mode) {
96-
NSUnimplemented()
127+
var shouldRemove = false
128+
monitoredPortsWithModesLock.synchronized {
129+
guard let modes = monitoredPortsWithModes[aPort], modes.contains(mode) else {
130+
return
131+
}
132+
133+
shouldRemove = true
134+
135+
if modes.count == 1 {
136+
if let observer = monitoredPortObservers.removeValue(forKey: aPort) {
137+
NotificationCenter.default.removeObserver(observer)
138+
}
139+
monitoredPortsWithModes.removeValue(forKey: aPort)
140+
} else {
141+
monitoredPortsWithModes[aPort]?.remove(mode)
142+
}
143+
}
144+
145+
if shouldRemove {
146+
aPort.remove(from: self, forMode: mode)
147+
}
97148
}
98149

99150
open func limitDate(forMode mode: RunLoop.Mode) -> Date? {

TestFoundation/TestRunLoop.swift

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,6 @@
88
//
99

1010
class TestRunLoop : XCTestCase {
11-
static var allTests : [(String, (TestRunLoop) -> () throws -> Void)] {
12-
return [
13-
("test_constants", test_constants),
14-
("test_runLoopInit", test_runLoopInit),
15-
("test_commonModes", test_commonModes),
16-
// these tests do not work the same as Darwin https://bugs.swift.org/browse/SR-399
17-
// ("test_runLoopRunMode", test_runLoopRunMode),
18-
// ("test_runLoopLimitDate", test_runLoopLimitDate),
19-
]
20-
}
21-
2211
func test_constants() {
2312
XCTAssertEqual(RunLoop.Mode.common.rawValue, "kCFRunLoopCommonModes",
2413
"\(RunLoop.Mode.common.rawValue) is not equal to kCFRunLoopCommonModes")
@@ -92,4 +81,82 @@ class TestRunLoop : XCTestCase {
9281

9382
waitForExpectations(timeout: 10)
9483
}
84+
85+
func test_addingRemovingPorts() {
86+
let runLoop = RunLoop.current
87+
var didDeallocate = false
88+
89+
do {
90+
let port = TestPort {
91+
didDeallocate = true
92+
}
93+
let customMode = RunLoop.Mode(rawValue: "Custom")
94+
95+
XCTAssertEqual(port.scheduledModes, [])
96+
97+
runLoop.add(port, forMode: .default)
98+
XCTAssertEqual(port.scheduledModes, [.default])
99+
100+
runLoop.add(port, forMode: .default)
101+
XCTAssertEqual(port.scheduledModes, [.default])
102+
103+
runLoop.add(port, forMode: customMode)
104+
XCTAssertEqual(port.scheduledModes, [.default, customMode])
105+
106+
runLoop.remove(port, forMode: customMode)
107+
XCTAssertEqual(port.scheduledModes, [.default])
108+
109+
runLoop.add(port, forMode: customMode)
110+
XCTAssertEqual(port.scheduledModes, [.default, customMode])
111+
112+
port.invalidate()
113+
}
114+
115+
XCTAssertTrue(didDeallocate)
116+
}
117+
118+
static var allTests : [(String, (TestRunLoop) -> () throws -> Void)] {
119+
return [
120+
("test_constants", test_constants),
121+
("test_runLoopInit", test_runLoopInit),
122+
("test_commonModes", test_commonModes),
123+
// these tests do not work the same as Darwin https://bugs.swift.org/browse/SR-399
124+
// ("test_runLoopRunMode", test_runLoopRunMode),
125+
// ("test_runLoopLimitDate", test_runLoopLimitDate),
126+
("test_addingRemovingPorts", test_addingRemovingPorts),
127+
]
128+
}
129+
}
130+
131+
class TestPort: Port {
132+
let sentinel: () -> Void
133+
init(sentinel: @escaping () -> Void) {
134+
self.sentinel = sentinel
135+
super.init()
136+
}
137+
138+
deinit {
139+
invalidate()
140+
sentinel()
141+
}
142+
143+
private var _isValid = true
144+
open override var isValid: Bool { return _isValid }
145+
146+
open override func invalidate() {
147+
guard isValid else { return }
148+
149+
_isValid = false
150+
NotificationCenter.default.post(name: Port.didBecomeInvalidNotification, object: self)
151+
}
152+
153+
var scheduledModes: [RunLoop.Mode] = []
154+
155+
open override func schedule(in runLoop: RunLoop, forMode mode: RunLoop.Mode) {
156+
scheduledModes.append(mode)
157+
}
158+
159+
open override func remove(from runLoop: RunLoop, forMode mode: RunLoop.Mode) {
160+
scheduledModes = scheduledModes.filter { $0 != mode }
161+
}
95162
}

0 commit comments

Comments
 (0)