Conversation
|
and wrappers used to wrap Cocoa Touch and AppKit code and provide more Ruby like APIs. Installationgem install bubble-wrapSetup
require 'bubble-wrap'If you use Bundler: gem 'bubble-wrap', '~> 1.9.7'BubbleWrap is split into multiple modules so that you can easily choose which parts are included at compile-time. If you wish to only include the require 'bubble-wrap/rss_parser'If you wish to only include the require 'bubble-wrap/reactor'If you wish to only include the UI-related wrappers: require 'bubble-wrap/ui'If you wish to only include the require 'bubble-wrap/camera'If you wish to only include the require 'bubble-wrap/location'If you wish to only include the require 'bubble-wrap/media'If you wish to only include the require 'bubble-wrap/mail'If you wish to only include the require 'bubble-wrap/sms'If you wish to only include the require 'bubble-wrap/motion'If you wish to only include the require 'bubble-wrap/network-indicator'If you want to include everything (ie kitchen sink mode) you can save time and do: require 'bubble-wrap/all'You can also do this directly in your gem 'bubble-wrap', require: %w[bubble-wrap/core bubble-wrap/location, bubble-wrap/reactor]Note: DON'T use
class AppDelegate
def application(application, didFinishLaunchingWithOptions:launchOptions)
puts "#{App.name} (#{App.documents_path})"
true
end
endNote: You can also vendor this repository but the recommended way is to CoreMiscUUID generator: BubbleWrap.create_uuid
=> "68ED21DB-82E5-4A56-ABEB-73650C0DB701"Localization (using BubbleWrap.localized_string(:foo, 'fallback')
=> "fallback"Color conversion: BubbleWrap.rgba_color(23, 45, 12, 0.4)
=> #<UIDeviceRGBColor:0x6db6ed0>
BubbleWrap.rgb_color(23, 45, 12)
=> #<UIDeviceRGBColor:0x8ca88b0>
'blue'.to_color
=> #<UICachedDeviceRGBColor:0xda535c0>
'dark_gray'.to_color
=> #<UICachedDeviceWhiteColor:0x8bb5be0>
'#FF8A19'.to_color
=> #<UIDeviceRGBColor:0x8d54110>
'#88FF8A19'.to_color # ARGB format
=> #<UIDeviceRGBColor:0xca0fe00>Debug flag: BubbleWrap.debug?
=> false
BubbleWrap.debug = true
=> true
BubbleWrap.debug?
=> trueAppA module with useful methods related to the running application > App.documents_path
# "/Users/mattetti/Library/Application Support/iPhone Simulator/5.0/Applications/EEC6454E-1816-451E-BB9A-EE18222E1A8F/Documents"
> App.resources_path
# "/Users/mattetti/Library/Application Support/iPhone Simulator/5.0/Applications/EEC6454E-1816-451E-BB9A-EE18222E1A8F/testSuite_spec.app"
> App.name
# "testSuite"
> App.identifier
# "io.bubblewrap.testSuite"
> App.alert("BubbleWrap is awesome!")
# creates and shows an alert message.
> App.alert("BubbleWrap is awesome!", {cancel_button_title: "I know it is!", message: "Like, seriously awesome."})
# creates and shows an alert message with optional parameters.
> App.run_after(0.5) { p "It's #{Time.now}" }
# Runs the block after 0.5 seconds.
> App.open_url("http://matt.aimonetti.net")
> App.open_url("tel://123456789")
# Opens the url using the device's browser. Can also open custom URL schemas (accepts a string url or an instance of `NSURL`.)
> App.can_open_url("tel://")
# Returns whether the app can open a given URL resource.
> App::Persistence['channels'] # application specific persistence storage
# ['NBC', 'ABC', 'Fox', 'CBS', 'PBS']
> App::Persistence['channels'] = ['TF1', 'France 2', 'France 3']
# ['TF1', 'France 2', 'France 3']
> App.environment
# 'test'Other available methods:
DeviceA collection of useful methods about the current device: Examples: > Device.iphone?
# true
> Device.ipad?
# false
> Device.camera.front?
# true
> Device.camera.rear?
# true
> Device.orientation
# :portrait
> Device.interface_orientation
# :portrait
> Device.simulator?
# true
> Device.ios_version
# "6.0"
> Device.retina?
# false
> Device.screen.width
# 320
> Device.screen.height
# 480
> Device.screen.width_for_orientation(:landscape_left)
# 480
> Device.screen.height_for_orientation(:landscape_left)
# 320
> Device.vendor_identifier
# <NSUUID>CameraAdded interface for better camera access: # Uses the front camera
BW::Device.camera.front.picture(media_types: [:movie, :image]) do |result|
image_view = UIImageView.alloc.initWithImage(result[:original_image])
end
# Uses the rear camera
BW::Device.camera.rear.picture(media_types: [:movie, :image]) do |result|
image_view = UIImageView.alloc.initWithImage(result[:original_image])
end
# Uses the photo library
BW::Device.camera.any.picture(media_types: [:movie, :image]) do |result|
image_view = UIImageView.alloc.initWithImage(result[:original_image])
end
# Lets the user edit the photo (with access to the edited and original photos)
BW::Device.camera.any.picture(allows_editing: true, media_types: [:image]) do |result|
edited_image_view = UIImageView.alloc.initWithImage(result[:edited_image])
original_image_view = UIImageView.alloc.initWithImage(result[:original_image])
end
# Capture a low quality movie with a limit of 10 seconds
BW::Device.camera.front.picture(media_types: [:movie], video_quality: :low, video_maximum_duration: 10) do |result|
video_file_path = result[:media_url]
endOptions include:
JSON
BW::JSON.generate({'foo' => 1, 'bar' => [1,2,3], 'baz' => 'awesome'})
=> "{\"foo\":1,\"bar\":[1,2,3],\"baz\":\"awesome\"}"
BW::JSON.parse "{\"foo\":1,\"bar\":[1,2,3],\"baz\":\"awesome\"}"
=> {"foo"=>1, "bar"=>[1, 2, 3], "baz"=>"awesome"}NSIndexPathHelper methods added to give index_path = table_view.indexPathForCell(cell)
index_path + 1 # NSIndexPath for next cell in the same section
=> #<NSIndexPath:0x120db8e0>NSNotificationCenterHelper methods to give NSNotificationCenter a Ruby-like interface: def viewWillAppear(animated)
@foreground_observer = App.notification_center.observe UIApplicationWillEnterForegroundNotification do |notification|
loadAndRefresh
end
@reload_observer = App.notification_center.observe 'ReloadNotification' do |notification|
loadAndRefresh
end
end
def viewWillDisappear(animated)
App.notification_center.unobserve @foreground_observer
App.notification_center.unobserve @reload_observer
end
def reload
App.notification_center.post 'ReloadNotification'
endNSUserDefaultsHelper methods added to the class repsonsible for user preferences used PersistenceOffers a way to persist application specific information using a very > App::Persistence['channels'] # application specific persistence storage
# ['NBC', 'ABC', 'Fox', 'CBS', 'PBS']
> App::Persistence['channels'] = ['TF1', 'France 2', 'France 3']
# ['TF1', 'France 2', 'France 3']
> App::Persistence.delete('channels')
# ['TF1', 'France 2', 'France 3']
> App::Persistence['something__new'] # something previously never stored
# nil
> App::Persistence.all
# {'all':'values', 'stored':'by', 'bubblewrap':'as a hash!'}ObserversSince: > version 0.4 You can observe for object's changes and trigger blocks: class ExampleViewController < UIViewController
include BW::KVO
def viewDidLoad
@label = UILabel.alloc.initWithFrame [[20,20],[280,44]]
@label.text = ""
view.addSubview @label
observe(@label, :text) do |old_value, new_value|
puts "Hello from viewDidLoad!"
end
end
def viewDidAppear(animated)
observe(@label, :text) do |old_value, new_value|
puts "Hello from viewDidAppear!"
end
end
endYou can remove observers using Since: > version 1.9.0 Optionally, multiple key paths can be passed to the class ExampleViewController < UIViewController
include BW::KVO
def viewDidLoad
@label = UILabel.alloc.initWithFrame [[20,20],[280,44]]
@label.text = ""
view.addSubview @label
observe(@label, [:text, :textColor]) do |old_value, new_value, key_path|
puts "Hello from viewDidLoad for #{key_path}!"
end
end
endAlso you can use StringThe Ruby > "matt_aimonetti".camelize
=> "MattAimonetti"
> "MattAimonetti".underscore
=> "matt_aimonetti"TimeThe > Time.iso8601("2012-05-31T19:41:33Z")
=> 2012-05-31 21:41:33 +0200LocationInterface for Ruby-like GPS and compass access (the CoreLocation framework): > BW::Location.enabled? # Whether location services are enabled on the device
=> true
> BW::Location.authorized? # If your app is authorized to use location services
=> falseBW::Location.get(purpose: 'We need to use your GPS because...') do |result|
p "From Lat #{result[:from].latitude}, Long #{result[:from].longitude}"
p "To Lat #{result[:to].latitude}, Long #{result[:to].longitude}"
endNote: The BW::Location.get_compass do |result|
p result[:magnetic_heading] # Heading towards magnetic north
p result[:true_heading] # Heading towards true north
p result[:accuracy] # Potential error between magnetic and true heading
p result[:timestamp] # Timestamp of the heading calculation
end
BW::Location.get_once(desired_accuracy: :three_kilometers, ...) do |result|
if result.is_a?(CLLocation)
p result.coordinate.latitude
p result.coordinate.longitude
else
p "ERROR: #{result[:error]}"
end
end
BW::Location.get_compass_once do |heading|
p result[:magnetic_heading]
p result[:true_heading]
p result[:accuracy]
p result[:timestamp]
endiOS 8 Location RequirementsiOS 8 introduced stricter location services requirements. Although BubbleWrap will handle most of this for you automatically, you are required to add a few key/value pairs to the app.info_plist['NSLocationAlwaysUsageDescription'] = 'Description'
app.info_plist['NSLocationWhenInUseUsageDescription'] = 'Description'Note: you need both keys to use MotionInterface for the accelerometer, gyroscope, and magnetometer sensors (the Each sensor has an The
If you pass a string instead, a new queue will be created and its The AccelerometerBW::Motion.accelerometer.available?
BW::Motion.accelerometer.data # returns CMAccelerometerData object or nil
# ask the CMMotionManager to update every 5 seconds
BW::Motion.accelerometer.every(5) do |result|
# result contains the following data (from CMAccelerometerData#acceleration):
p result[:data] # the CMAccelerometerData object
p result[:acceleration] # the CMAcceleration struct
p result[:x] # acceleration in the x direction
p result[:y] # " y direction
p result[:z] # " z direction
end
# every, start, and repeat all need to be stopped later.
BW::Motion.accelerometer.stop
# repeat, but don't set the interval
BW::Motion.accelerometer.repeat do |result|
end
# you can specify a :queue where the operations will be executed. See above for details
BW::Motion.accelerometer.every(5, queue: :background) { |result| ... }
BW::Motion.accelerometer.every(5, queue: :main) { |result| ... }
BW::Motion.accelerometer.every(5, queue: :current) { |result| ... }
BW::Motion.accelerometer.every(5, queue: 'my queue') { |result| ... }
BW::Motion.accelerometer.once do |result|
# ...
endGyroscopeBW::Motion.gyroscope.available?
BW::Motion.gyroscope.data # returns CMGyroData object or nil
# ask the CMMotionManager to update every second.
BW::Motion.gyroscope.every(1) do |result|
# result contains the following data (from CMGyroData#rotationRate):
p result[:data] # the CMGyroData object
p result[:rotation] # the CMRotationRate struct
p result[:x] # rotation in the x direction
p result[:y] # " y direction
p result[:z] # " z direction
end
BW::Motion.gyroscope.stop
BW::Motion.gyroscope.once do |result|
# ...
endMagnetometerBW::Motion.magnetometer.available?
BW::Motion.magnetometer.data # returns CMMagnetometerData object or nil
# ask the CMMotionManager to update every second
BW::Motion.magnetometer.every(1) do |result|
# result contains the following data (from CMMagnetometerData#magneticField):
p result[:data] # the CMMagnetometerData object
p result[:field] # the CMMagneticField struct
p result[:x] # magnetic field in the x direction
p result[:y] # " y direction
p result[:z] # " z direction
end
BW::Motion.magnetometer.stop
BW::Motion.magnetometer.once do |result|
# ...
endDevice MotionThis is an amalgam of all the motion sensor data. BW::Motion.device.available?
BW::Motion.device.data # returns CMDeviceMotion object or nil
BW::Motion.device.every(1) do |result|
# result contains the following data:
p result[:data] # the CMDeviceMotion object
# orientation data, from CMDeviceMotion#attitude
p result[:attitude] # the CMAttitude struct
p result[:roll]
p result[:pitch]
p result[:yaw]
# rotation data, from CMDeviceMotion#rotationRate
p result[:rotation] # the CMRotationRate struct
p result[:rotation_x]
p result[:rotation_y]
p result[:rotation_z]
# gravity+acceleration vector, from CMDeviceMotion#gravity
p result[:gravity] # the CMAcceleration struct
p result[:gravity_x]
p result[:gravity_y]
p result[:gravity_z]
# just the acceleration vector, from CMDeviceMotion#userAcceleration
p result[:acceleration] # the CMAcceleration struct
p result[:acceleration_x]
p result[:acceleration_y]
p result[:acceleration_z]
# the magnetic data, from CMDeviceMotion#magneticField
p result[:magnetic] # the CMCalibratedMagneticField struct
p result[:magnetic_field] # the CMMagneticField struct from the CMCalibratedMagneticField
p result[:magnetic_x]
p result[:magnetic_y]
p result[:magnetic_z]
p result[:magnetic_accuracy] # this will be a symbol, :low, :medium, :high, or nil if the magnetic data is uncalibrated
# less useful data from CMAttitude, unless you're into the whole linear algebra thing:
p result[:matrix] # CMAttitude#rotationMatrix
p result[:quarternion] # CMAttitude#quarternion
end
# the reference frame should be one of the CMAttitudeReferenceFrame constants...
ref = CMAttitudeReferenceFrameXArbitraryZVertical
# ... or one of these symbols: :arbitrary_z, :corrected_z, :magnetic_north, :true_north
ref = :corrected_z
BW::Motion.device.every(1, queue: :background, reference: ref) { |result| ... }
BW::Motion.device.once do |result|
# ...
endMediaAdded wrapper for playing remote and local media. Available are # Plays in your custom frame
local_file = NSURL.fileURLWithPath(File.join(NSBundle.mainBundle.resourcePath, 'test.mp3'))
BW::Media.play(local_file) do |media_player|
media_player.view.frame = [[10, 100], [100, 100]]
self.view.addSubview media_player.view
end
# Plays in an independent modal controller
BW::Media.play_modal("http://www.hrupin.com/wp-content/uploads/mp3/testsong_20_sec.mp3")Wrapper for showing an in-app mail composer view. You should always determine if the device your app is running on is configured to send mail before displaying a mail composer window. # Opens as a modal in the current UIViewController
BW::Mail.compose(
delegate: self, # optional, defaults to rootViewController
to: [ "tom@example.com" ],
cc: [ "itchy@example.com", "scratchy@example.com" ],
bcc: [ "jerry@example.com" ],
html: false,
subject: "My Subject",
message: "This is my message. It isn't very long.",
animated: false
) do |result, error|
result.sent? # => boolean
result.canceled? # => boolean
result.saved? # => boolean
result.failed? # => boolean
error # => NSError
endSMSWrapper for showing an in-app message (SMS) composer view. You should always determine if the device your app is running on can send SMS messages before displaying a SMS composer window. # Opens as a modal in the current UIViewController
BW::SMS.compose (
{
delegate: self, # optional, will use root view controller by default
to: [ "1(234)567-8910" ],
message: "This is my message. It isn't very long.",
animated: false
}) {|result, error|
result.sent? # => boolean
result.canceled? # => boolean
result.failed? # => boolean
error # => NSError
}NetworkIndicatorWrapper for showing and hiding the network indicator (the status bar spinner). BW::NetworkIndicator.show # starts the spinner
BW::NetworkIndicator.hide # stops it
# the nice thing is if you call 'show' multiple times, the 'hide' method will
# not have any effect until you've called it the same number of times.
BW::NetworkIndicator.show
# ...somewhere else
BW::NetworkIndicator.show
# ...down the line
BW::NetworkIndicator.hide
# indicator is still visible
BW::NetworkIndicator.hide
# NOW the indicator is hidden!
# If you *really* want to hide the indicator immediately, you can call `reset!`
# but this is in no way encouraged.
BW::NetworkIndicator.reset!
# and for completeness, a check to see if the indicator is visible
BW::NetworkIndicator.visible?UIGesturesExtra methods on view.when_tapped do
UIView.animateWithDuration(1,
animations:lambda {
# animate
# @view.transform = ...
})
endThere are similar methods for In order to prevent retain cycles due to strong references within the passed block, use the use_weak_callbacks flag so the blocks do not retain a strong reference to self: BubbleWrap.use_weak_callbacks = trueUIViewControllerA custom method was added to UIControl / UIButtonHelper methods to give button.when(UIControlEventTouchUpInside) do
self.view.backgroundColor = UIColor.redColor
endThe button.when(UIControlEventTouchUpInside | UIControlEventTouchUpOutside) do
self.view.backgroundColor = UIColor.redColor
endYou can use symbols for events (but won't work with the bitwise operator): button.when(:touch_up_inside) do
self.view.backgroundColor = UIColor.redColor
end
button.when(:value_changed) do
self.view.backgroundColor = UIColor.blueColor
endSet the use_weak_callbacks flag so the blocks do not retain a strong reference to self: BubbleWrap.use_weak_callbacks = trueUIBarButtonItem
ConstructorsInstead specifying a target-action pair, each constructor method accepts an optional block. When the button is tapped, the block is executed. BW::UIBarButtonItem.system(:save) do
# ...
end
title = "Friends"
BW::UIBarButtonItem.styled(:plain, title) do
# ...
end
image = UIImage.alloc.init
BW::UIBarButtonItem.styled(:bordered, image) do
# ...
end
image = UIImage.alloc.init
landscape = UIImage.alloc.init
BW::UIBarButtonItem.styled(:bordered, image, landscape) do
# ...
end
view = UIView.alloc.init
BW::UIBarButtonItem.custom(view) do
# ...
end
# NOTE: The block is attached to the view as a single tap gesture recognizer.The options = { :system => :save }
BW::UIBarButtonItem.new(options) do
# ...
end
options = { :styled => :plain, :title => "Friends" }
BW::UIBarButtonItem.new(options) do
# ...
end
options = { :styled => :bordered, :image => UIImage.alloc.init }
BW::UIBarButtonItem.new(options) do
# ...
end
options = {
:styled => :bordered,
:image => UIImage.alloc.init,
:landscape => UIImage.alloc.init
}
BW::UIBarButtonItem.new(options) do
# ...
end
options = { :custom => UIView.alloc.init }
BW::UIBarButtonItem.new(options) do
# ...
end
# NOTE: The block is attached to the view as a single tap gesture recognizer.Button typesThe :plain
:bordered
:doneAnd the :done
:cancel
:edit
:save
:add
:flexible_space
:fixed_space
:compose
:reply
:action
:organize
:bookmarks
:search
:refresh
:stop
:camera
:trash
:play
:pause
:rewind
:fast_forward
:undo
:redo
:page_curlUIActivityViewController
You can initiate a # Without a completion handler
BW::UIActivityViewController.new(
items: "Some Text", # or ["Some Text", NSURL.URLWithString('http://www.rubymotion.com')] or a UIImage
animated: true, # Defaults to true
excluded: :add_to_reading_list # One item or an array
)
# With completion handler
BW::UIActivityViewController.new(
items: "Some Text",
animated: true,
excluded: [:add_to_reading_list, :print, :air_drop]
) do |activity_type, completed|
puts "completed with activity: #{activity_type} - finished?: #{completed}"
endBuilt in activities that can be passed to the :post_to_facebook
:post_to_twitter
:post_to_weibo
:message
:mail
:print
:copy_to_pasteboard
:assign_to_contact
:save_to_camera_roll
:add_to_reading_list
:post_to_flickr
:post_to_vimeo
:post_to_tencent_weibo
:air_dropRSS ParserSince: > version 1.0.0 The RSS Parser provides an easy interface to consume RSS feeds in an feed_parser = BW::RSSParser.new("http://feeds2.feedburner.com/sdrbpodcast")
feed_parser.parse do |item|
# called asynchronously as items get parsed
p item.title
endThe yielded RSS item is of type
The item can be converted into a hash by calling DelegateSince: > version 1.0.0 You can also designate a delegate to the parser and implement change feed_parser = BW::RSSParser.new("http://feeds.feedburner.com/sdrbpodcast")
feed_parser.delegate = self
feed_parser.parse do |item|
p item.title
end
# Delegate method
def when_parser_initializes
p "The parser is ready!"
end
def when_parser_parses
p "The parser started parsing the document"
end
def when_parser_is_done
p "The feed is entirely parsed, congratulations!"
end
def when_parser_errors
p "The parser encountered an error"
ns_error = feed_parser.parserError
p ns_error.localizedDescription
endThese delegate methods are optional, however, you might find the Parsing a remote content or actual dataYou have the choice to initialize a parser instance with a string # string representing an url:
feed_parser = BW::RSSParser.new("http://feeds2.feedburner.com/sdrbpodcast")
# a NSURL instance:
url = NSURL.alloc.initWithString("http://matt.aimonetti.net/atom.xml")
feed_parser = BW::RSSParser.new(url)
# Some data
feed = File.read('atom.xml')
feed_parser = BW::RSSParser.new(feed, true)ReactorSince: > version 1.0.0
DeferablesBubbleWrap provides both a A deferrable is an object with four states: unknown, successful, failure Using By default, callbacks will be made on the thread that the deferrable Success> d = EM::DefaultDeferrable.new
=> #<BW::Reactor::DefaultDeferrable:0x6d859a0>
> d.callback { |what| puts "Great #{what}!" }
=> [#<Proc:0x6d8a1e0>]
> d.succeed "justice"
Great justice!
=> nilFailure> d = EM::DefaultDeferrable.new
=> #<BW::Reactor::DefaultDeferrable:0x8bf3ee0>
> d.errback { |what| puts "Great #{what}!" }
=> [#<Proc:0x8bf3ef0>]
> d.fail "sadness"
Great sadness!
=> nilDelegate> d = EM::DefaultDeferrable.new
=> #<BW::Reactor::DefaultDeferrable:0x8bf3ee0>
> delegate = EM::DefaultDeferrable.new
=> #<BW::Reactor::DefaultDeferrable:0x8bf5910>
> d.delegate delegate
=> #<BW::Reactor::DefaultDeferrable:0x8bf3ee0>
> delegate.callback { |*args| puts args }
=> [#<Proc:0x8bf3ef0>]
> d.succeed :passed
=> nil
=> [:passed]DependentDeferrable
> d1 = EM::DefaultDeferrable.new
=> #<BubbleWrap::Reactor::DefaultDeferrable:0x10c713750>
> d2 = EM::DefaultDeferrable.new
=> #<BubbleWrap::Reactor::DefaultDeferrable:0x10370bb10>
> d = EM::DependentDeferrable.on(d1, d2)
=> #<BubbleWrap::Reactor::DependentDeferrable:0x106c17b80>
> d.callback {|a, b| puts "a: #{a} b: #{b}"}
=> [#<Proc:0x103075210>]
> d1.succeed 'one', 'one more'
> d2.succeed :two
a: ["one", "one more"] b: [:two]ThreadAwareDeferrable> d = EM::ThreadAwareDeferrable.new
=> #<BW::Reactor::ThreadAwareDeferrable:0x8bf3ee0>
> queue = Dispatch::Queue.new(:deferrable.to_s)
> queue.async do
> d.callback do |*args|
> Dispatch::Queue.current == queue
> => true # this is normally false
> end
> end
> d.succeed trueTimeout> d = EM::DefaultDeferrable.new
=> #<BW::Reactor::DefaultDeferrable:0x8bf5910>
> d.errback { puts "Great scott!" }
=> [#<Proc:0x8bf6350>]
> d.timeout 2
=> #<BW::Reactor::Timer:0x6d920a0 @timer=#<__NSCFTimer:0x6d91990>>
# wait...
> Great scott!TimersAll timers can be cancelled using One-shot timers> EM.add_timer 1.0 do
> puts "Great scott!"
> end
=> 146335904
> Great scott!Periodic timers> count = 0
=> 0
> timer = EM.add_periodic_timer 1.0 do
> count = count + 1
> puts "Great scott!"
> (count < 10) || EM.cancel_timer(timer)
> end
=> 146046832
> Great scott!
Great scott!
Great scott!
Great scott!
Great scott!
Great scott!
Great scott!
Great scott!
Great scott!
Great scott!Scheduling operationsYou can use > EM.schedule { puts Thread.current.object_id }
146027920
=> nil
> EM.schedule_on_main { puts Thread.current.object_id }
112222480
=> nilDeferrable operationsYou can also use > operation = proc { 88 }
=> #<Proc:0x6d763c0>
> callback = proc { |speed| puts speed >= 88 ? "Time travel!" : "Conventional travel!" }
=> #<Proc:0x8bd3910>
> EM.defer(operation, callback)
=> nil
Time travel!EventsAlthough not part of the EventMachine API, BubbleWrap provides > o = Class.new { include EM::Eventable }.new
=> #<#<Class:0xab63f00>:0xab64430>
> o.on(:november_5_1955) { puts "Ow!" }
=> [#<Proc:0xad9bf00>]
> flux = proc{ puts "Flux capacitor!" }
=> #<Proc:0xab630f0>
> o.on(:november_5_1955, &flux)
=> [#<Proc:0xad9bf00>, #<Proc:0xab630f0>]
> o.trigger(:november_5_1955)
Ow!
Flux capacitor!
=> [nil, nil]
> o.off(:november_5_1955, &flux)
=> #<Proc:0xab630f0>
> o.trigger(:november_5_1955)
Ow!
=> [nil]
> o.on(:november_5_1955) { puts "Ow!" }
> o.on(:november_5_1955) { puts "Another Ow!" }
> o.off(:november_5_1955)
=> nilContributingDo you have a suggestion for a specific wrapper? Feel free to open an
|


R