class RMX def self.safe_block(block) weak_block_owner_holder = RMXWeakHolder.new(block.owner) block.weak! proc do |*args| if wbo = weak_block_owner_holder.value block.call(*args) end end end # Raises an exception when called from a thread other than the main thread. # Good for development and experimenting. def self.assert_main_thread! raise "Expected main thread. #{Dispatch::Queue.current.description}" unless NSThread.currentThread.isMainThread end # call the block immediately if called on the main thread, # otherwise call it async on the main queue def self.inline_or_on_main_q(&block) if NSThread.currentThread.isMainThread block.call else Dispatch::Queue.main do block.call end end end # call the block immediately if called on the main thread with the given args, # otherwise call it async on the main queue. # silently ignores nil blocks to avoid if !block.nil? checks, useful for async callbacks # that optionally take a callback def self.block_on_main_q(block, *args) unless block.nil? inline_or_on_main_q do block.call(*args) end end end def require_queue!(queue, file, line) unless Dispatch::Queue.current.description == queue.description raise "WRONG QUEUE: was: #{Dispatch::Queue.current.description}, expected: #{queue.description}. #{@object.value.inspect} #{file}:#{line}, #{caller.inspect}" end end def own_methods if object = unsafe_unretained_object (object.methods - (object.superclass.methods)).sort end end # Shortcut to instance_variable_get and instance_variable_get: # 1 arg for instance_variable_get # 1 arg and block for instance_variable_get || instance_variable_set # 2 args for instance_variable_set def ivar(*args, &block) if object = unsafe_unretained_object key = args[0] val = nil if args.size == 1 if block val = object.instance_variable_get("@#{key}") if val.nil? val = block.call object.instance_variable_set("@#{key}", val) val end else val = object.instance_variable_get("@#{key}") end elsif args.size == 2 val = args[1] object.instance_variable_set("@#{key}", val) else raise "RMX#ivar called with invalid arguments: #{args.inspect}" end val end end def nil_instance_variables! if object = unsafe_unretained_object ivars = [] + object.instance_variables while ivar = ivars.pop object.instance_variable_set(ivar, nil) end true end end def debounce(unique_id, opts={}, &block) if (seconds = opts[:seconds]) && seconds > 0 debounce_seconds(seconds, unique_id, opts[:now], &block) else debounce_runloop(unique_id, opts[:now], &block) end end def debounce_runloop(unique_id, run_immediately=false, &block) if object = unsafe_unretained_object lookup = Thread.current["rmx_debounce_runloop"] ||= {} key = [ object, unique_id ] lookup[key] ||= begin block.call if run_immediately CFRunLoopPerformBlock( CFRunLoopGetCurrent(), KCFRunLoopDefaultMode, lambda do lookup.delete(key) block.call end ) true end nil end end def debounce_seconds(seconds, unique_id, run_immediately=false, &block) if object = unsafe_unretained_object lookup = Thread.current["rmx_debounce_seconds"] ||= {} key = [ object, unique_id ] lookup[key] ||= begin block.call if run_immediately units = CFGregorianUnits.new units.seconds = seconds CFRunLoopAddTimer( CFRunLoopGetCurrent(), CFRunLoopTimerCreateWithHandler( KCFAllocatorDefault, CFAbsoluteTimeAddGregorianUnits( CFAbsoluteTimeGetCurrent(), nil, units ), 0, 0, 0, lambda do |timer| lookup.delete(key) block.call end ), KCFRunLoopDefaultMode ) true end nil end end end