我的目标是使用ReactiveCocoa实现单选按钮。这意味着我有一个子视图模型数组,一次只能有一个处于选定状态。我还希望能够在需要时快速切换到multi-select
,但我的问题在于single-select
。当我尝试清除以前选择的项目时,我遇到了僵局。下面的代码将给出以下错误:
-[NSLock lock]: deadlock (<NSLock: 0x7fc64233e180> 'org.reactivecocoa.ReactiveCocoa.Signal')
2016-06-09 04:05:08.731 ios_samples[92288:26027579] *** Break on _NSLockError() to debug.
我不确定我是否只是将这一切都搞错了。请帮忙。
代码示例:
// MAIN METHOD
let model = Model(selectionType: .Single)
model.children[0].toggleState()
model.children[1].toggleState() // deadlocks here
print("selected:\(model.selectedChildren.value.filter { $0.isSelected }.map { $0.name }), count: \(model.totalSelected.value)")
// TYPES
enum SelectionType { case Single, Multi }
class Model {
let children = [ChildModel(name: "child1"), ChildModel(name: "child2")]
let totalSelected = MutableProperty<Int>(0)
let selectedChildren = MutableProperty<[ChildModel]>([])
init(selectionType: SelectionType) {
let aggregateProducer = SignalProducer(values: children.map { $0.selectedSignal.producer })
.flatten(.Merge)
func singleSelectChildrenProducer() -> SignalProducer<[ChildModel], NoError> {
return SignalProducer { emitter, disposable in
aggregateProducer.startWithNext { state in
self.children
.filter { $0.isSelected && $0.name != state.childModel.name }
.forEach { $0.clearState() /* SOURCE OF DEADLOCK */ }
emitter.sendNext(state.childModel.isSelected ? [state.childModel] : [])
}
}
}
func multiSelectChildrenProducer() -> SignalProducer<[ChildModel], NoError>{
return SignalProducer { emitter, disposable in
aggregateProducer.startWithNext { _ in
let allSelectedChildren = self.children.filter { $0.isSelected }
emitter.sendNext(allSelectedChildren)
}
}
}
let selectionProducer = selectionType == .Multi
? multiSelectChildrenProducer()
: singleSelectChildrenProducer()
selectionProducer.startWithSignal { signal, disposable in
totalSelected <~ signal.map { $0.count }
selectedChildren <~ signal
}
}
}
class ChildModel {
let name: String
var selectedSignal: MutableProperty<ChildModelState>!
init(name: String) {
self.name = name
selectedSignal = MutableProperty<ChildModelState>(ChildModelState(childModel: self, state: false))
}
var isSelected: Bool { return selectedSignal.value.state }
func toggleState() {
selectedSignal.value = ChildModelState(childModel: self, state: !selectedSignal.value.state)
}
func clearState() {
selectedSignal.value = ChildModelState(childModel: self, state: false)
}
}
struct ChildModelState {
let childModel: ChildModel
let state: Bool
}
所以我通过提出一些关于我的视图模型对象图变化的规则来解决这个问题:
1)有两组observable,一组用于表示传入事件,另一组用于表示viewModel状态的状态属性;可以阅读或绑定的状态。
2)状态属性的更改只能在“观察事件”时进行,而不能在观察属性时进行。这意味着一个州财产不应该改变自己或其他国家财产并观察变化。
3)传入事件不能启动其他事件,即每个堆栈/ runloop循环一个事件
现在代码如下:
// MAIN METHOD
let model = Model(selectionType: .Multi)
model.children[0].tapEvent.bind(SignalProducer(value: Void()))
model.children[1].tapEvent.bind(SignalProducer(value: Void()))
print("selected:\(model.selectedChildren.value.filter { $0.isSelected }.map { $0.name }), count: \(model.totalSelected.value)")
// TYPES
enum SelectionType { case Single, Multi }
class Model {
let children = [ChildModel(name: "child1"), ChildModel(name: "child2")]
let totalSelected = MutableProperty<Int>(0)
let selectedChildren = MutableProperty<[ChildModel]>([])
init(selectionType: SelectionType) {
// watch events and update state properties
let allTapProducer = SignalProducer(values: children.map { $0.tapEvent.producer }).flatten(.Merge)
allTapProducer.startWithNext { child in
if selectionType == .Single {
self.children
.filter { $0.isSelected && $0.name != child.name }
.forEach { $0.clearState() }
}
child.toggleState()
}
//watch state properties and bind to other state properties
SignalProducer(values: children.map { $0.selectedSignal.producer })
.flatten(.Merge)
.map { _ in self.children.filter { $0.isSelected } }
.startWithSignal { signal, disposable in
totalSelected <~ signal.map { $0.count }
selectedChildren <~ signal
}
}
}
class ChildModel {
let name: String
var selectedSignal = MutableProperty<Bool>(false)
var tapEvent: UIEventSignal<ChildModel>!
init(name: String) {
self.name = name
tapEvent = UIEventSignal<ChildModel>(sender: self)
}
var isSelected: Bool { return selectedSignal.value }
func toggleState() {
selectedSignal.value = !self.selectedSignal.value
}
func clearState() {
selectedSignal.value = false
}
}
class UIEventSignal<T> {
private let property = MutableProperty<T?>(nil)
let sender: T
init(sender: T) {
self.sender = sender
}
func bind(uiSignalProducer: SignalProducer<Void, NoError>) {
property <~ uiSignalProducer.map { [weak self] _ in self?.sender }
}
var producer: SignalProducer<T, NoError> {
return property.producer.filter { $0 != nil }.map { $0! }
}
}
这种模式感觉它会让事情变得简单,因为对象图关系变得更加复杂......也许。我现在可能会考虑这个,但请为ReactiveCocoa建议任何替代模式甚至替代库。