Code Monkey home page Code Monkey logo

rxfeedback.swift's People

Contributors

beeth0ven avatar cabeca avatar dvlprliu avatar freak4pc avatar ixrevo avatar kzaher avatar lexilabs avatar reflejo avatar shengyang998 avatar sylvanasx avatar vkt0r avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rxfeedback.swift's Issues

[Proposal] React for Void?

Hi there πŸ‘‹

My company struggled a bit after 2.0 removed react for non-equatable types. I think, it's absolutely reasonable to to require types returned by request be equatable.

Though, we are using Void? in quite a few places to trigger feedback loops that don't require any parameters. Examples include anything from networking to navigation, from current location fetch to database sync.

We ended up writting a custom react just for this case:

func react<State, Event>(request: @escaping (State) -> Void?,
	                 effects: @escaping () -> Signal<Event>) -> (Driver<State>) -> Signal<Event> {
    return react(request: { request($0) != nil ? true : nil }, // Bool to the rescue πŸ™ˆ
                 effects: { _ in effects() })
}

Do you think it would make sense to add a react for Void? to RxFeedback? Have you encountered this?

Thanks!

RequestLifetimeTracking forwardRequests: Duplicate keys were found in Dictionary

Hi,
First of all, thanks again for both RxSwift and RxFeedback, I really appreciate your work!

I'm not sure if this is the right channel for presenting this issue, but I'm experiencing (quite randomly but frequently) a crash for which I'm running out of ideas:

Thread 1: Fatal error: Duplicate keys of type 'ClassesStateMachine' were found in a Dictionary.

This is part of the stack trace:
#0 0x00000001f9231194 in swift_runtime_on_report ()
#1 0x00000001f928781c in swift_stdlib_reportFatalError ()
#2 0x00000001f8fd0ff4 in specialized String.withUnsafeBufferPointerToUTF8(:) ()
#3 0x00000001f91b4de0 in specialized assertionFailure( : _ : flags:) ()
#4 0x00000001f90ca048 in KEY_TYPE_OF_DICTIONARY_VIOLATES_HASHABLE_REQUIREMENTS(
:) ()
#5 0x00000001f9035084 in NativeDictionary.setValue(:forKey:isUnique:) ()
#6 0x00000001f9015268 in Dictionary.Variant.setValue(:forKey:) ()
#7 0x00000001f91d7b20 in specialized Dictionary.subscript.setter ()
#8 0x00000001f8fe32d8 in Dictionary.subscript.setter ()
#9 0x0000000104a7b760 in closure #1 in RequestLifetimeTracking.forwardRequests(
:) at myProject/Pods/RxFeedback/Sources/RxFeedback/Feedbacks.swift:160
#10 0x0000000104a77cfc in AsyncSynchronized.async(:) at myProject/Pods/RxFeedback/Sources/RxFeedback/AsyncSynchronized.swift:32
#11 0x0000000104a7ad38 in RequestLifetimeTracking.forwardRequests(
:) at myProject/Pods/RxFeedback/Sources/RxFeedback/Feedbacks.swift:148
#12 0x0000000104a7dd90 in closure #1 in closure #1 in closure #1 in react<A, B, C, D>(requests:effects:) at myProject/Pods/RxFeedback/Sources/RxFeedback/Feedbacks.swift:236

The line that crashes is 160:
state.lifetimeByIdentifier[requestID] = (...)

Could it be possible that this method:

internal func async(_ mutate: @escaping Mutate) {
        guard var executeMutation = self.enqueue(mutate) else {
            return
        }
        repeat {
            executeMutation(&state) // -> is a lock needed here?
            guard let nextExecute = self.dequeue() else {
                return
            }
            executeMutation = nextExecute
        } while true
    }

mutates concurrently the state, therefore the dictionary, creating duplicate entries?

I'm wondering if that code needs to be synchronized too like queue and enqueue in the same class.

Any thoughts?

Not sure if this information helps, but: ClassesStateMachine is a Request object that I'm returning as part of some of the feedbacks that I have registered in a Driver.system. I'm using a Set to keep alive some of the requests and services started in previous loops.

I'd really appreciate any ideas you might have about this,
Thanks again!
Leandro

The printing is printed only after network request is done.

When I use the following, everything works fine. But the logs come out not in sequence by time, but all come out after request is completed. Why?

image

image

The purpose is to track the loading activity before requesting by showing loading hud. However, it is no time between the logs.

How to test the Event creation?

I find that it is hard to test the event creation.For example i want to query some every 10s.And i write this in

viewDidLoad

method.I'm wandoring how to test the request produce and the event created in the right way?

let query: (Driver<State>) -> Signal<Event> = react(
        request: {state -> QueryRequest? in
            ....
            return QueryRequest()
        },
        effects: {request in
        return Signal.just(Event.query)
        })

Driver<Any>.system(
            initialState: State(),
            reduce: State.reduce,
            feedback: query
            )
            .drive()
            .disposed(by: self.rx.disposeBag)

It seams the only way is run the app and debug.So, is anyone has the same problem as me?

bind(self) { ... } leaks

bind variant that takes _ owner: WeakOwner actually just captures an owner strongly because it is executed on Observer.system creation and returns closure with a captured object. I'm using bind without owner and doing strongify manually as a workaround for now.

Feedback loop type issue

image
image 1

RxFeedback when use bindUI: (ObservableSchedulerContext<State>) -> Observable<Mutation>? when use bindUI: (Driver<State>) -> Signal<Mutation>, the bindUI has different type, what different between them?

Which bindUI type should use in different case?

[Proposal] - Make FeedbackLoop as a struct

Hi, guys!

I've been thinking how we can improve API in a way that users won't be able to path their own FeedbackLoop as they may use it wrong by not using scheduler correctly

So here is my proposal:

public struct FeedbackLoop<State, Event> {
    let loop: (ImmediateSchedulerType, Observable<State>) -> Observable<Event>
    
    public init<Control: Equatable>(query: @escaping (State) -> Control?, effects: @escaping (Control) -> Observable<Event>) {
        self.loop = { scheduler, state -> Observable<Event> in
            return state.map(query)
                .distinctUntilChanged { $0 == $1 }
                .flatMapLatest { control -> Observable<Event> in
                    guard let control = control else { return Observable.empty() }
                    
                    return effects(control).enqueue(scheduler)
                }
        }
    }
    
    public init(predicate: @escaping (State) -> Bool, effects: @escaping (State) -> Observable<Event>) {
        self.loop = { scheduler, state -> Observable<Event> in
            return state.flatMapLatest { state -> Observable<Event> in
                guard predicate(state) else { return Observable.empty() }
                
                return effects(state).enqueue(scheduler)
            }
        }
    }
    
    public init(effects: @escaping (State) -> Observable<Event>) {
        self.loop = { scheduler, state in
            return state.flatMapLatest { state in
                return effects(state).enqueue(scheduler)
            }
        }
    }
}

By exposing only these three available initializers. I guess we can also somehow figure out how something similar can be adopted for Driver as well.

What do you guys think?

GithubPaginatedSearch example bug

IMAGE ALT TEXT HERE

Reproduce Step:

Step 1:

Input the search text swift(request success)

Step 2:

TableView scroll near bottom. Lost network or the next page request failure. Lead response failure.

Execute case .response(.failure(let error))

case .response(.failure(let error)):
    var result = state
    result.shouldLoadNextPage = false
    result.lastError = error
    return result
}

Then the state emit flatMapLatest execute. shouldLoadNextPage current is false. Because current TableView near the bottom. Event.scrollingNearBottom emit again.

So requst next page again. if request next page failure again. The Feedback Repeat execution. Can not stop.

let triggerLoadNextPage: (Driver<State>) -> Signal<Event> = { state in
            return state.flatMapLatest { state -> Signal<Event> in
                if state.shouldLoadNextPage {
                    return Signal.empty()
                }
                
                return searchResults.rx.nearBottom.map { _ in Event.scrollingNearBottom }
                .debug()
            }
        }

Archive failed when installed by cocoapods

Hi

I get the following error when archive

[18:03:23]: β–Έ ❌  error: unexpected duplicate task: CopyPlistFile /Users/ios/Library/Developer/Xcode/DerivedData/ETURecord-aihjngexeivjzlgyldazycpbvucf/Build/Intermediates.noindex/ArchiveIntermediates/TeacherDev/IntermediateBuildFilesPath/UninstalledProducts/iphoneos/RxFeedback.framework/RxFeedback.plist /Users/ios/Projects/eturecorder-teacher/ios/Pods/RxFeedback/Sources/RxFeedback/RxFeedback.plist (in target 'RxFeedback')
[18:03:23]: β–Έ ❌  error: unexpected duplicate task: CopyPlistFile /Users/ios/Library/Developer/Xcode/DerivedData/ETURecord-aihjngexeivjzlgyldazycpbvucf/Build/Intermediates.noindex/ArchiveIntermediates/TeacherDev/IntermediateBuildFilesPath/UninstalledProducts/iphoneos/RxFeedback.framework/RxFeedback.plist /Users/ios/Projects/eturecorder-teacher/ios/Pods/RxFeedback/Sources/RxFeedback/RxFeedback.plist (in target 'RxFeedback')

This can be worked around by removing RxFeedback.plist from compile source. I think by changing s.source_files = "Sources/**/* to s.source_files = "Sources/**/*.swift in RxFeedback.podspec would fix this issue.

Migration to a new CI

The current CI we have in this project is TravisCI, for some reason lately TravisCI had presented a lot of issues with the builds taking a lot of time to build 😒. CircleCI can be one the best choices out there and they have support for OSS projects.

I was talking with a Support Engineer for CircleCI and he told me that definitely, they support OSS projects in his Seed plan for OSX. I post what we need to add the project to CircleCI:

What you would need to do is sign up for an account if you have not already. Once you have done that, add the projects to your account here:
https://circleci.com/projects/gh/YOURUSERNAME

Then if you could just provide me a link to your project I would be happy to enable the Seed plan for you.

Your project link will look like this
https://circleci.com/gh/YOURUSERNAME/PROJECTNAME/

So pretty much after we had done that I can send it the link to he enable the Seed for the project. Please let me know guys what do you think about it. Of course, another options of CI are under the table if we want to discuss it.

Swift4 support?

Unable to satisfy the following requirements:
RxSwift (~> 3.4)required byRxFeedback (0.1.0)

Return Bindings instead of tuple

I feel like it would make code more readable if we return Bindings instead of tuple?

  • highlight intention for the consumer of the API.
func bind<State, Event>(_ bindings: @escaping (Observable<State>) -> (Bindings) -> (Observable<State>) -> Observable<Event>
  • make user API more readable
    func makeBindings(for state: Observable<State>) -> Bindings<Event> {
        let subscriptions = [
            state.map(String.init).bind(to: label.rx.text)
        ]
        let events = [
            plus.rx.tap.map { Event.increment },
            minus.rx.tap.map { Event.decrement }
        ]
        return Bindings(subscriptions: subscriptions, events: events)
    }
}

Thoughts ?

previous request are cancelled by the following request

I'm using the RxFeedback in my project, and i got a problem that a processing network request will be cancelled by the follwing request.

To describe the question clearly, i made a demo here.

the UI is below, it's simple

screen shot 2018-08-05 at 6 06 32 pm

If 'request 1' button taped, a simulative request will be send, and delay to 2.0s, the button below will show 'response 1 received'.
and if 'request 2' button taped, 'response 2 received' will display the same way.

While, the problem is that if 'request 1' button taped, and before response 1 received, i taped the 'request 2' button, then the response 1 will never come back, it is cancelled.

And if i make debug here:
let event = just.delay(2.0) .debug(query.debugIdentifier, trimOutput: false)
i will got the following log:

---- request 1 ----
-> subscribed
---- request 2 ----
-> subscribed
---- request 1 ----
-> isDisposed
---- request 2 ----
-> Event next(response(RxFeedbackQueryTest.RequestType.second))
---- request 2 ----
-> Event completed
---- request 2 ----
-> isDisposed

we can see that request 1 was disposed once request 2 is subscribed

after reading the source codes of RxFeedback, i think, the codes leading the problem is below:

1

2

3

by using 'flatMapLatest' and 'switchLatest' operators, the previous sequence will be disposed,
why did you design to cancel the previous 'query' if a new 'query' comes?
can i just replace the 'flatMapLatest' with 'flatMap', and remove the operation 'takeUntilWithCompletedAsync'?

and how 'takeUntilWithCompletedAsync' can avoid the reentrancy issues?

The main code is below:
` private func setupBinding() {

    let UIBindings: (Driver<State>) -> Signal<Event> = bind(self) { me, state in
        let subscriptions = [
            state.map { $0.response1 }.drive(me.responseLabel1.rx.text),
            state.map { $0.response2 }.drive(me.responseLabel2.rx.text)
        ]
        
        let events: [Signal<Event>] = [
            me.requestButton1.rx.tap.map { Event.request(.first) }.asSignal(onErrorJustReturn: .none),
            me.requestButton2.rx.tap.map { Event.request(.second) }.asSignal(onErrorJustReturn: .none),
            me.clearButton.rx.tap.map { Event.clear }.asSignal(onErrorJustReturn: .none)
        ]
        return Bindings(subscriptions: subscriptions, events: events)
    }
    
    let nonUIBindings: (Driver<State>) -> Signal<Event> = react(query: { (state) -> Set<RequestType> in
        let querys = Set(state.requestsToSend.all())
        return querys
    }) { (query) -> Signal<Event> in
        let just = Signal.just(Event.response(query))
        let event = just.delay(2.0)
                     .debug(query.debugIdentifier, trimOutput: false)
        return event
    }
    
    Driver
        .system(initialState: State(),
                reduce: State.reduce,
                feedback: UIBindings, nonUIBindings)
        .drive()
        .disposed(by: bag)
}`

`enum Event {
case none
case request(RequestType)
case response(RequestType)
case clear
}

struct State: Mutable {

var requestsToSend = Requests()

var response1: String?
var response2: String?

static func reduce(state: State, event: Event) -> State {
    switch event {
    case .request(let req):
       return state.mutateOne {
            $0.requestsToSend.append(req)
        }
    case .response(let req):
        return state.mutateOne {
            switch req {
            case .first:
                $0.response1 = req.responseString
            case .second:
                $0.response2 = req.responseString
            }
        }
    case .clear:
        return state.mutateOne {
            $0.response1 = nil
            $0.response2 = nil
            _ = $0.requestsToSend.all()
        }
    default: return state
    }
}

}`

cannot send out multiple requests at the same time

I'm using the RxFeedback on my project. And one of the business is to send out multiple requests when entering a specific page. So I change the State and in the side-effects callback I merge those Observables for each request into one and map the merged Observable into signal. however, only one request data is refreshed by the Driver though all the requests came back. What am I doing incorrectly?

here is my code FYI:

        internal static func sideEffect(query: Query,
                                        with dependancy: Dependancy) -> Signal<Command> {
            if query.urls.count > 0 {
                return dependancy.api
                    .makeRequests(apis: query.urls,
                                  constrained: dependancy.constrained)
                    .asSignal(onErrorJustReturn: .failure(.unknown))
                    .map(Command.reponseReceived)
            }
            return Signal.empty()
        }

func request(apis: [URLType]) -> Observable<Response> {
        
        let observables: [Observable<Response>] =
            apis.flatMap { api in
                return
                    Cornerstone
                        .NetworkProxy
                        .implementor
                        .response(api: api, url: api.URL, get: api.query)
                        .map {
                            if let data = ...  as? [String: Any] {
                                return .success((data: data, type: api))
                            } 
                            return .failure(.unknown)
                        }.catchErrorJustReturn(.failure(.unknown))
        }
        
        return Observable.merge(observables)
    }

The current Readme examples are outdated

The examples in the README produce:

'system(initialState:reduce:scheduler:feedback:)' is deprecated: Renamed to version that takes `ObservableSchedulerContext` as argument.

with the latest version of RxFeedback

Reentrancy issue

If a feedback observable immediately triggers an event, it won't be recursively called with the new state unless the scheduler is asynchronous.

In the attached example project, I'm using MainScheduler.instance as the selected scheduler. Switching to MainScheduler.asyncInstance unlocks the proper behavior.

While this is not specifically an issue, the fact that there is no runtime warning about reentrancy can make the issue difficult to figure out by inexperienced users.

RxFeedbackSyncIssue.zip

[Request] Could you port RxFeedback to Combine

As Combine is the new go to Rx framework and probably will replace RxSwift, RxCocoa, etc. in the future there is a need for a rx feedback loop using Combine.

How feasible is a port and how much work would it probably be?

Can't build version 1.1.0 with Carthage

When I do 'carthage build --no-skip-current' I get the following error:

Failed to write to /Users/ferranpujolcamins/development/RxFeedback.swift/Carthage/Build/Mac/RxFeedback.framework: Error Domain=NSCocoaErrorDomain Code=260 "The file β€œRxFeedback.framework” couldn’t be opened because there is no such file." UserInfo={NSURL=file:///Users/ferranpujolcamins/Library/Developer/Xcode/DerivedData/RxFeedback-hlgvungruiurhlczuflxvahyfwrv/Build/Intermediates.noindex/ArchiveIntermediates/RxFeedback/BuildProductsPath/Release-iphoneos/RxFeedback.framework, NSFilePath=/Users/ferranpujolcamins/Library/Developer/Xcode/DerivedData/RxFeedback-hlgvungruiurhlczuflxvahyfwrv/Build/Intermediates.noindex/ArchiveIntermediates/RxFeedback/BuildProductsPath/Release-iphoneos/RxFeedback.framework, NSUnderlyingError=0x7fc514926810 {Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"}}

Difference between ReactorKit and RxFeedback

Hi,
It's more an explanation than an issue. I am new to these architecture and have recently started Rx programming.
1- What's the difference between RxFeedback and ReactorKit ? They look similar but the implementation is a bit different.
2- I would like to implement the business logic in C++. Is there an example on how to do this?
3- When you implement a UI Feedback loop, will the State usually look like the ViewModel?

Thanks,
Elie

Hello. I need help :)

I use RxFeedback in the project, but I have a problem and I can not solve it either. And most likely it's from a lack of knowledge.
I use "methodInvoked (#selector (UIViewController.viewWillAppear (_ :)))" and send the "readyView" event to the system.
But "methodInvoked" does not send element to the system, but if you subscribe without the system, everything is fine.
And if the UIViewController.viewWillAppear method is repeatedly called, then the message comes through.

Here is an example
https://github.com/mefilt/RxFeedbackTest/blob/master/RxFeedbackTest/ViewController.swift

Thx

Eventless bindings dispose of subscriptions too early.

I had a bug where I needed to bind and return Bindings without any events. The delayed stream was disposed before having a chance to actually send any events.

Consider this piece of code:

typealias MyFeedback = Driver<MyState> -> Signal<MyEvent>

func viewDidLoad() {
  super.viewDidLoad()

  let bindings: MyFeedback = bind(self) { owner, state 

    // this driver will send an event when view appears
    // but "view did appear" will never be printed
    let sub = owner.rx.viewDidAppear.do(onNext: { print("view did appear") }).drive()

    let events: [Signal<MyEvent>] = [] // this is needed to disambiguate `Bindings` initialiser
    return Bindings(subscriptions: [sub], events: events)
  }
  Driver.system(initial: MyState.initial, reduce: MyState.reduce, feedback: [bindings])
}

The owner.rx.viewDidAppear is a stream that sends value whenever VC's view appears. But as stated - the expected console output is never produced.

I did find the remedy to use [Signal.never()] as a value to events argument. Maybe a convenience initialiser that accepts only subscriptions would be nice to have? Something like

extension Bindings {
  init(subscriptions: [Disposable]) {
    self.init(subscriptions: subscriptions, events: [Signal.never()])
  }
}

RxFeedback functions autocomplete problem

screenshot 2019-02-22 at 15 44 03

![image](https://user-images.githubusercontent.com/17700769/53240848-40788280-36b9-11e9-95d5-1cba5c44a4d8.png)

screenshot 2019-02-22 at 15 48 06

Xcode don't suggests autocomplete for function of RxFeedback. However, if I am taking complete implementation of "Driver.system" usage from examples. It's successful builds

macOS support

There is any reason why there is no target for macOS? I add one myself and seems to build without issues so far.

Problem building dependencies of the example app.

For some reason running Examples/dependencies.sh or git submodule update --init --recursive --force before building the Example target inside of theExamples/Examples.xcodeproj in a fresh cloned repo succeeds

... but trying to build Example target before running the Examples/dependencies.sh or git submodule update --init --recursive --force causes build failure.

I have no idea what's happening here.

Navigation Example

Hi,

I've recently started to introduce the RxFeedback into my app. It's very simple, powerful and promotes very structured code. I am struggling a little bit with the navigation maybe because I am new to the concept. Is there a way to provide examples around this topic.
1- How to share a state across a couple of view controllers:
Although it's not that good but to keep things simple I inherited a navigation controller. The navigation controller has the system operator. I tried to create a feedback loop (react). It will query the state to see if a new navigation should be performed and the effect would be to create the next view controller and bind it to the main state. Unfortunately, I wasn't able to implement the last part.
2- How to create outer loops:
I am trying to do what you stated in the 20 mins pitch. I think that this similar to the first one.
image
3- For the navigation I tried to do things like button.rx.tap.withLatestFrom(state). So that I can pass a part of the state to another controller. Is this bad?

Thanks

Empty events for `Bindings` leads to paired subscriptions get disposed of prematurely

Some Feedbacks just have subscriptions and using an Observable#using to track subscriptions leads to subscriptions' lifecycle depending on the events' lifecycle.
So when passed in an empty events, the subscriptions get disposed of instantly.

There are two solutions:

  1. Use a CompositeDisposable to track their lifecycle separately
  2. Concat the events Observable with an Observable.never() to ensure it will not get disposed of prematurely

I would like to provide a Pull-request to fix this if we can reach an agreement on which solution to use.

Add more example about the new react function

I notice RxFeedback will release 2.0.
I don't understand how to use this react function.

public func react<State, Request: Equatable, RequestID, Event>(
    requests: @escaping (State) -> [RequestID: Request],
    effects: @escaping (_ initial: Request, _ state: Observable<Request>) -> Observable<Event>
) -> (ObservableSchedulerContext<State>) -> Observable<Event>

Can't build with Carthage on Xcode 10

I can't build version 1.0.3 using Xcode 10. The log shows following errors:

...project/Carthage/Checkouts/RxFeedback/RxSwift/RxCocoa/Deprecated.swift:185:48: error: use of undeclared type 'RxTabBarDelegateProxy'
        public func createRxDelegateProxy() -> RxTabBarDelegateProxy {
                                               ^~~~~~~~~~~~~~~~~~~~~
...project/Carthage/Checkouts/RxFeedback/RxSwift/RxCocoa/Deprecated.swift:192:48: error: use of undeclared type 'RxTabBarControllerDelegateProxy'
        public func createRxDelegateProxy() -> RxTabBarControllerDelegateProxy {
                                               ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...project/Carthage/Checkouts/RxFeedback/RxSwift/RxCocoa/Deprecated.swift:227:48: error: use of undeclared type 'RxWebViewDelegateProxy'
        public func createRxDelegateProxy() -> RxWebViewDelegateProxy {
                                               ^~~~~~~~~~~~~~~~~~~~~~

Is it because project uses older version of RxCocoa?

[Proposal] Introduce ObservableSystem make system chainable.

Overview

We can introduce ObservableSystem, this will make system chainable:

PlayCatch Example

Before:

let bindUI: (ObservableSchedulerContext<State>) -> Observable<Event> = bind(self) { me, state in
    ...
    return Bindings(subscriptions: subscriptions, events: events)
}

Observable.system(
    initialState: State.humanHasIt,
    reduce: { (state: State, event: Event) -> State in
        switch event {
        case .throwToMachine:
            return .machineHasIt
        case .throwToHuman:
            return .humanHasIt
        }
    },
    scheduler: MainScheduler.instance,
    feedback:
        // UI is human feedback
        bindUI,
        // NoUI, machine feedback
        react(request: { $0.machinePitching }, effects: { (_) -> Observable<Event> in
            return Observable<Int>
                .timer(.seconds(1), scheduler: MainScheduler.instance)
                .map { _ in Event.throwToHuman }
        })
    )
    .subscribe()
    .disposed(by: disposeBag)

After:

ObservableSystem.create(
    initialState: State.humanHasIt,
    reduce: { (state: State, event: Event) -> State in
        switch event {
        case .throwToMachine:
            return .machineHasIt
        case .throwToHuman:
            return .humanHasIt
        }
    },
    scheduler: MainScheduler.instance
    )
    .binded(self) { me, state in
        ...
        return Bindings(subscriptions: subscriptions, events: events)
    }
    .reacted(request: { $0.machinePitching }, effects: { (_) -> Observable<Event> in
        return Observable<Int>
            .timer(.seconds(1), scheduler: MainScheduler.instance)
            .map { _ in Event.throwToHuman }
    })
    .system([])
    .subscribe()
    .disposed(by: disposeBag)

Evolution

The solution is inspired by Rx. Let's get in.

What do we have currently in Rx?

I will show minimal type inferface in Rx, as it will help us move fast to destination:

typealias Event<Element> = Element // mocked, just a name

typealias Observer<Element> = (Event<Element>) -> Void

typealias Disposable = () -> Void

typealias Observable<Element> = (@escaping Observer<Element>) -> Disposable

I've removed unrelate logic to make our evolution "pure".

Now we can adds some operators which are free functions:

func filter<Element>(
    _ predicate: @escaping (Element) -> Bool
    ) -> (@escaping Observable<Element>) -> Observable<Element> {

    return { source -> Observable<Element> in
        ...
    }
}

func map<Element, Result>(
    _ transform: @escaping (Element) -> Result
    ) -> (@escaping Observable<Element>) -> Observable<Result> { ... }

func flatMap<Element, Result>(
    _ transform: @escaping (Element) -> Observable<Result>
    ) -> (@escaping Observable<Element>) -> Observable<Result> { ... }

As far as we can tell, Operator behaiver like a Middleware:

typealias Middleware<Element, Result> = (@escaping Observable<Element>) -> Observable<Result>

We can change operator a little bit to:

func fulter1<Element>(_ predicate: @escaping (Element) -> Bool) -> Middleware<Element, Element> { ... }

func map1<Element, Result>(_ transform: @escaping (Element) -> Result) -> Middleware<Element, Result> { ... }

func flatMap1<Element, Result>(_ transform: @escaping (Element) -> Observable<Result>) -> Middleware<Element, Result> { ... }

That's what we have now in Rx.

Port to RxFeedback

We can find a way to port all these stuff to RxFeedback:

What do we have in RxFeedback?

typealias Feedback<State, Event> = (Observable<State>) -> Observable<Event>

typealias ImmediateSchedulerType = Any // Ignored in this demo context.

func system<State, Event>(
    initialState: State,
    reduce: @escaping (State, Event) -> State,
    scheduler: ImmediateSchedulerType,
    feedback: [Feedback<State, Event>]
    ) -> Observable<State> { ... }

We may add a createSystem function:

func createSystem<State, Event>(
    initialState: State,
    reduce: @escaping (State, Event) -> State,
    scheduler: ImmediateSchedulerType
    ) -> ([Feedback<State, Event>]) -> Observable<State> {
    
    return { feedback -> Observable<State> in
        ...
    }
}

By comparing function system with createSystem, It's not hard to find the return type has been changed form Observable<State> to ([Feedback<State, Event>]) -> Observable<State>.

Ok. This will open a new world, let's call the new return type System:

typealias System<State, Event> = ([Feedback<State, Event>]) -> Observable<State>

Then createSystem becomes:

func createSystem1<State, Event>(
    initialState: State,
    reduce: @escaping (State, Event) -> State,
    scheduler: ImmediateSchedulerType
    ) -> System<State, Event> { ... }

Next we can introduce SystemMiddleware:

typealias SystemMiddleware<State, Event> = (System<State, Event>) -> System<State, Event>

The feedback creator funtion like react and bind in RxFeedback now becomes operator:

func react<State, Request: Equatable, Event>(
    request: @escaping (State) -> Request?,
    effects: @escaping (Request) -> Observable<Event>
    ) -> SystemMiddleware<State, Event> { ... }

func react<State, Request: Equatable, Event>(
    requests: @escaping (State) -> Set<Request>,
    effects: @escaping (Request) -> Observable<Event>
    ) -> SystemMiddleware<State, Event> { ... }

func bind<State, Event>(
    _ bindings: @escaping (Observable<State>) -> (subscriptions: [Disposable], events: [Observable<Event>])
    ) -> SystemMiddleware<State, Event> { ... }

Real

Let's bring this to real.

Introduce ObservableSystem to RxFeedback:

public struct ObservableSystem<State, Event> {
    public typealias Feedback = Observable<Any>.Feedback<State, Event>
    public typealias System = ([Feedback]) -> Observable<State>
    
    public let system: System
    
    private init(_ system: @escaping System) {
        self.system = system
    }
}

extension ObservableSystem {
    
    public static func create(
        initialState: State,
        reduce: @escaping (State, Event) -> State,
        scheduler: ImmediateSchedulerType
        ) -> ObservableSystem<State, Event> {
        return ObservableSystem { feedback in
            return Observable<Any>.system(
                initialState: initialState,
                reduce: reduce,
                scheduler: scheduler,
                feedback: feedback
            )
        }
    }
    
    public func reacted<Request: Equatable>(
        request: @escaping (State) -> Request?,
        effects: @escaping (Request) -> Observable<Event>
        ) -> ObservableSystem<State, Event> {
        let newFeedback: Feedback = react(request: request, effects: effects)
        let sourceSystem = self.system
        return ObservableSystem { feedback in sourceSystem([newFeedback] + feedback) }
    }
    
    public func reacted<Request: Equatable>(
        requests: @escaping (State) -> Set<Request>,
        effects: @escaping (Request) -> Observable<Event>
        ) -> ObservableSystem<State, Event> {
        let newFeedback: Feedback = react(requests: requests, effects: effects)
        let sourceSystem = self.system
        return ObservableSystem { feedback in sourceSystem([newFeedback] + feedback) }
    }
    
    public func binded<WeakOwner: AnyObject>(
        _ owner: WeakOwner,
        _ bindings: @escaping (WeakOwner, ObservableSchedulerContext<State>) -> (Bindings<Event>)
        ) -> ObservableSystem<State, Event> {
        let newFeedback: Feedback = bind(owner, bindings)
        let sourceSystem = self.system
        return ObservableSystem { feedback in sourceSystem([newFeedback] + feedback) }
    }

    // ... other operator

    // There are some duplicate code in each operator, 
    // It's fine in the demo context since this will improve readabylity.
}

The ObservableSystem is like Observable in Rx.

And reacted, binded is like Operators in Rx.

Now the system can be chainable:

ObservableSystem.create(
    initialState: State.humanHasIt,
    reduce: { (state: State, event: Event) -> State in
        switch event {
        case .throwToMachine:
            return .machineHasIt
        case .throwToHuman:
            return .humanHasIt
        }
},
    scheduler: MainScheduler.instance
    )
    .binded(self) { ... }
    .reacted(request: { $0.machinePitching }, effects: { ... })
    .reacted(request: { ... }, effects: { ... })
    .reacted(request: { ... }, effects: { ... })
    .system([])
    .subscribe()
    .disposed(by: disposeBag)

It will bring us some benefits:

  • system has its own namespace ObservableSystem
  • more consist with Rx
  • easier to add operator
  • less typing

With the benefits, I proposal to add this feature.

A running example can be found here with commit: introduce ObservableSystem. It also handle driver version (DriverSystem).

I'm open to disccuss πŸ˜„, If this is accepted, I will make a PR.

Thanks.

Carthage for macOS

I'm trying to add RxFeedback to my macOS App using Carthage and getting a build error when running carthage update RxFeedback --platform Mac

error: Building for macOS, but the linked framework 'RxCocoa.framework' was built for iOS + iOS Simulator. (in target 'RxFeedback' from project 'RxFeedback')
error: Building for macOS, but the linked framework 'RxSwift.framework' was built for iOS + iOS Simulator. (in target 'RxFeedback' from project 'RxFeedback')

Could you give me a hint as to what I'm doing wrong or have a look to see if this is a bug?

Non-equatable requests in 2.0

Hi!

Are there plans to readd the hooks for non-equatable values returned from state requests? In 1.X it was possible to pass own == function that would always return false for such cases. In 2.0 there doesn't seem to be one left.

Best,

Cannot call value of non-function type '[Observable<Event>.Type]'

Xcode 15
M1

  • Moya/RxSwift (15.0.0):
    • Moya/Core
    • RxSwift (~> 6.0)
private func bindingsStrongify<Event, O, WeakOwner>(_ owner: WeakOwner, _ bindings: @escaping (WeakOwner, O) -> (Bindings<Event>))
    -> (O) -> (Bindings<Event>) where WeakOwner: AnyObject {
    return { [weak owner] state -> Bindings<Event> in
        guard let strongOwner = owner else {
            return Bindings(subscriptions: [], events: [Observable<Event>]())
        }
        return bindings(strongOwner, state)
    }
}
ζˆͺ屏2023-10-16 δΈ‹εˆ5 20 00

Add a parameter: UI.bind(self) { me, state in ... }

Hi thanks for provide the simplest architecture for RxSwift with RxFeedback,
I've a small question, is it good to add a strongify self parameter like what UIBindingObserver dose.
This can avoid let label = self.label! :

//let label = self.label!
//let minus = self.minus!
//let plus = self.plus!

Observable.system(
    ...
    feedback:
        UI.bind(self) { me, state -> UI.Bindings<Event> in
            let subscriptions = [
                state.map(String.init).bind(to: me.label.rx.text)
            ]
            let events = [
                me.plus.rx.tap.map { Event.increment },
                me.minus.rx.tap.map { Event.decrement }
            ]
            return UI.Bindings(subscriptions: subscriptions, events: events)
        }
    )
    .subscribe()
    .disposed(by: disposeBag)

Rename `Event` to `Mutation`

What was the motivation behind this rename as the version change minor, but this change broke the source backwards compatibility.

How to capture non UI events to manipulate state?

I have an observable coming in from NotificationCenter. Right now, I'm introducing it the same as I would do for incoming UI events.

I know it works, but Is this the correct approach?

let applicationScopeNotification: (Driver<LoginState>) -> Signal<LoginEvent> = RxFeedback.bind(self) { (this, state) -> (Bindings<LoginEvent>) in
    let events: [Signal<LoginEvent>] = [
        didSetSelectDefaultApplicationNotification.asSignal(onErrorSignalWith: Signal.empty()).map(LoginEvent.userSelectedApplicationScope)
    ]
    return Bindings(subscriptions: [], events: events)
}

Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.