Last active
June 14, 2022 11:39
-
-
Save ollieatkinson/1261ceb2fb873c5c3a6a494553864177 to your computer and use it in GitHub Desktop.
Making NotificationCenter easier to work with and allowing type-safe rules
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension NotificationCenter { | |
public func observe( | |
name: NSNotification.Name, | |
object obj: Any? = nil, | |
queue: OperationQueue? = .main, | |
using block: @escaping (Notification?) -> Void | |
) -> Notification.Token { | |
return Notification.Token( | |
on: self, | |
token: addObserver(forName: name, object: obj, queue: queue, using: block) | |
) | |
} | |
} | |
extension NotificationCenter { | |
public func observe<T>( | |
_ descriptor: Notification.Descriptor<T>, | |
object obj: Any? = nil, | |
queue: OperationQueue? = .main, | |
using block: @escaping (T) -> () | |
) -> Notification.Token { | |
return Notification.Token( | |
on: self, | |
token: addObserver(for: descriptor, object: obj, queue: queue, using: block) | |
) | |
} | |
public func addObserver<T>( | |
for descriptor: Notification.Descriptor<T>, | |
object obj: Any? = nil, | |
queue: OperationQueue? = .main, | |
using block: @escaping (T) -> () | |
) -> NSObjectProtocol { | |
return addObserver(forName: descriptor.name, object: obj, queue: queue, using: { n in | |
block(descriptor.transform(n)) | |
}) | |
} | |
} | |
extension Notification { | |
public final class Token: NSObject { | |
let notificationCenter: NotificationCenter | |
let token: NSObjectProtocol | |
init(on notificationCenter: NotificationCenter = .default, token: NSObjectProtocol) { | |
self.notificationCenter = notificationCenter | |
self.token = token | |
} | |
deinit { | |
notificationCenter.removeObserver(token) | |
} | |
} | |
public struct Descriptor<T> { | |
public typealias ID = UInt | |
private(set) public var id: ID = { __count &+= 1; return __count }() | |
public let name: Notification.Name | |
public let transform: (Notification) -> T | |
public init(name: Notification.Name, transform: @escaping (Notification) -> T) { | |
self.name = name | |
self.transform = transform | |
} | |
} | |
} | |
private var __count: UInt = 0 | |
extension Notification.Descriptor: Hashable { | |
public func hash(into hasher: inout Hasher) { | |
hasher.combine(name) | |
} | |
public static func == (l: Notification.Descriptor<T>, r: Notification.Descriptor<T>) -> Bool { | |
l.name == r.name && l.id == r.id | |
} | |
} | |
extension Notification.Descriptor { | |
public func sink<Root>(center: NotificationCenter = .default, queue: OperationQueue? = nil, to method: @escaping (Root) -> (T) -> Void, on root: Root) -> Notification.Token where Root: AnyObject { | |
sink(center: center, queue: queue, to: { [weak root] o in root.map(method)?(o) }) | |
} | |
public func sink(center: NotificationCenter = .default, queue: OperationQueue? = nil, to method: @escaping (T) -> Void) -> Notification.Token { | |
center.observe(self, object: nil, queue: queue, using: method) | |
} | |
} | |
extension Notification.Name { | |
public func sink<Root>(center: NotificationCenter = .default, queue: OperationQueue? = nil, to method: @escaping (Root) -> () -> Void, on root: Root) -> Notification.Token where Root: AnyObject { | |
sink(center: center, queue: queue, to: { [weak root] _ in root.map(method)?() }) | |
} | |
public func sink(center: NotificationCenter = .default, queue: OperationQueue? = nil, to method: @escaping () -> Void) -> Notification.Token { | |
center.observe(name: self, object: nil, queue: queue, using: { _ in method() }) | |
} | |
public func sink<Root>(center: NotificationCenter = .default, queue: OperationQueue? = nil, to method: @escaping (Root) -> (Notification?) -> Void, on root: Root) -> Notification.Token where Root: AnyObject { | |
sink(center: center, queue: queue, to: { [weak root] note in root.map(method)?(note) }) | |
} | |
public func sink(center: NotificationCenter = .default, queue: OperationQueue? = nil, to method: @escaping (Notification?) -> Void) -> Notification.Token { | |
center.observe(name: self, object: nil, queue: queue, using: method) | |
} | |
} | |
// AnyToken | |
public final class AnyObservationToken: NSObject { | |
let wrapped: NSObject | |
public init(_ wrapped: NSObject) { | |
self.wrapped = wrapped | |
} | |
} | |
protocol ExpressibleAsAnyObservationToken: NSObject { | |
func store(in set: inout Set<AnyObservationToken>) | |
func store<C>(in collection: inout C) where C: RangeReplaceableCollection, C.Element == AnyObservationToken | |
func eraseToAnyObservationToken() -> AnyObservationToken | |
} | |
extension ExpressibleAsAnyObservationToken { | |
public func store(in set: inout Set<AnyObservationToken>) { | |
set.insert(eraseToAnyObservationToken()) | |
} | |
public func store<C>(in collection: inout C) where C : RangeReplaceableCollection, C.Element == AnyObservationToken { | |
collection.append(eraseToAnyObservationToken()) | |
} | |
public func eraseToAnyObservationToken() -> AnyObservationToken { .init(self) } | |
} | |
extension AnyObservationToken { | |
public func store(in set: inout Set<AnyObservationToken>) { | |
set.insert(self) | |
} | |
} | |
extension NSKeyValueObservation: ExpressibleAsAnyObservationToken { } | |
extension Notification.Token: ExpressibleAsAnyObservationToken { } | |
// KeyPath + Unwrap | |
extension KeyPath { | |
public func tryUnwrap<T>(_ as: T.Type = T.self) -> (Root) throws -> T { | |
return { try $0[keyPath: self] as? T ??^ "\(self) is not type \(T.self)" } | |
} | |
public func optionalUnwrap<T>(_ as: T.Type = T.self) -> (Root) -> T? { | |
return { $0[keyPath: self] as? T } | |
} | |
public func unwrap<T>(_ as: T.Type = T.self) -> (Root) -> T { | |
return { $0[keyPath: self] as! T } | |
} | |
} | |
// Unwrap or throw | |
extension String: Error { } | |
extension Optional { | |
public static func ??^ (optional: Optional, error: Error) throws -> Wrapped { | |
guard case let value? = optional else { throw error } | |
return value | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extension UIWindow { | |
public static let didBecomeVisibleNotificationDescriptor: Notification.Descriptor<UIWindow> = .init(name: UIWindow.didBecomeVisibleNotification, transform: (\Notification.object).unwrap()) | |
public static let didBecomeHiddenNotificationDescriptor: Notification.Descriptor<UIWindow> = .init(name: UIWindow.didBecomeHiddenNotification, transform: (\Notification.object).unwrap()) | |
public static let didBecomeKeyNotificationDescriptor: Notification.Descriptor<UIWindow> = .init(name: UIWindow.didBecomeKeyNotification, transform: (\Notification.object).unwrap()) | |
public static let didResignKeyNotificationDescriptor: Notification.Descriptor<UIWindow> = .init(name: UIWindow.didResignKeyNotification, transform: (\Notification.object).unwrap()) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
e.g.
or grabbing properties from the notification