How to create custom signal in ReactiveCocoa 4?

2019-06-22 04:45发布

问题:

I have the following setup, a GridView that consists of GridViewCells.

GridView

class GridView : UIView {

    var gridViewCells: [GridViewCell] = []
    let tapHandler: Position -> ()

    init(frame: CGRect, tapHandler: Position -> ()) {
        self.tapHandler = tapHandler
        super.init(frame: frame)
        self.gridViewCells = createCells(self)
        addCellsToView(self.gridViewCells)
    }

    func addCellsToView(cells: [GridViewCell]) {
        for cell in cells {
            self.addSubview(cell)
        }
    }
}

GridViewCell

class GridViewCell: UIImageView {

    let position: Position
    let tapHandler: Position -> ()

    init(frame: CGRect, position: Position, tapHandler: Position -> ()) {
        self.position = position
        self.tapHandler = tapHandler
        super.init(frame: frame)
    }

    func handleTap(sender: UITapGestureRecognizer) {
        self.tapHandler(self.position)
    }    
}

Note that I omitted some less relevant parts of the code, just know that when creating the cells in createCells(), every cell gets a UITapGestureRecognizer that targets handleTap:, I am also using:

typealias Position = (Int, Int)

So, as you can see, whenever a GridView gets instantiated, it gets passed a callback function tapHandler: Position -> () that ultimately gets called by the cell when a cell is tapped by the user.

Now, I want to turn the tap events into a RAC Signal. I have no clue how to approach this, as I am very new to RAC. Thanks to Colin Eberhardts blog articles, I managed to get a basic understanding of the building blocks and implement a custom signal like so:

func createSignal() -> Signal<String, NoError> {
    var count = 0
    return Signal {
        sink in
        NSTimer.schedule(repeatInterval: 1.0) { timer in
            sink.sendNext("tick #\(count++)")
        }
        return nil
    }
}

I now basically want a similar behaviour only that the emitted events aren't triggered by a NSTimer, but rather by the handleTap() function.

回答1:

You can accomplish this by using Signal.pipe(). This gives you a tuple with both the signal, and the observer tied to that signal:

let (signal, observer) = Signal<String, NoError>.pipe()

You can use that the same way you used sink in your example (note that sink is just the old terminology for observer :))

In the case of buttons or gesture recognizers though, you can leverage the RAC 2 extensions. For example:

let signal: SignalProducer<(), NoError> = gestureRecognizer
   .rac_gestureSignal()
   .toSignalProducer()
   .mapError { fatalError("Unexpected error: \(error)"); return () } // errors cannot occur, but because they weren't typed in `RACSignal` we have to explicitly ignore them.
   .map { _ in () }

Or UIControl.rac_signalForControlEvents.

I published a gist with extensions to simplify some of these common operations. I hope that's useful!