Comments (20)
pipe.output.filterMap { $0 == .a ? Event.b : nil }.observe(pipe.input)
Nice example :)
I also think this kind of data-flow should be accepted, i.e. let's use recursive lock.
from reactiveswift.
Let me give you more concrete example:
class View {
enum Event {
case textChanged(text: String)
case buttonPressed
}
private let eventIO = Signal<Event, NoError>.pipe() // or view model
private let textField = UITextField()
private let button = UIButton()
private func bind() {
textField.reactive.continuousTextValues
.map { Event.textChanged(text: $0) }
.observe(eventIO.input)
button.reactive.controlEvents(.touchUpInside)
.map { _ in Event.buttonPressed }
.observe(eventIO.input)
}
func observe(action: @escaping (Event) -> Void) {
eventIO.output.observeValues {
action($0)
}
}
}
class ViewController {
let someView = View()
private func bind() {
someView.observe { [weak self] event in
switch event {
case .textChanged(let text):
// do something
case .buttonPressed:
// do something that eventually invoke below
self?.view.endEditing(false)
}
}
}
}
When it invokes self?.view.endEditing(false)
(or just call textField.resignFirstResponder()
) UIKit sometimes sends UITextView.textDidChangeNotification
(custom keyboard is related I am assuming) so continuousTextValues
sends new value, and that ends up with deadlock. This kind of things can easily happen (ex: Apple changes the event cycle) and it's too risky to make it deadlock and forcing app to crash.
I do understand pipe.output.filterMap { $0 == .a ? Event.b : nil }.observe(pipe.input)
kind of cases should be discouraged though, it actually happens in UI world easily (and sometimes unexpectedly), and I do not see the reason to force them to crash while it works.
What do you think?
from reactiveswift.
FYI
- Queue-drain model without RecursiveLock: #540
- IMO interesting approach, but impl is quite complicated so I still prefer simple RecursiveLock
- RxSwift impl: Rx.swift#L89-L122
- Uses RecursiveLock
fatalError
with friendly message when#if DEBUG
- Prints warning message at console when non-DEBUG
Since detecting cyclic dependency is hard in general and requires a lot of runtime test, I prefer the approach RxSwift is taking: prompt a message and use RecursiveLock.
And IMO "discouraged" only works when the problem is predictable and preventable beforehand.
But as far as I see many unexpected deadlock examples here, I would say the problem is overall "unpredictable".
Also, without reentrancy, I think many other potential operators e.g. #308 retryWhen
can't be implemented in ReactiveSwift (unless explicitly using scheduler to move on to async).
from reactiveswift.
After setting a breakpoint on _NSLockError()
, it looks like it is line 66 in Signal.swift
causing the lock and eventual breakdown.
This is one of my first bigger stabs at RAC4 after coming from RAC2.X. I might be misusing some things. Please let me know!
from reactiveswift.
Another note: I am using ReactiveCocoa 4.2.2, but this seemed to be a better place to post the issue as it appeared you guys were trying to get the Swift API oriented issues over here.
If I need to move this just let me know.
from reactiveswift.
Hmmm... interesting.... The root of the problem appears to be this code:
viewModel.undoEnabled.producer.startWithNext { [unowned self] enabled in
if enabled {
self.viewModel.undoCurrentAction()
}
}
If I do this:
viewModel.undoEnabled.producer.startWithNext { [unowned self] enabled in
if enabled {
// undoPressed simply calls viewModel.undoCurrentAction()
self.navigationItem.rightBarButtonItem =
UIBarButtonItem(title: "UNDO", style: .Plain, target: self, action: #selector(self.undoPressed))
} else {
self.navigationItem.rightBarButtonItem = nil
}
}
Everything works fine. Can anyone elaborate on this?
from reactiveswift.
It seems you are having an infinite feedback loop. undoCurrentAction
is being called when undoEnabled
changes, while undoEnabled
is bound to accept values from action.undoable
, which is being modified in undoCurrentAction
.
from reactiveswift.
This is just a simple example (actually leaking), but as you can see it is not infinite loop, so I feel it makes more sense to make Lock recursive.
enum Event {
case a
case b
}
let pipe = Signal<Event, NoError>.pipe()
pipe.output.filterMap { $0 == .a ? Event.b : nil }.observe(pipe.input)
pipe.output.observeValues { event in
print("value: \(event)")
}
pipe.input.send(value: .a) // deadlock
What do you think? @andersio @mdiep
Related PR that makes Lock recursive #308
Especially when we use RAS with RAC, all events are pretty much UI driven, and sometimes UIKit causes this case unexpectedly.
from reactiveswift.
I think that organization should be discouraged. FRP is all about unidirectional data flow, so I don't think it makes sense to support cycles.
from reactiveswift.
@mdiep @andersio Can we start from reopening this issue? We might be able to gather more opinions.
from reactiveswift.
In that particular case, the pipe is an unnecessary level of indirection.
This seems like it'd work:
class View {
enum Event {
case textChanged(text: String)
case buttonPressed
}
private let textField = UITextField()
private let button = UIButton()
func observe(action: @escaping (Event) -> Void) {
textField.reactive.continuousTextValues
.map { Event.textChanged(text: $0) }
.observe(action)
button.reactive.controlEvents(.touchUpInside)
.map { _ in Event.buttonPressed }
.observe(action)
}
}
class ViewController {
let someView = View()
private func bind() {
someView.observe { [weak self] event in
switch event {
case .textChanged(let text):
// do something
case .buttonPressed:
// do something that eventually invoke below
self?.view.endEditing(false)
}
}
}
}
I don't think that the RxSwift behavior of not crashing but violating the contract is better.
from reactiveswift.
That is right, but as commented in the code, we do have a view model that has some logics inside, so we do need this kind of pipe.
Also even if we did not use pipe, if want to do something like below ultimately, same thing will happen.
textView.reactive.continuousTextValues
.filter { $0 == "resign" }
.observe { [weak textView] _ in textView?.resignFirstResponder() }
I don't think that the RxSwift behavior of not crashing but violating the contract is better.
Agree, since sometimes we cannot prevent this happening..
What do you think of providing a way to switch the lock then?
from reactiveswift.
It should be possible to detect cycles when you're constructing the signal chain. š¤
from reactiveswift.
š¤
textView.reactive.continuousTextValues
.filter { $0 == "resign" }
.observe { [weak textView] _ in textView?.resignFirstResponder() }
How can you solve this? resignFirstResponder()
sometimes sends new value though. I assume there is no way other than switching the scheduler..
from reactiveswift.
Oh, good point! š¤
from reactiveswift.
Yeah..
It's not UITextView specific, this kind of thing can easily happen as some methods have unexpected side effect sometimes..
from reactiveswift.
FYI here's a flipflop example that won't crash our real world:
https://gist.github.com/inamiy/bd7d82a77a4df70b3b4a367a2d014ca9
from reactiveswift.
Hello. š Thanks for opening this issue. Due to inactivity, we will soft close the issue. If you feel that it should remain open, please let us know. š
from reactiveswift.
Just for sharing, we have been patching RAS internally to workaround this.
from reactiveswift.
@chuganzy do you think you could open a PR with the fix?
from reactiveswift.
Related Issues (20)
- Release 6.6.0 requires Swift 5.3 and therefore Xcode 12.x
- Using "<~" binding function with Signal.Observers causes memory leaks. HOT 3
- Using old xcconfig causing problems with Carthage & Apple Silicon macOS builds
- Support await / AsyncSequence HOT 13
- Playground does not work HOT 1
- Hello, ask a question about Disposable, thanks HOT 1
- What's means 'targeting' in QueueScheduler? HOT 2
- Signal.merge reported issue after migrating from ReactiveSwift from 4.0.0 -> 6.6.0 HOT 2
- xCode13 CompileSwiftSources normal x86_64 com.apple.xcode.tools.swift.compiler
- Usage with SwiftUI HOT 4
- Could not find module 'ReactiveSwift' for target 'arm64-apple-ios-simulator' HOT 2
- [BUG] XCode 14 beta 3 - bitcode issue HOT 1
- Is it planned to add count argument to `collect(every:on:skipEmpty:discardWhenCompleted:)`?
- Adopt OSAllocatedUnfairLock on iOS 16 HOT 9
- Add support for mapping Property/MutableProperty types to a Binding
- Signal triggered by phone call when device is closed HOT 3
- Infinite recursion in observeSwitchToLatest() HOT 3
- EXC_BAD_ACCESS Crashes occur in xcode15 HOT 4
- Reactive Swift crash with getCache function and observeSwitchToLatest signal
- Is there any plan for future releases? HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
š Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ā¤ļø Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from reactiveswift.