具有Reactive Cocoa的单选按钮逻辑并避免死锁

时间:2016-06-09 11:56:56

标签: swift frp reactive-cocoa-4

我的目标是使用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)有两组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建议任何替代模式甚至替代库。

0 个答案:

没有答案