Skip to content

Instantly share code, notes, and snippets.

@zhigang1992
Last active August 29, 2015 14:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zhigang1992/8ed2c3d26ddcffccd9b8 to your computer and use it in GitHub Desktop.
Save zhigang1992/8ed2c3d26ddcffccd9b8 to your computer and use it in GitHub Desktop.
ReactiveCocoa RubyMotion Extension
class UITextField
alias_method :text_signal, :rac_textSignal
end
class UIActionSheet
alias_method :button_signal, :rac_buttonClickedSignal
end
class UIControl
alias_method :event_signal, :rac_signalForControlEvents
end
class UIViewController
def rac_dismiss_vc(animated)
RACSignal.create! do |subscriber|
dismissViewControllerAnimated(animated, completion: ->() {
subscriber.sendCompleted
})
RACDisposable.disposableWithBlock(nil)
end
end
def rac_present_vc(viewController, animated)
RACSignal.create! do |subscriber|
presentViewController(viewController, animated: animated, completion: ->() {
subscriber.sendCompleted
})
RACDisposable.disposableWithBlock(nil)
end
end
end
class RACSignal
# RubyMotion's bridge support does not handle Objective-C methods that take block
# arguments typed as id so as to take blocks of varying arity. To work around this,
# an Objective-C category has been created with numbered methods, each explicitly
# typed, which pass the arguments to the original method.
#
# The same work-around will be required for all other methods that take an id block.
def self.reduceLatest(*signals, &block)
raise "Block must take #{signals.size} arguments to match the number of signals." if signals.size != block.arity
case block.arity
when 1 then
combineLatest(signals, reduce1: block)
when 2 then
combineLatest(signals, reduce2: block)
when 3 then
combineLatest(signals, reduce3: block)
when 4 then
combineLatest(signals, reduce4: block)
when 5 then
combineLatest(signals, reduce5: block)
end
end
# In RubyMotion, signals for boolean properties are resulting in values of 0 and 1,
# both of which evaluate as true in Ruby. Consequently the stream is full of true
# values. The work-around is to explicitly map the values to a boolean.
# See #to_bool defined below for TrueClass, FalseClass and Fixnum
def boolean
map ->(primitive) { primitive.to_bool }
end
# Create ! versions of a few ReactiveCocoa methods, allowing the methods to take
# a block the Ruby way and avoid explicit lambda expressions.
# This conflicts with the common semantics of using ! to imply the method modifies
# the receiver, but the alternatives (ex: map_, map?) are less appealing.
def map!(&block)
map(block)
end
def initially!(&block)
initially(block)
end
def finally!(&block)
finally(block)
end
def do_next!(&block)
doNext(block)
end
def do_error!(&block)
doError(block)
end
def do_completed!(&block)
doCompleted(block)
end
def do_all!(&block)
doNext(block)
.doError(block)
.doCompleted(block)
end
def filter!(&block)
filter(block)
end
def each!(&block)
subscribeNext(block)
end
alias_method :each, :each!
def each_error(&block)
subscribeError(block)
end
def throttle!(interval, &block)
return throttle(interval, valuesPassingTest: block) if block_given?
throttle(interval)
end
alias_method :error!, :each_error
def add_signal(&block)
addSignalBlock(block)
end
def reduce_next!(initial, &block)
combinePreviousWithStart(initial, reduce:block)
end
def reduce!(initial, &block)
scanWithStart(initial, combine: block)
end
alias_method :inject!, :reduce!
def first!
take(1)
end
def main_thread
deliverOn(RACScheduler.mainThreadScheduler)
end
def latest
switchToLatest
end
def aggregate!(start, &block)
aggregateWithStart(start, reduce: block)
end
def reduce_each!(&block)
map! do |args|
case block.arity
when 0 then
block.call()
when 1 then
block.call(args.first)
when 2 then
block.call(args.first, args.second)
when 3 then
block.call(args.first, args.second, args.third)
when 4 then
block.call(args.first, args.second, args.third, args.fourth)
when 5 then
block.call(args.first, args.second, args.third, args.fourth, args.fifth)
end
end
end
alias_method :distinct, :distinctUntilChanged
# Map a signal of truth values to a true value and false value.
def flip_flop(trueValue, falseValue)
map! do |truth|
truth ? trueValue : falseValue
end
end
class << self
def merge!(*signals)
self.merge(signals)
end
def combined!(*signals)
self.combineLatest(signals)
end
def concat!(*args)
self.concat(args)
end
def create!(&block)
self.createSignal(block)
end
def defer!(&block)
self.defer(block)
end
end
end
class RACStream
def named(name)
self.name = name
self
end
end
[TrueClass, FalseClass].each do |boolClass|
boolClass.class_exec do
def to_bool;
self
end
end
end
class Fixnum
def to_bool;
self != 0
end
end
class RACMotionKeyPathAgent
def initialize(object, observer)
@object = object
@observer = observer
@keyPath = []
end
def key(key)
@keyPath << key.to_s
self
end
def signal
@object.rac_signalForKeyPath(keyPath, observer: @observer)
end
def method_missing(method, *args, &block)
# Conclude when the method corresponds to a RACSignal method; see to RACAble() macro
if RACSignal.method_defined?(method)
signal.send(method, *args, &block)
# Conclude when assigning a signal; see RAC() macro
elsif args.size == 1 && args.first.is_a?(RACSignal) && method.to_s.end_with?('=')
key(method)
@object.rac_deriveProperty(keyPath.chop, from: args.first)
# Conclude when calling a non-RAC method with a signal argument, lift it
elsif !@keyPath.empty? && args.any? { |arg| arg.is_a?(RACSignal) }
# method_missing sets method to just the first argument's portion of the called selector.
# Construct the full selector. Obviously works only for Objective-C methods.
options = args.last.is_a?(Hash) ? args.pop : {}
selector = [method].concat(options.keys).join(':') << ':'
objects = args.concat(options.values)
target = @object.valueForKeyPath(keyPath)
if target.respondsToSelector(selector)
# RubyMotion can't splat an array into a varags paramter. Case it out.
o = objects
case objects.size
when 2 then
target.rac_liftSelector(selector, withObjects: o[0], o[1])
when 3 then
target.rac_liftSelector(selector, withObjects: o[0], o[1], o[2])
when 4 then
target.rac_liftSelector(selector, withObjects: o[0], o[1], o[3], o[4])
end
else
raise "#{target.inspect} (via keyPath '#{keyPath}') does not respond to `#{selector}`"
end
# Continue when simple getter is called and extend the key path
else
key(method)
end
end
def keyPath
@keyPath.join('.')
end
alias_method :to_s, :keyPath
end
class RACTuple
def self.with_objects(*objects)
self.tupleWithObjectsFromArray(objects)
end
end
class Object
def signal_for_selector(selector)
rac_signalForSelector(selector)
end
def rac(object=self)
RACMotionKeyPathAgent.new(object, self)
end
# Capitalized doesn't work as well due to the required () to avoid it being treated as a constant
alias_method :RAC, :rac
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment