Skip to content

Commit 2446d78

Browse files
committed
RunLoops should be a single instance per their scope
NSRunLoop.mainRunLoop() should return a single object and it should be the same as NSRunLoop. currentRunLoop() and subsequent calls of both should not create new run loops
1 parent 176e8c5 commit 2446d78

File tree

5 files changed

+39
-6
lines changed

5 files changed

+39
-6
lines changed

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ struct _NSXMLParserBridge {
177177
const unsigned char *SystemID);
178178
};
179179

180+
struct _NSRunLoop {
181+
CFTypeRef (*_new)(CFRunLoopRef rl);
182+
};
183+
180184
struct _CFSwiftBridge {
181185
struct _NSObjectBridge NSObject;
182186
struct _NSArrayBridge NSArray;
@@ -188,6 +192,7 @@ struct _CFSwiftBridge {
188192
struct _NSStringBridge NSString;
189193
struct _NSMutableStringBridge NSMutableString;
190194
struct _NSXMLParserBridge NSXMLParser;
195+
struct _NSRunLoop NSRunLoop;
191196
};
192197

193198
__attribute__((__visibility__("hidden"))) extern struct _CFSwiftBridge __CFSwiftBridge;
@@ -264,6 +269,8 @@ extern void *_CFReallocf(void *ptr, size_t size);
264269

265270
CFHashCode CFStringHashNSString(CFStringRef str);
266271

272+
extern CFTypeRef _CFRunLoopGet2(CFRunLoopRef rl);
273+
267274
extern CFIndex __CFStringEncodeByteStream(CFStringRef string, CFIndex rangeLoc, CFIndex rangeLen, Boolean generatingExternalFile, CFStringEncoding encoding, uint8_t lossByte, UInt8 * _Nullable buffer, CFIndex max, CFIndex * _Nullable usedBufLen);
268275

269276
typedef unsigned char __cf_uuid[16];

CoreFoundation/RunLoop.subproj/CFRunLoop.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,7 +1526,12 @@ CF_PRIVATE void __CFFinalizeRunLoop(uintptr_t data) {
15261526
}
15271527
if (rl && CFRunLoopGetMain() != rl) { // protect against cooperative threads
15281528
if (NULL != rl->_counterpart) {
1529+
#if DEPLOYMENT_RUNTIME_SWIFT
1530+
extern void swift_release(void *);
1531+
swift_release((void *)rl->_counterpart);
1532+
#else
15291533
CFRelease(rl->_counterpart);
1534+
#endif
15301535
rl->_counterpart = NULL;
15311536
}
15321537
// purge all sources before deallocation
@@ -1549,6 +1554,12 @@ pthread_t _CFRunLoopGet1(CFRunLoopRef rl) {
15491554
CF_EXPORT CFTypeRef _CFRunLoopGet2(CFRunLoopRef rl) {
15501555
CFTypeRef ret = NULL;
15511556
__CFLock(&loopsLock);
1557+
#if DEPLOYMENT_RUNTIME_SWIFT
1558+
if (rl->_counterpart == NULL) {
1559+
CFTypeRef ns = __CFSwiftBridge.NSRunLoop._new(rl); // returns retained so we will claim ownership of that return value by just assigning (the release is balanced in the destruction of the CFRunLoop
1560+
rl->_counterpart = ns;
1561+
}
1562+
#endif
15521563
ret = rl->_counterpart;
15531564
__CFUnlock(&loopsLock);
15541565
return ret;

Foundation/NSRunLoop.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,22 +12,24 @@ import CoreFoundation
1212
public let NSDefaultRunLoopMode: String = kCFRunLoopDefaultMode._swiftObject
1313
public let NSRunLoopCommonModes: String = kCFRunLoopCommonModes._swiftObject
1414

15+
internal func _NSRunLoopNew(cf: CFRunLoopRef) -> Unmanaged<AnyObject>! {
16+
let rl = Unmanaged<NSRunLoop>.passRetained(NSRunLoop(cfObject: cf))
17+
return unsafeBitCast(rl, Unmanaged<AnyObject>.self) // this retain is balanced on the other side of the CF fence
18+
}
19+
1520
public class NSRunLoop : NSObject {
1621
internal var _cfRunLoop : CFRunLoopRef!
17-
internal static var _mainRunLoop : NSRunLoop = {
18-
return NSRunLoop(cfObject: CFRunLoopGetMain())
19-
}()
2022

2123
internal init(cfObject : CFRunLoopRef) {
2224
_cfRunLoop = cfObject
2325
}
2426

2527
public class func currentRunLoop() -> NSRunLoop {
26-
return NSRunLoop(cfObject: CFRunLoopGetCurrent())
28+
return _CFRunLoopGet2(CFRunLoopGetCurrent()) as! NSRunLoop
2729
}
2830

2931
public class func mainRunLoop() -> NSRunLoop {
30-
return _mainRunLoop
32+
return _CFRunLoopGet2(CFRunLoopGetMain()) as! NSRunLoop
3133
}
3234

3335
public var currentMode: String? {

Foundation/NSSwiftRuntime.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@ internal func __CFInitializeSwift() {
199199
__CFSwiftBridge.NSXMLParser.comment = _NSXMLParserComment
200200
__CFSwiftBridge.NSXMLParser.externalSubset = _NSXMLParserExternalSubset
201201

202+
__CFSwiftBridge.NSRunLoop._new = _NSRunLoopNew
203+
202204
__CFDefaultEightBitStringEncoding = UInt32(kCFStringEncodingUTF8)
203205
}
204206

TestFoundation/TestNSRunLoop.swift

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,18 @@ class TestNSRunLoop : XCTestCase {
3131
XCTAssertNotNil(mainRunLoop)
3232
let currentRunLoop = NSRunLoop.currentRunLoop()
3333
XCTAssertNotNil(currentRunLoop)
34+
35+
let secondAccessOfMainLoop = NSRunLoop.mainRunLoop()
36+
XCTAssertEqual(mainRunLoop, secondAccessOfMainLoop, "fetching the main loop a second time should be equal")
37+
XCTAssertTrue(mainRunLoop === secondAccessOfMainLoop, "fetching the main loop a second time should be identical")
38+
39+
let secondAccessOfCurrentLoop = NSRunLoop.currentRunLoop()
40+
XCTAssertEqual(currentRunLoop, secondAccessOfCurrentLoop, "fetching the current loop a second time should be equal")
41+
XCTAssertTrue(currentRunLoop === secondAccessOfCurrentLoop, "fetching the current loop a second time should be identical")
42+
43+
// We can assume that the tests will be run on the main run loop
44+
// so the current loop should be the main loop
45+
XCTAssertEqual(mainRunLoop, currentRunLoop, "the current run loop should be the main loop")
3446
}
3547

3648
func test_runLoopRunMode() {
@@ -70,5 +82,4 @@ class TestNSRunLoop : XCTestCase {
7082

7183
XCTAssertLessThan(abs(timerTickInterval - expectedTimeInterval), 0.01)
7284
}
73-
7485
}

0 commit comments

Comments
 (0)