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 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
#if canImport(Dispatch)
@_implementationOnly import CoreFoundation
extension NotificationQueue {
public enum PostingStyle : UInt, Sendable {
case whenIdle = 1
case asap = 2
case now = 3
}
public struct NotificationCoalescing : OptionSet, Sendable {
public let rawValue : UInt
public init(rawValue: UInt) { self.rawValue = rawValue }
public static let none = NotificationCoalescing([])
public static let onName = NotificationCoalescing(rawValue: 1 << 0)
public static let onSender = NotificationCoalescing(rawValue: 1 << 1)
}
}
@available(*, unavailable)
extension NotificationQueue : @unchecked Sendable { }
open class NotificationQueue: NSObject {
internal typealias NotificationQueueList = NSMutableArray
internal typealias NSNotificationListEntry = (Notification, [RunLoop.Mode]) // Notification ans list of modes the notification may be posted in.
internal typealias NSNotificationList = [NSNotificationListEntry] // The list of notifications to post
internal let notificationCenter: NotificationCenter
internal var asapList = NSNotificationList()
internal var idleList = NSNotificationList()
internal final lazy var idleRunloopObserver: CFRunLoopObserver = {
return CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFOptionFlags(kCFRunLoopBeforeTimers), true, 0) {[weak self] observer, activity in
self!.notifyQueues(.whenIdle)
}
}()
internal final lazy var asapRunloopObserver: CFRunLoopObserver = {
return CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFOptionFlags(kCFRunLoopBeforeWaiting | kCFRunLoopExit), true, 0) {[weak self] observer, activity in
self!.notifyQueues(.asap)
}
}()
// The NSNotificationQueue instance is associated with current thread.
// The _notificationQueueList represents a list of notification queues related to the current thread.
private static nonisolated(unsafe) var _notificationQueueList = NSThreadSpecific<NSMutableArray>()
internal static var notificationQueueList: NotificationQueueList {
return _notificationQueueList.get() {
return NSMutableArray()
}
}
// The default notification queue for the current thread.
private static nonisolated(unsafe) var _defaultQueue = NSThreadSpecific<NotificationQueue>()
open class var `default`: NotificationQueue {
return _defaultQueue.get() {
return NotificationQueue(notificationCenter: NotificationCenter.default)
}
}
public init(notificationCenter: NotificationCenter) {
self.notificationCenter = notificationCenter
super.init()
NotificationQueue.registerQueue(self)
}
deinit {
NotificationQueue.unregisterQueue(self)
removeRunloopObserver(self.idleRunloopObserver)
removeRunloopObserver(self.asapRunloopObserver)
}
open func enqueue(_ notification: Notification, postingStyle: PostingStyle) {
enqueue(notification, postingStyle: postingStyle, coalesceMask: [.onName, .onSender], forModes: nil)
}
open func enqueue(_ notification: Notification, postingStyle: PostingStyle, coalesceMask: NotificationCoalescing, forModes modes: [RunLoop.Mode]?) {
var runloopModes: [RunLoop.Mode] = [.default]
if let modes = modes {
runloopModes = modes
}
if !coalesceMask.isEmpty {
self.dequeueNotifications(matching: notification, coalesceMask: coalesceMask)
}
switch postingStyle {
case .now:
let currentMode = RunLoop.current.currentMode
if currentMode == nil || runloopModes.contains(currentMode!) {
self.notificationCenter.post(notification)
}
case .asap: // post at the end of the current notification callout or timer
addRunloopObserver(self.asapRunloopObserver)
self.asapList.append((notification, runloopModes))
case .whenIdle: // wait until the runloop is idle, then post the notification
addRunloopObserver(self.idleRunloopObserver)
self.idleList.append((notification, runloopModes))
}
}
open func dequeueNotifications(matching notification: Notification, coalesceMask: NotificationCoalescing) {
var predicate: (NSNotificationListEntry) -> Bool
switch coalesceMask {
case [.onName, .onSender]:
predicate = { entry in
return __SwiftValue.store(notification.object) !== __SwiftValue.store(entry.0.object) || notification.name != entry.0.name
}
case [.onName]:
predicate = { entry in
return notification.name != entry.0.name
}
case [.onSender]:
predicate = { entry in
return __SwiftValue.store(notification.object) !== __SwiftValue.store(entry.0.object)
}
default:
return
}
self.asapList = self.asapList.filter(predicate)
self.idleList = self.idleList.filter(predicate)
}
// MARK: Private
private final func addRunloopObserver(_ observer: CFRunLoopObserver) {
CFRunLoopAddObserver(RunLoop.current._cfRunLoop, observer, kCFRunLoopDefaultMode)
CFRunLoopAddObserver(RunLoop.current._cfRunLoop, observer, kCFRunLoopCommonModes)
}
private final func removeRunloopObserver(_ observer: CFRunLoopObserver) {
CFRunLoopRemoveObserver(RunLoop.current._cfRunLoop, observer, kCFRunLoopDefaultMode)
CFRunLoopRemoveObserver(RunLoop.current._cfRunLoop, observer, kCFRunLoopCommonModes)
}
private func notify(_ currentMode: RunLoop.Mode?, notificationList: inout NSNotificationList) {
for (idx, (notification, modes)) in notificationList.enumerated().reversed() {
if currentMode == nil || modes.contains(currentMode!) {
self.notificationCenter.post(notification)
notificationList.remove(at: idx)
}
}
}
/**
Gets queues from the notificationQueueList and posts all notification from the list related to the postingStyle parameter.
*/
private func notifyQueues(_ postingStyle: PostingStyle) {
let currentMode = RunLoop.current.currentMode
for queue in NotificationQueue.notificationQueueList {
let notificationQueue = queue as! NotificationQueue
if postingStyle == .whenIdle {
notificationQueue.notify(currentMode, notificationList: ¬ificationQueue.idleList)
} else {
notificationQueue.notify(currentMode, notificationList: ¬ificationQueue.asapList)
}
}
}
private static func registerQueue(_ notificationQueue: NotificationQueue) {
self.notificationQueueList.add(notificationQueue)
}
private static func unregisterQueue(_ notificationQueue: NotificationQueue) {
guard self.notificationQueueList.index(of: notificationQueue) != NSNotFound else {
return
}
self.notificationQueueList.remove(notificationQueue)
}
}
#endif
|