-
Notifications
You must be signed in to change notification settings - Fork 175
Expose observe overloads for separate tracking and application of changes
#286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 13 commits
a8f3be2
5df4eb9
c058a10
a1573d6
ef2926b
684393d
c37705e
189e51a
dd42f44
03a0238
cfd5b88
994a415
5937275
f105a85
52c254a
07b5cdb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||
| #if canImport(ObjectiveC) | ||||||
| import Dispatch | ||||||
| import ObjectiveC | ||||||
| import ConcurrencyExtras | ||||||
|
|
||||||
| @MainActor | ||||||
| extension NSObject { | ||||||
|
|
@@ -11,7 +12,7 @@ | |||||
| /// any accessed fields so that the view is always up-to-date. | ||||||
| /// | ||||||
| /// It is most useful when dealing with non-SwiftUI views, such as UIKit views and controller. | ||||||
| /// You can invoke the ``observe(_:)`` method a single time in the `viewDidLoad` and update all | ||||||
| /// You can invoke the ``observe(_:)-(()->Void)`` method a single time in the `viewDidLoad` and update all | ||||||
| /// the view elements: | ||||||
| /// | ||||||
| /// ```swift | ||||||
|
|
@@ -37,7 +38,7 @@ | |||||
| /// ever mutated, this trailing closure will be called again, allowing us to update the view | ||||||
| /// again. | ||||||
| /// | ||||||
| /// Generally speaking you can usually have a single ``observe(_:)`` in the entry point of your | ||||||
| /// Generally speaking you can usually have a single ``observe(_:)-(()->Void)`` in the entry point of your | ||||||
| /// view, such as `viewDidLoad` for `UIViewController`. This works even if you have many UI | ||||||
| /// components to update: | ||||||
| /// | ||||||
|
|
@@ -64,7 +65,7 @@ | |||||
| /// a label or the `isHidden` of a button. | ||||||
| /// | ||||||
| /// However, if there is heavy work you need to perform when state changes, then it is best to | ||||||
| /// put that in its own ``observe(_:)``. For example, if you needed to reload a table view or | ||||||
| /// put that in its own ``observe(_:)-(()->Void)``. For example, if you needed to reload a table view or | ||||||
| /// collection view when a collection changes: | ||||||
| /// | ||||||
| /// ```swift | ||||||
|
|
@@ -106,13 +107,77 @@ | |||||
| /// of a property changes. | ||||||
| /// - Returns: A cancellation token. | ||||||
| @discardableResult | ||||||
| public func observe(_ apply: @escaping @MainActor @Sendable () -> Void) -> ObserveToken { | ||||||
| public func observe( | ||||||
| _ apply: @escaping @MainActor @Sendable () -> Void | ||||||
| ) -> ObserveToken { | ||||||
| observe { _ in apply() } | ||||||
| } | ||||||
|
|
||||||
| /// Observe access to properties of an observable (or perceptible) object. | ||||||
| /// | ||||||
| /// A version of ``observe(_:)`` that is passed the current transaction. | ||||||
| /// This tool allows you to set up an observation loop so that you can access fields from an | ||||||
| /// observable model in order to populate your view, and also automatically track changes to | ||||||
| /// any fields accessed in the tracking parameter so that the view is always up-to-date. | ||||||
| /// | ||||||
| /// - Parameter tracking: A closure that contains properties to track | ||||||
| /// - Parameter onChange: Invoked when the value of a property changes | ||||||
| /// - Returns: A cancellation token. | ||||||
| @discardableResult | ||||||
| public func observe( | ||||||
| _ context: @escaping @MainActor @Sendable () -> Void, | ||||||
| onChange apply: @escaping @MainActor @Sendable () -> Void | ||||||
| ) -> ObserveToken { | ||||||
| observe { _ in | ||||||
| context() | ||||||
| } onChange: { _ in | ||||||
| apply() | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| /// Observe access to a property of an observable (or perceptible) object. | ||||||
| /// | ||||||
| /// A version of ``observe(_:onChange:)-(()->Void,_)`` that is passed updated value. | ||||||
| /// | ||||||
| /// - Parameter tracking: A closure that contains properties to track | ||||||
| /// - Parameter onChange: Invoked when the value of a property changes | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. though, in general, I would promote the label onChange to be the parameter name and get rid of |
||||||
| /// - Returns: A cancellation token. | ||||||
| @discardableResult | ||||||
| public func observe<T>( | ||||||
| _ context: @escaping @MainActor @Sendable @autoclosure () -> T, | ||||||
| onChange apply: @escaping @MainActor @Sendable (T) -> Void | ||||||
| ) -> ObserveToken { | ||||||
| observe(context()) { apply($1) } | ||||||
| } | ||||||
|
|
||||||
| /// Observe access to a property of an observable (or perceptible) objectt. | ||||||
| /// | ||||||
| /// A version of ``observe(_:onChange:)-(_,(T)->Void)`` that is passed the current transaction | ||||||
| /// alongside.updated value | ||||||
| /// | ||||||
| /// - Parameter context: An access to property to track | ||||||
| /// - Parameter onChange: Invoked when the value of a property changes | ||||||
| /// - Returns: A cancellation token. | ||||||
| @discardableResult | ||||||
| public func observe<T>( | ||||||
| _ context: @escaping @MainActor @Sendable @autoclosure () -> T, | ||||||
| onChange apply: @escaping @MainActor @Sendable (_ transaction: UITransaction, T) -> Void | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
I think it makes more sense to have the object of the observation as the first argument of the apply closure.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't be possible if we introduce variadic generics, but otherwise I think I agree UPD: I changed the order as proposed since variadic generic won't work with |
||||||
| ) -> ObserveToken { | ||||||
| let token = SwiftNavigation._observe(isolation: MainActor.shared) { _ in | ||||||
| MainActor._assumeIsolated { | ||||||
| UncheckedSendable(context()) | ||||||
| } | ||||||
| } onChange: { transaction, value in | ||||||
| MainActor._assumeIsolated { | ||||||
| apply(transaction, value.wrappedValue) | ||||||
| } | ||||||
| } | ||||||
| tokens.append(token) | ||||||
| return token | ||||||
| } | ||||||
|
|
||||||
| /// Observe access to properties of an observable (or perceptible) object. | ||||||
| /// | ||||||
| /// A version of ``observe(_:)-(()->Void)`` that is passed the current transaction. | ||||||
| /// | ||||||
| /// - Parameter apply: A closure that contains properties to track and is invoked when the value | ||||||
| /// of a property changes. | ||||||
|
|
@@ -121,13 +186,34 @@ | |||||
| public func observe( | ||||||
| _ apply: @escaping @MainActor @Sendable (_ transaction: UITransaction) -> Void | ||||||
| ) -> ObserveToken { | ||||||
| let token = SwiftNavigation._observe { transaction in | ||||||
| let token = SwiftNavigation._observe(isolation: MainActor.shared) { transaction in | ||||||
| MainActor._assumeIsolated { | ||||||
| apply(transaction) | ||||||
| } | ||||||
| } task: { transaction, work in | ||||||
| DispatchQueue.main.async { | ||||||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably an important change, I used a higher-level API here, to avoid duplication of derived observation handling and removed DispatchQueue.main.async in favor of |
||||||
| withUITransaction(transaction, work) | ||||||
| } | ||||||
| tokens.append(token) | ||||||
| return token | ||||||
| } | ||||||
|
|
||||||
| /// Observe access to properties of an observable (or perceptible) object. | ||||||
| /// | ||||||
| /// A version of ``observe(_:onChange:)-(()->Void,_)`` that is passed the current transaction. | ||||||
| /// | ||||||
| /// - Parameter context: A closure that contains properties to track | ||||||
| /// - Parameter onChange: Invoked when the value of a property changes | ||||||
| /// - Returns: A cancellation token. | ||||||
| @discardableResult | ||||||
| public func observe( | ||||||
| _ context: @escaping @MainActor @Sendable (_ transaction: UITransaction) -> Void, | ||||||
| onChange apply: @escaping @MainActor @Sendable (_ transaction: UITransaction) -> Void | ||||||
| ) -> ObserveToken { | ||||||
| let token = SwiftNavigation._observe(isolation: MainActor.shared) { transaction in | ||||||
| MainActor._assumeIsolated { | ||||||
| context(transaction) | ||||||
| } | ||||||
| } onChange: { transaction, _ in | ||||||
| MainActor._assumeIsolated { | ||||||
| apply(transaction) | ||||||
| } | ||||||
| } | ||||||
| tokens.append(token) | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.