1

我的目标是使用 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

所以我通过提出一些关于我的视图模型对象图如何变化的规则来解决这个问题:

1) 有两组 observables,一组表示传入事件,另一组是表示 viewModel 状态的状态属性;可以读取或绑定到的状态。

2) 只能在“观察事件”时更改状态属性,而不能在观察属性时进行。这意味着一个状态属性不应该改变自己或另一个状态属性并观察变化。

3) 传入事件不能启动其他事件,即每个堆栈/运行循环循环一个事件

代码现在如下所示:

// 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。

4

0 回答 0