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
[Breaking Changes] Introduce InoutRef object - Used in commit() to detect changes are occurred. #180
Conversation
Sources/VergeCore/Inout.swift
Outdated
private let pointer: UnsafeMutablePointer<State> | ||
|
||
public init(pointer: UnsafeMutablePointer<State>, onModified: @escaping () -> Void) { |
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.
Passing the concrete instance causes a copy operation.
To escape from this, use UnsafeMutablePointer.
I want to get behavior programmatically such as well as inout
attribute in Swift language.
All of the methods to access the properties of the State use this pointer.
I don't know this works well and correctly.
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.
In addition, different from inout
attribute, developers can do escaping Inout
object to anywhere.
this is the most dangerous scenario.
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.
(inout Inout<Wrapped>) ->
probably works well? to prevent escaping in compile-time.
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.
If it work, I'll change the name of Inout<Wrapped>
because it would be confusing.
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.
Changed UnsafeInoutReference<Wrapped>
It didn't work well in production |
I misunderstood the usage of |
} | ||
|
||
@inline(__always) | ||
public final func _update(_ update: (inout Value) throws -> UpdateResult) rethrows { |
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.
We can choose if updates have occurred with UpdateResult
import Foundation | ||
|
||
@dynamicMemberLookup | ||
public final class UnsafeInoutReference<Wrapped> { |
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.
This is new object
/** | ||
A context to batch multiple mutations | ||
*/ | ||
public struct BatchCommitContext<State, Scope> { |
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.
Just moved from Store.swift
@@ -130,7 +130,7 @@ public class Derived<Value>: _VergeObservableObjectBase, DerivedType { | |||
break | |||
case .updated(let newState): | |||
store?.commit { | |||
$0 = newState | |||
$0.replace(with: newState) |
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.
The reason why we use this method to replace value is we can't override =
operator.
@@ -77,7 +77,7 @@ extension DispatcherType { | |||
_ file: StaticString = #file, | |||
_ function: StaticString = #function, | |||
_ line: UInt = #line, | |||
mutation: (inout Scope) throws -> Result | |||
mutation: (inout UnsafeInoutReference<Scope>) throws -> Result |
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.
The reason why we still use inout
attribute is to restrict escaping the reference object by developers in compile time.
func testEmptyCommit() { | ||
|
||
let store = DemoStore() | ||
|
||
var count = 0 | ||
|
||
let subs = store.sinkState(queue: .passthrough) { (_) in | ||
count += 1 | ||
} | ||
|
||
XCTAssertEqual(store.state.version, 0) | ||
|
||
store.commit { | ||
$0.count = 100 | ||
} | ||
|
||
XCTAssertEqual(store.state.version, 1) | ||
|
||
store.commit { _ in | ||
|
||
} | ||
|
||
// no changes | ||
XCTAssertEqual(store.state.version, 1) | ||
|
||
store.commit { | ||
// explict marking | ||
$0.markAsModified() | ||
} | ||
|
||
// many times calling empty commits | ||
for _ in 0..<3 { | ||
store.commit { _ in } | ||
} | ||
|
||
// no affects from read a value | ||
store.commit { | ||
if $0.count > 100 { | ||
$0.count = 0 | ||
XCTFail() | ||
} | ||
} | ||
|
||
XCTAssertEqual(store.state.version, 2) | ||
XCTAssertEqual(count, 3) | ||
|
||
withExtendedLifetime(subs, {}) | ||
|
||
} |
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.
Test code is here.
we can see the version is not updated after empty-commit.
Now, trying on my application. |
Main purpose: Store wants to skip a mutation had no changes to reduce emitting meaningless events.
https://github.com/apple/swift-evolution/blob/master/proposals/0205-withUnsafePointer-for-lets.md
Currently: if we do like following, the store emits updates to all of the subscribers.
This PR make Store skips emitting by these commits.
How we detect changes that have occurred is by modifying the properties through KeyPath (dynamicMemberLookup)
However, Wrapping the state causes copying in Swift. So we can avoid that by using
UnsafeMutablePointer<T>
onUnsafeInoutReference<Wrapped>
.Breaking changes
Using
replace
methodInside of commit, we become to can not replace with new value by assignment operator.
Instead, we use
.replace
Before
After
StateType
functionalities will be no longer meaningful.StateType
provides some stuff to modify the value easily.And the reason why we created this, we had no way to wrap the state to provide helper methods.
But now, we have wrapper object
UnsafeInoutReference
, this will have methods such asStateType
potentially in the future.