notests / rxfeedback.swift Goto Github PK
View Code? Open in Web Editor NEWThe universal system operator and architecture for RxSwift
License: MIT License
The universal system operator and architecture for RxSwift
License: MIT License
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!
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
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
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.
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?
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()
}
}
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.
this fix must be her too
RxSwiftCommunity/RxSwiftExt@494fe20
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.
Unable to satisfy the following requirements:
RxSwift (~> 3.4)required by
RxFeedback (0.1.0)
I feel like it would make code more readable if we return Bindings
instead of tuple?
func bind<State, Event>(_ bindings: @escaping (Observable<State>) -> (Bindings) -> (Observable<State>) -> Observable<Event>
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 ?
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
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:
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
}
}
}`
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 Observable
s 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 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
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.
Hi,
It could be great if you add your project here -> https://swift.org/source-compatibility/. In order to prevent future Swift version to break it.
https://www.jessesquires.com/blog/a-story-about-swift-source-compat/
Cheers,
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?
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"}}
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
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
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()])
}
}
There is any reason why there is no target for macOS? I add one myself and seems to build without issues so far.
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.
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.
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
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:
CompositeDisposable
to track their lifecycle separatelyObservable.never()
to ensure it will not get disposed of prematurelyI would like to provide a Pull-request to fix this if we can reach an agreement on which solution to use.
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>
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?
We can introduce ObservableSystem
, this will make system chainable:
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)
The solution is inspired by Rx
. Let's get 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
.
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> { ... }
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:
ObservableSystem
Rx
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.
Hello. How to exchange events between different loops?
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?
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,
Xcode 15
M1
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)
}
}
Hi,
I seem to be having a problem using this. Tried also this example:
https://github.com/NoTests/RxFeedback.swift/blob/master/Examples/Examples/Counter.swift
And has the same issue. Can you point me to the problem or to a working example?
Please update the examples in the README, thanks!
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)
What was the motivation behind this rename as the version change minor, but this change broke the source backwards compatibility.
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
Is it possible to get a new release tag with the changes from @fabianmuecke ?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.