1

私の目標は、ReactiveCocoa を使用してラジオ ボタンを実装することです。これは、子ビューモデルの配列があり、一度に選択状態にできるのは 1 つだけであることを意味します。また、必要なときにすばやく切り替える機能も必要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) オブザーバブルの 2 つのセットがあり、1 つのセットは受信イベントを表し、もう 1 つのセットはビューモデルの状態を表す状態プロパティです。読み取りまたはバインドできる状態。

2) 状態プロパティの変更は、「イベントを監視している」場合にのみ実行でき、プロパティを監視している間は実行できません。これは、1 つの状態プロパティがそれ自体または別の状態プロパティを決して変更してはならず、その変化を観察してはならないことを意味します。

3) 着信イベントは他のイベントを開始できません。つまり、スタック/ランループ ループごとに 1 つのイベントです。

コードは次のようになります。

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