-
-
Save sohocoke/10333185 to your computer and use it in GitHub Desktop.
## standard boilerplate. | |
class AppDelegate | |
def applicationDidFinishLaunching(notification) | |
buildMenu | |
buildWindow | |
Story.begin | |
end | |
end | |
# this mixin attaches a property for other objects to observe via KVO. | |
module Sleepable | |
attr_accessor :sleepy | |
def sleep | |
log "going to sleep." | |
self.sleepy = true | |
end | |
end | |
class Watcher | |
attr_accessor :watching # what am i watching? | |
end | |
## begin our story. | |
module Story | |
def self.begin | |
## so now we assemble an object using the module. simple, object-oriented (cf. class-oriented) programming here. Would be really cool if it works! | |
# first we have a bear. | |
@the_bear = the_bear = Object.new | |
p "can the bear sleep? #{the_bear.respond_to? :sleep}" | |
# we teach him to be able to sleep, via a module. the idea is to build an object using modules of features. | |
the_bear.extend Sleepable | |
p "can the bear sleep after being extended? #{the_bear.respond_to? :sleep}" | |
# ok, now we have someone wanting to watch him fall sleep. nature documentary use case. | |
@camera_man = camera_man = Watcher.new # use a class so we can rule this one out, should things go funny. ideally, we'd like not to use silly class definitions like this. | |
camera_man.react_to :watching { p "Oh there, #{camera_man} sees a bear falling asleep." } | |
camera_man.watching = the_bear | |
## and so? can it happen? | |
the_bear.sleep # yes it does!! | |
## bear went to sleep, cameraman's watching, we have all our state. | |
## now, let's try extending the cameraman. | |
p "camera_man's watching: #{camera_man.watching}" | |
# let's mix in the feature to the camera man. we're able to reproduce using the extend method... | |
# camera_man.extend Sleepable | |
# or working with the eigenclass. | |
class << camera_man | |
include Sleepable | |
end | |
# and of course, before he goes to sleep, he should still be watching the bear. | |
p "he's still watching: #{camera_man.watching}" | |
## and this is where we confirm the bug -- the cameraman forgot what he was watching. | |
p "is he sleepy? #{camera_man.sleepy}" # everything gone! | |
## QED. sad story. | |
## until you guys can fix it!!!! ;) ;) ;) | |
## the main reason i'd like this fixed, is because it's greatly beneficial to add features to objects without thinking about their class definitions all the time. | |
## there are many ways around the #extend or eigenclass modification, but they result in much more cumbersome code. | |
## i'd greatly appreciate this being prioritised up. | |
## Thanks RubyMotion team! | |
end | |
end | |
## hope you've enjoyed that story. now, for some integration. | |
class Watcher | |
# bubble-wrap's implementation of kvo boilerplating is pretty straightforward and well done -- let's use that to allow the watcher to watch things and react. | |
include BubbleWrap::KVO | |
def react_to key_path, &handler | |
observe key_path, &handler | |
end | |
end | |
def log *args | |
NSLog *args | |
end | |
class AppDelegate | |
def buildWindow | |
@mainWindow = NSWindow.alloc.initWithContentRect([[240, 180], [480, 360]], | |
styleMask: NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask, | |
backing: NSBackingStoreBuffered, | |
defer: false) | |
@mainWindow.title = NSBundle.mainBundle.infoDictionary['CFBundleName'] | |
@mainWindow.orderFrontRegardless | |
end | |
end | |
### to be continued... @@page.address@@ |
I do love a good story :)
@alloy @lrz Glad you liked the story. I was originally writing a quick blog article to summarise my use of KVO as I thought it would be interesting to some, then took a detour as the whole story is in the vicinity of RM-391. Sadly, I just realised that is it in fact no more of a reduction than the one eloy summarised in the ticket based on my initial bug report. Ugh, slightly embarrassing...
Having said that, now I'm full of curiosity on this front. E.g. There was some mention in the 'orthodox' ruby community that using mixins this way would translate to extremely bad performance due to the code path clearing the method cache on some (most?) ruby implementations. How is RubyMotion implemented in this regard? Should the right response to the bug be in fact a recommendation not to extend objects the way it's done above? If so, what would be the mechanics dictating that conclusion?
Normally, I would be happy going through the full story by way of digging through the source code, but with RubyMotion, the best I could do is for the team to shine some light. I suppose that motivated me to write it up like this.
Well that's certainly the best bug reduction we ever received :-)