Skip to content
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

Merged
merged 20 commits into from Oct 29, 2020

Conversation

muukii
Copy link
Collaborator

@muukii muukii commented Oct 24, 2020

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.

commit { _ in }
commit {
  if false {
    $0.xxx = something
  }

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> on UnsafeInoutReference<Wrapped>.

Breaking changes

Using replace method

Inside of commit, we become to can not replace with new value by assignment operator.
Instead, we use .replace

Before

commit {
  $0 = value
}

After

commit {
  $0.replace(with: value)
}

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 as StateType potentially in the future.

Comment on lines 29 to 31
private let pointer: UnsafeMutablePointer<State>

public init(pointer: UnsafeMutablePointer<State>, onModified: @escaping () -> Void) {
Copy link
Collaborator Author

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

@muukii muukii Oct 24, 2020

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.

Copy link
Collaborator Author

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed UnsafeInoutReference<Wrapped>

@muukii
Copy link
Collaborator Author

muukii commented Oct 25, 2020

It didn't work well in production

@muukii
Copy link
Collaborator Author

muukii commented Oct 25, 2020

It didn't work well in production

I misunderstood the usage of withUnsafeMutablePointer.
I changed to performing all inside withUnsafeMutablePointer.

@muukii muukii changed the title [WIP / Experimental] Introduce Inout object - Used in commit() to detect changes are occurred. Introduce UnsafeInoutReference object - Used in commit() to detect changes are occurred. Oct 25, 2020
@muukii muukii changed the title Introduce UnsafeInoutReference object - Used in commit() to detect changes are occurred. [Breaking Changes] Introduce UnsafeInoutReference object - Used in commit() to detect changes are occurred. Oct 25, 2020
@muukii muukii self-assigned this Oct 25, 2020
}

@inline(__always)
public final func _update(_ update: (inout Value) throws -> UpdateResult) rethrows {
Copy link
Collaborator Author

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> {
Copy link
Collaborator Author

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> {
Copy link
Collaborator Author

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)
Copy link
Collaborator Author

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
Copy link
Collaborator Author

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.

Comment on lines +223 to +271
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, {})

}
Copy link
Collaborator Author

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.

@muukii muukii marked this pull request as ready for review October 25, 2020 12:11
@muukii
Copy link
Collaborator Author

muukii commented Oct 25, 2020

Now, trying on my application.

@muukii muukii changed the title [Breaking Changes] Introduce UnsafeInoutReference object - Used in commit() to detect changes are occurred. [Breaking Changes] Introduce InoutRef object - Used in commit() to detect changes are occurred. Oct 27, 2020
@muukii muukii merged commit 7b48f36 into master Oct 29, 2020
@muukii muukii deleted the muukii/inout branch October 29, 2020 21:13
@muukii muukii restored the muukii/inout branch October 29, 2020 21:13
@muukii muukii deleted the muukii/inout branch December 29, 2020 04:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants