Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Activity
    Christian Tietze
    @DivineDominion
    To fix the Carthage build errors with Xcode 11.5, I tried to separate the library into multiple targets again and it works with Xcode 11.5 now, but apparently not with 10.1
    or 9.3 for that matter :)
    Xiaoyu Guo
    @MichaelGuoXY
    Hi guys, I'm not sure if this question was asked before (if yes, please point me to the reference). The question is, I have 2 actions are dispatched almost at the same time, but I want the 2nd action to change the updated state (updated by the 1st action). But the reality is, those 2 actions arrives at the reducer almost at the same time and they both take the initial state and update it. I was thinking of adding serial queue somewhere but not quite sure how to solve it.
    e.g.
    action1: PlusOne()
    action2: PlusOne()
    state = 1
    action1 sees state = 1 and update to state = 2
    action2 also sees state = 1 and update to state = 2
    Xiaoyu Guo
    @MichaelGuoXY
    I have wrapped the dispatch() method with, but it doesn't seem helpful
    func dispatch(_ action: Action) {
          dispatchQueue.async { [unowned self] in
              self.store.dispatch(action)
          }
    }
    Xiaoyu Guo
    @MichaelGuoXY
    this https://github.com/ReSwift/ReSwift/blob/master/ReSwift/CoreTypes/Store.swift#L159-L172 doesn't seem to be good to prevent this from happening
    Malcolm Jarvis
    @mjarvis
    @MichaelGuoXY ReSwift is to be used in a single threaded manner. Unexpected behaviour can occur if you're dispatching on multiple threads. If all dispatches happen on one thread, then the above behaviour you've described is impossible.
    Christian Tietze
    @DivineDominion
    I often forget this in practice as well: that whenever I send 2 store.dispatch right after one another, the whole ReSwift stack will finish and notify subscribers before the second dispatch is called, which throws a lot of concurrency problems out of the window already. You have to stick to single-threaded usage, though.
    Xiaoyu Guo
    @MichaelGuoXY
    @mjarvis @DivineDominion Sorry just wanna be clear, by sticking to single-threaded, do you mean I should use main thread only? or are there recommended ways of creating a single thread only for ReSwift usage?
    by dispatching actions in a custom serial queue doesn't seem to solve the problem, since the serial queue might dispatch the actions on to different threads. How can I make sure it's single-threaded?
    Christian Tietze
    @DivineDominion
    It is by default working on whatever thread you call dispatch on, and you usually do that from the main thread. So when you encounter race conditions like the one you mentioned, this indicates you're sometimes calling store.dispatch from another thread
    so Step 1 would be to figure out when you dispatch actions from other queues, and then try to wrap these in DispatchQueue.main (if that does the trick, you potentially want to redesign your components that work on other queues to encapsulate being-on-another-queue differently)
    Malcolm Jarvis
    @mjarvis
    @MichaelGuoXY As mentioned in the github reply, a serial queue should work correctly with ReSwift. If not, this is indicative of some issue in the setup of your serial queue, or perhaps you have some other issue causing the concurrency -- (eg middleware dispatching next on a different queue)
    Xiaoyu Guo
    @MichaelGuoXY
    @mjarvis Hi Malcolm, thanks. I've created a new issue regarding the problem I am facing ReSwift/ReSwift#443 I guess the issue is about the middleware dispatching as you mentioned above, but not sure how to solve it.
    Jonah Neugass
    @bgntsty_twitter
    Hi guys! Just wondering what you all think of this...basically a way to create thunks with dependencies:
    import Foundation
    
    public protocol Action {}
    
    public class ThunkMiddleware<T: ThunkAction, Deps: ThunkDependencies> where T.Dependencies == Deps {
        let dependencies: Deps
    
        public init(dependencies: Deps) {
            self.dependencies = dependencies
        }
    
        public func process(thunk: T) {
            thunk.process(dependencies: dependencies)
        }
    }
    
    public protocol ThunkAction: Action {
        associatedtype Dependencies
        func process(dependencies: Dependencies)
    }
    
    public protocol ThunkDependencies { }
    
    
    public class ThunkActionImpl<Deps: ThunkDependencies>: ThunkAction {
        public let events: [ThunkActionImpl<Deps>]?
    
        public init(events: [ThunkActionImpl<Deps>]? = nil) {
            self.events = events
        }
    
        public func process(dependencies: Deps) {
            events?.forEach {
                $0.process(dependencies: dependencies)
            }
        }
    }
    
    class SayHiAction: AuthenticationThunkAction {
        public override func process(dependencies: AuthenticationDependencies) {
            print(dependencies.hiString)
        }
    }
    
    class SayByeAction: AuthenticationThunkAction {
        public override func process(dependencies: AuthenticationDependencies) {
            print(dependencies.byeString)
        }
    }
    
    public struct AuthenticationDependencies: ThunkDependencies {
        public let hiString = "Hi!"
        public let byeString = "Bye!"
    }
    
    public typealias AuthenticationThunkAction = ThunkActionImpl<AuthenticationDependencies>
    
    let authDependencies = AuthenticationDependencies()
    
    let thunkMiddleware = ThunkMiddleware<AuthenticationThunkAction, AuthenticationDependencies>(dependencies: authDependencies)
    
    let compundAction = AuthenticationThunkAction(events: [SayHiAction(), SayByeAction()])
    
    thunkMiddleware.process(thunk: compundAction)
    Malcolm Jarvis
    @mjarvis
    @bgntsty_twitter looks good to me.
    Jonah Neugass
    @bgntsty_twitter
    Awesome, thanks for the feedback!
    nanocba
    @nanocba
    Hi everyone! I was wondering the following: In general it goes like I do an API call and with the results I need to update several properties on the state. Some of these updates do have logic that I would like to reuse. Right now, the way I usually approach this is by encapsulating this logic in the reducers, but I end up doing like three or four dispatches to the stores which trigger UI updates since the states have changed. My views are not particularly interested in this "micro-updates" to the state, but instead it would be enough to be notified just once after all the updates have been done. Is this like code smell that I'm doing something wrong within the architecture and I should re-think the approach or is this a good use case that there's a solution for it?
    nanocba
    @nanocba
    @bgntsty_twitter how is that thunk middleware plugged into the ReSwift flow? Who call's thunkMiddleware.process?
    Malcolm Jarvis
    @mjarvis
    @nanocba One method that can be used is a single indicator of change in state that gets incremented when all the updates are done, like a version number for that encapsulation of state.
    Jonah Neugass
    @bgntsty_twitter
    @nanocba Hope this helps
    public protocol ThunkDependencies { }
    
    public protocol MiddlewareContainer {
        func process() -> Middleware<StateType>
    }
    
    open class GenericThunkMiddleware<T: ThunkEvent, Dependencies: ThunkDependencies>: MiddlewareContainer where T.Dependencies == Dependencies {
        let dependencies: Dependencies
    
        public init(dependencies: Dependencies) {
            self.dependencies = dependencies
        }
    
        public func process() -> Middleware<StateType> {
        { dispatch, getState in { next in
            { event in
                if let thunkEvent = event as? T {
                    thunkEvent.process(dependencies: self.dependencies)
                } else {
                    next(event)
                }
            }
            }
            }
        }
    }
    
    public class AppDataStore: Store<AppState> {
    
        public func addMiddleware(_ middleware: [MiddlewareContainer]) {
            let reversedMiddleware = middleware.reversed()
            dispatchFunction = reversedMiddleware.reduce({ [unowned self] action in
                self._defaultDispatch(action: action)
            }, { dfunk, middleware in
                let dispatch: (ReSwift.Action) -> Void = { [weak self] in self?.dispatch($0) }
                let getState = { [weak self] in self?.state }
                return middleware.process()(dispatch, getState)(dfunk)
            })
        }
    
        public override func dispatch(_ action: ReSwift.Action) {
            if !Thread.isMainThread {
                DispatchQueue.main.sync {
                    super.dispatch(action)
                }
            } else {
                super.dispatch(action)
            }
        }
    }
    Christian Tietze
    @DivineDominion
    @nanocba When you use skipRepeats and narrow down to changes of some substate, the substate selector and equality checks will be triggered for the "micro-updates" but the views should not redraw.
    nanocba
    @nanocba
    thanks @DivineDominion. But how can you make the views not to redraw with those micro updates? Do you put some logic on the newState method that prevents that from happening?
    @bgntsty_twitter thanks that's insightful.
    using skip(when: ==) you can prevent views from updating. You subscribe the views to a substate only, not the whole app state, for this to take effect
    nanocba
    @nanocba
    @DivineDominion oh yes, my app state conforms to equatable so my derived sub-states so I get that for free and that's working incredibly well. But here's the scenario I was trying to expose: on my reducer I have Action A performing Logic A, then Action B performing Logic B, but then from a middleware I'm intercepting Action C that dispatches Action A and Action B. The two dispatches are causing two view updates since they both modify the state. So I'm wondering what would be the best way to group these modifications together so there's just one view update at the end while still being able to reuse Logic A and Logic B.
    Christian Tietze
    @DivineDominion
    Here's two impulses I'd follow to see where they lead: :one: make the middleware not reuse these actions but dispatch its own stuff. Aka reconsider what your actions mean and if they apply in all cases, or if it's just a convenient implementation detail. (Code duplication is just a heuristic: if it means different things, the same code can live in two places just fine and actually improve the overall architecture.) :two: go for a solely code-based fix: wrap the action dispatching in BeginningXUpdates/EndingXUpdates actions that toggle a Bool in your state. Views are updated only after EndingXUpdates turns off that flag again. Just like table views offer beginUpdates/endUpdates.
    Christian Tietze
    @DivineDominion
    Maybe more inspiration:
    # 202007111018 Coalesce multiple state updates to trigger a single view update
    #reswift #ui #middleware
    
    What if you have an action `A` that is processed in a middleware that dispatches `B` and `C` in sequence, both affecting a substate that will be rendered in the view, but you want to trigger only 1, not 2 updates in the UI?
    
    1. Can you **debounce/throttle** the subscription?
    2. **Double-buffering:** Can you change `B` to a variant `Btemp` and `C` to `Ctemp` and reduce the change to a **different, temporary substate**? That state could even be `private`. When you're finished, you copy the result over to the real, UI-visible state.
    4. **Versioning:** Update the UI only when the `Version<T>` number increases. Update versions only when you're finished. Similar to double-buffering, but you can work with a **scratch/draft/proposed version**. -- Similar to the staging area in git VS the commit history.
    3. **Coalescing actions:** Introduce `BeginningUpdates`/`EndingUpdates` actions that flick a boolean switch on/off. If it's on, the UI doesn't receive updates. If it's off, it receives updates again. Everything in between is effectively coalesced.
    nanocba
    @nanocba
    @DivineDominion wow! I really like many of those techniques, specifically the debounce one since I think you can wrap the dispatch calls into a debounce block so this handling will be transparent for the UI. Thanks for sharing this. Really helpful.
    nanocba
    @nanocba
    Actually, I need to correct myself: looking at the code, it doesn't seem to be possible to debounce subscription from outside the store class. So I think the debounce needs to be implemented in newState.
    Dario
    @Diarrhio
    Anybody here using ReSwift in a real time type AR application? I'm wondering how suitable the store is for invoking actions through the store at 30-60fps. Our app is already extremely CPU (and GPU) heavy due to the nature of our AR tracking. I've so far avoided invoking any actions through the store (our store/state is broken up into subsystems which are shielded from one another other than through invocations of actions through the store). Some of our subsystems need to invoke code on others subsystems, sometimes at 30-60fps. To avoid executing actions through the store, I've ended having subsystem A putting a function pointer on its state, and then having subsystem B calling the function pointer when needed. I'm not fond of this and I'd just rather have subsystem B just invoke a regular action that subsystem A handles. Before I spend a bunch of time refactoring and profiling both approaches, does anybody already have experience with this? Or do the authors of ReSwift know if the store is fast/lean enough to handle something like this?
    Dario
    @Diarrhio
    I should add that we have something like 11 subsystems and we lean heavily on Redux to manage state and also to just keep code isolated and siloed. Each subsystem has its own actions, reducers, middleware, etc. I guess in particular, having 11 reducers and middleware functions being called at 60fps, each doing its own switch/case on the action, is my primary concern, on top of whatever machinery exists in the ReSwift core to do what it does when each action is dispatched
    nanocba
    @nanocba
    Here's how I achieved debouncing view updates (thanks @DivineDominion!). I had StateObserver and PresenterType from before so it was easy to add this functionality for every view conforming to StateObserver.
    protocol PresenterType {
        associatedtype State: Equatable
        associatedtype ViewModel
        func viewModel(state: State) -> ViewModel
    }
    
    protocol StateObserver: StoreSubscriber {
        associatedtype Presenter: PresenterType where Presenter.State == StoreSubscriberStateType
    
        typealias StateKeyPath = KeyPath<AppState,Presenter.State>
    
        var store: AppStore { get }
        var presenter: Presenter { get }
        var viewModel: Presenter.ViewModel { get set }
        var subscription: StateKeyPath { get }
    
        func reload()
    }
    
    extension StateObserver {
        func unsubscribe() {
            store.unsubscribe(self)
        }
    
        func subscribe() {
            store.subscribe(self) { subscription in
                subscription.select({$0[keyPath: self.subscription]})
            }
        }
    
        func newState(state: Presenter.State) {
            viewModel = presenter.viewModel(state: state)
            Debounce.input(state, comparedAgainst: self.current, delay: 0.02) { input in
                self.reload()
            }
        }
    
        private var current: Presenter.State {
            store.state[keyPath: subscription]
        }
    }
    Malcolm Jarvis
    @mjarvis
    @Diarrhio I think you'll need to do some performance testing yourself to see if it can be applicable in your case.
    Generally my recommendation is if its desirable to have a redux like approach in a real-time system is to have a separate store dedicated to that system thats very bare-bones + optimized. That store can be run on a separate thread, and it can dispatch out to the main store (via middleware) + fetch content as needed.
    Dario
    @Diarrhio
    @mjarvis not a bad idea. Was just curious if anybody already had experience with this already (perhaps with a hard yes or no answer). I'll do some testing and see what the performance costs are. Thanks!
    Christian Tietze
    @DivineDominion
    @nanocba Can you share an example viewModel(state:) factory method implementation? Am curious what you do there
    nanocba
    @nanocba

    @nanocba Can you share an example viewModel(state:) factory method implementation? Am curious what you do there

    That's on the presenter's side. Basically, a presenter always take some state and converts that into the view model that view needs. Note that the concept of view model is not that from MVVM. These are pure view models. They don't have any functions, just properties that map 1:1 to view properties. The type of things you see inside this viewModel(state:) method is always transformations from state to view model, such as if the state exposes a date and you need to display it in the view, this is the place where you format the Date into a String.

    Christian Tietze
    @DivineDominion
    gotcha, thanks!
    Tim DeGraw
    @timdegraw_twitter
    Can anyone shed some light on the state ReSwift? The last release was over a year ago so I'm curious if it is still being maintained.
    Christian Tietze
    @DivineDominion
    I wanted to publish a Swift 5.x-built binary release for a while, guess I should put this onto my todo list for tomorrow for once :)
    @timdegraw_twitter The lib works, is used in production, alive and kicking
    Tim DeGraw
    @timdegraw_twitter
    Cool! Thank you. I'm working on a project that has it integrated and I was trying to decide whether to build on top of it or find an alternative. Thanks for letting me know. I'll keep my eye out for the update ;)
    Christian Tietze
    @DivineDominion
    There're some large-scale changes in the pipeline that nobody ever got around to finish up that could make the approach look a bit more like modern Swift, but if you dig classic OOP for the subscribers, you'll be fine with the current state of the framework 👍
    (And if you don't, it's easy to write closure-based convenience subscribers :))
    Tim DeGraw
    @timdegraw_twitter
    Sounds great
    I'm curious, is there built in support to persist to Realm/CoreData? Or do you projects usually serialize to disk?
    Christian Tietze
    @DivineDominion
    ReSwift only comes with the state/subscriber/action/reducer flow, the rest is up to you
    With CoreData, I usually represent the app's core in my own model objects and then serialize/deserialize them using CoreData. It goes against the framework grain a bit, because then you could just as well use a dumb SQLite DB, but I don't want to weave CoreData into every aspect of my app. It's pretty invasive when you follow most tutorials.
    Tim DeGraw
    @timdegraw_twitter
    Interesting. I actually have written any apps that use CoreData. But I've used Realm quite a bit and I like it.