Skip to content

Instantly share code, notes, and snippets.

@sohocoke
Last active August 29, 2015 13:58
Show Gist options
  • Save sohocoke/10333185 to your computer and use it in GitHub Desktop.
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@@
@sohocoke
Copy link
Author

Haha, what a wild speculation that one is.

@sohocoke
Copy link
Author

And here's the output.

     Build ./build/MacOSX-10.9-Development
   Compile ./app/app_delegate.rb
      Link ./build/MacOSX-10.9-Development/kvo-patterns.app/Contents/MacOS/kvo-patterns
    Create ./build/MacOSX-10.9-Development/kvo-patterns.dSYM
       Run ./build/MacOSX-10.9-Development/kvo-patterns.app/Contents/MacOS/kvo-patterns
"can the bear sleep? false"
"can the bear sleep after being extended? true"
"Oh there, #<NSKVONotifying_Watcher:0x7faf40f4ee90> sees a bear falling asleep."
2014-04-10 03:02:53.616 kvo-patterns[4798:303] going to sleep.
"camera_man's watching: #<NSObject:0x7faf40eac8e0>"
"he's still watching: "
"is he sleepy? "
(main)> repl Story                                                              => Story
(Story)> @camera_man 
=> #<NSKVONotifying_Watcher:0x7faf40f4ee90>
(Story)> @camera_man.class.ancestors
=> [NSKVONotifying_Watcher, Watcher, BubbleWrap::KVO, NSObject, Kernel]
(Story)> @camera_man.instance_variables
=> []

@lrz
Copy link

lrz commented Apr 10, 2014

Well that's certainly the best bug reduction we ever received :-)

@alloy
Copy link

alloy commented Apr 10, 2014

I do love a good story :)

@sohocoke
Copy link
Author

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment