Skip to content

Commit

Permalink
weekly update
Browse files Browse the repository at this point in the history
1. try reactive nested features
2. begin change useStore
3. add some docs
4. think use Observation
  • Loading branch information
CreatorMetaSky committed Aug 23, 2023
1 parent ae95239 commit 4d10d14
Show file tree
Hide file tree
Showing 17 changed files with 190 additions and 35 deletions.
8 changes: 2 additions & 6 deletions Example/Example/UseCases/StoreUseCasesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ struct StoreUseCasesView: View {

typealias CounterStoreType = () -> (count: ReactiveValue<Int>, increment: () -> Void, decrement: () -> Void)

let useCounterStore: CounterStoreType = useStore("counter") {
let useCounterStore: CounterStoreType = defStore("counter") {
let count = defValue(0)

func increment() {
Expand All @@ -25,11 +25,7 @@ let useCounterStore: CounterStoreType = useStore("counter") {
count.value -= 1
}

return (
count: count,
increment: increment,
decrement: decrement
)
return (count, increment, decrement)
}

extension StoreUseCasesView {
Expand Down
4 changes: 2 additions & 2 deletions Example/Example/UseCases/ValueUseCasesView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import Water

struct ValueUseCasesView: View {
var body: some View {
// CounterView()
CounterView()
// CounterNameView()
// BooleanValueView()
// ValueNotChangeView()
// SimultaneousChangeValuesView()
ShowPasswordView()
// ShowPasswordView()
}
}

Expand Down
2 changes: 2 additions & 0 deletions Sources/Water/Composables/useReducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@

import Foundation

// TODO: - reducer -> store ?
public func useReducer<State, Action>(_ initialState: State, _ reducer: @escaping (inout State, Action) -> Void) -> (() -> State, (Action) -> Void) {
// FIXME: - change to defReactive ?
let reactiveState = defValue(initialState)

func dispatch(action: Action) -> Void {
Expand Down
40 changes: 26 additions & 14 deletions Sources/Water/Composables/useStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,37 @@
// useStore.swift
// Water
//
// 1. createStore
// 2. defStore
// 3. useStore
// 4. createSetupStore
// 5. unit test

import Foundation

class Store<T> {
private let setupFn: () ->T

init(setupClosure: @escaping () -> T) {
self.setupFn = setupClosure
}
public typealias UseStoreFn<Store> = () -> Store

// TODO: - view extension add global store manager to dispatcher
public func createStore() {

func setup() -> T {
return setupFn()
}
}

public func useStore<T>(_ storeId: String, setupClosure: @escaping () -> T) -> (() -> T) {
let store = Store(setupClosure: setupClosure)
let fn: () -> T = {
return store.setup()
func setupStore<Store>(with closure: @escaping () -> Store) -> Store {
// TODO: - call it with effect scope
// FIXME: - keep store reactive ?
let store = closure()
return store
}

public func defStore<Store>(_ storeId: String, setupStoreClosure: @escaping () -> Store) -> UseStoreFn<Store> {
func useStore() -> Store {
// TODO: - follow the step
// 1. get dispatcher
// 2. check store exist
// 3. generate store
let store: Store = setupStore(with: setupStoreClosure)
// 4. save store with id
return store
}
return fn
return useStore
}
3 changes: 1 addition & 2 deletions Sources/Water/Reactivity/Computed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,5 @@ extension ComputedValue: Reactor {
// MARK: - def

public func defComputed<T>(_ getter: @escaping ComputedGetter<T>, setter: ComputedSetter<T>? = nil) -> ComputedValue<T> {
let computedValue = ComputedValue(getter: getter, setter: setter)
return computedValue
ComputedValue(getter: getter, setter: setter)
}
8 changes: 7 additions & 1 deletion Sources/Water/Reactivity/Effect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,11 @@ public func stop<T>(_ runner: ReactiveEffectRunner<T>) {
runner.stop()
}

// MARK: - track and trigger reactor
// MARK: - reactor

public func isDefined(_ value: Any) -> Bool {
return value is Reactor
}

protocol Reactor: AnyObject {
func trackEffects()
Expand Down Expand Up @@ -185,6 +189,8 @@ func triggerEffects(_ effects: [AnyEffect]) {
}
}

// MARK: - track and trigger effect

struct ReactorEffectMap {
let reactor: AnyReactor
var effects: [AnyEffect] = []
Expand Down
7 changes: 6 additions & 1 deletion Sources/Water/Reactivity/Handler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,14 @@ extension ReactiveHandler {
return reactiveObject._target[keyPath: keyPath]
}

func handleSetProperty<T, V>(of reactiveObject: ReactiveObject<T>, at keyPath: WritableKeyPath<T, V>, with newValue: V) {
func handleSetProperty<T, V>(of reactiveObject: ReactiveObject<T>, at keyPath: KeyPath<T, V>, with newValue: V) {
assert(!isReadonly, "set property at keyPath: \(keyPath) failed, becase \(reactiveObject._target) is readonly")

guard let keyPath = keyPath as? WritableKeyPath<T, V> else {
assertionFailure("set value: \(newValue) at keyPath: \(keyPath) failed, maybe define your property use 'var' not 'let'")
return
}

let oldValue = reactiveObject._target[keyPath: keyPath]
if sameValue(lhs: oldValue, rhs: newValue) {
return
Expand Down
18 changes: 10 additions & 8 deletions Sources/Water/Reactivity/Object.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,19 +25,21 @@ public class ReactiveObject<T>: Reactor {
}
}

// FIXME: - reactive nested
public subscript<V>(dynamicMember keyPath: KeyPath<T, V>) -> V {
get {
// print("get keyPath \(keyPath)")
_reactiveHandler.handleGetProperty(of: self, at: keyPath)
}
set {
// print("set keyPath \(keyPath) - new value = \(newValue)")
if let keyPath = keyPath as? WritableKeyPath<T, V> {
_reactiveHandler.handleSetProperty(of: self, at: keyPath, with: newValue)
print("get keyPath \(keyPath)")
let value = _reactiveHandler.handleGetProperty(of: self, at: keyPath)
if isDefined(value), let castValue = value as? ReactiveValue<V> {
return castValue.unwrap()
} else {
fatalError("the key path is not writable")
return value
}
}
set {
print("set keyPath \(keyPath) - new value = \(newValue)")
_reactiveHandler.handleSetProperty(of: self, at: keyPath, with: newValue)
}
}

public func unwrap() -> T {
Expand Down
3 changes: 3 additions & 0 deletions Sources/Water/Reactivity/Value.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public class ReactiveValue<T>: Reactor {
init(value: T, handler: ReactiveHandler) {
_value = value
_reactiveHandler = handler
if isClass(value) {
assertionFailure("reference class type not supported") // FIXME: need more check
}
}

public var value: T {
Expand Down
19 changes: 19 additions & 0 deletions Tests/WaterTests/EffectSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,25 @@ class EffectSpec: QuickSpec {
expect(age1).to(equal(12))
}

it("effect with change nested object property") {
let foo = Foo(bar: "bar", user: User(age: 10))
let observed = defReactive(foo)

var age = 0
var callNum = 0
defEffect {
callNum += 1
age = observed.user.age
}

expect(callNum).to(equal(1))
expect(age).to(equal(10))

observed.user.age = 11
expect(callNum).to(equal(2))
expect(age).to(equal(11))
}

it("effect return runner") {
var foo = 10

Expand Down
26 changes: 25 additions & 1 deletion Tests/WaterTests/ReactiveObjectSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import Nimble

class ReactiveObjectSpec: QuickSpec {
override class func spec() {
it("happy path struct") {
it("object should be reactive") {
let origin = User(age: 10)

let user = defReactive(origin)
Expand Down Expand Up @@ -50,5 +50,29 @@ class ReactiveObjectSpec: QuickSpec {
user.age = 22
expect(user.age).to(equal(22))
}

it("change reactive object let property will assert") {
struct Foo {
let bar: Int
}

let foo = defReactive(Foo(bar: 10))
expect { foo.bar = 11 }.to(throwAssertion())
}

it("reactive with nested object should reactive") {
let foo = NestedReactiveFoo(bar: "bar", user: defReactive(User(age: 10)), array: defReactive([1, 2, 3]))

let observed = defReactive(foo)

expect(observed.isReactive).to(equal(true))
expect(observed.user.isReactive).to(equal(true))
expect(observed.array.isReactive).to(equal(true))
}

it("check object is defined reactor") {
let user = defReactive(User(age: 10))
expect(isDefined(user)).to(equal(true))
}
}
}
5 changes: 5 additions & 0 deletions Tests/WaterTests/ReactiveReadonlySpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ class ReactiveReadonlySpec: QuickSpec {
expect(user.isReadonly).to(equal(false))
expect(user.isReactive).to(equal(true))
}

it("check readonly is defined reactor") {
let user = defReadonly(User(age: 10))
expect(isDefined(user)).to(equal(true))
}
}

describe("readonly / array") {
Expand Down
45 changes: 45 additions & 0 deletions Tests/WaterTests/ReactiveValueSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import Quick
import Nimble
@testable import Water

extension User: Equatable {
static func == (lhs: User, rhs: User) -> Bool {
lhs.age == rhs.age
}
}

class ReactiveValueSpec: QuickSpec {
override class func spec() {
it("should hold a value") {
Expand All @@ -33,5 +39,44 @@ class ReactiveValueSpec: QuickSpec {
expect(callNum).to(equal(2))
expect(b).to(equal(2))
}

it("nested struct object should reactive") {
let user = defValue(User(age: 10))
var dummy = 0
var callNum = 0

defEffect {
callNum += 1
dummy = user.value.age
}

expect(callNum).to(equal(1))
expect(dummy).to(equal(10))

user.value.age = 11
expect(callNum).to(equal(2))
expect(dummy).to(equal(11))

user.value.age = 11
expect(callNum).to(equal(2))
expect(dummy).to(equal(11))
}

it("nested class object should not support") {
expect { defValue(ClassUser(uname: "haha", age: 10)) }.to(throwAssertion())
}

it("object contains value property") {
// struct Foo {
// let name = defValue("hello")
// }
//
// let foo = defReactive(Foo())
//
// expect(foo.name).to(equal("hello"))

// foo.name = "123"
// expect(foo.name).to(equal("123"))
}
}
}
13 changes: 13 additions & 0 deletions Tests/WaterTests/TestHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
// Water
//

@testable import Water

struct User {
var age: Int
}
Expand All @@ -26,3 +28,14 @@ class ClassUser {
self.age = age
}
}

struct Foo {
let bar: String
var user: User
}

struct NestedReactiveFoo {
let bar: String
let user: ReactiveObject<User>
let array: ReactiveArray<Int>
}
16 changes: 16 additions & 0 deletions docs/compare-with-x.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
## compare with TCA

- TCA not good
- force to use Redux style
- store state is more than view needs
- state action must equtable
- call stack is confusing
- avoid unnecessary body recall
- combine temp state and data state

- Water should learn
- runtime warning
- @usableFromInline @inlinable
- comments

- Water is good
8 changes: 8 additions & 0 deletions docs/reactivity/reactive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Reactive

## simple usage


## nested

- yourself must declare it as reactive
Empty file added docs/reactivity/readonly.md
Empty file.

0 comments on commit 4d10d14

Please sign in to comment.