Re: [F-Script-talk] adding Blocks as methods to ObjC classes
Brought to you by:
pmougin
From: Ken F. <ken...@gm...> - 2005-02-15 08:05:45
|
I've been meaning to post a similar message, so I'll throw in my 2 cents. :-) I have a class that does something similar with a slightly different emphasis. KFDecorator <http://homepage.mac.com/kenferry/Software/KFDecorator/KFDecorator.zip> uses message forwarding + blocks to act like an abstract decorator, with some limitations. I think it's best explained through a couple of examples. I don't use it in release code, but I've found it to be pretty useful when using FScript (Anywhere) as part of the development environment for a cocoa application. It's good for debugging, exploration of ideas, and sometimes automated testing. The idea of a decorator is that it wraps a base object and modifies its behavior. The decorator catches specified messages and processes them, and any messages it doesn't catch are forwarded to the base object. It's a design pattern from the Gang of Four book. > dataDecorator := tableView dataSource decorator > dataDecorator numberOfRowsInTableView:tableView 312 > dataDecorator setBlock:[:self :table | 10] forSelector:#numberOfRowsInTableView: > dataDecorator setBlock:[:self :table :column :row | 'test'] forSelector:#tableView:objectValueForTableColumn:row: > dataDecorator <KFDecorator[0x4cf5b60] base:<TableViewManager: 0x4c67a0> maps:{ :#numberOfRowsInTableView: => [:self :table | 10] #tableView:objectValueForTableColumn:row: => [:self :table :column :row | 'test'] }> > dataDecorator numberOfRowsInTableView:tableView 10 > dataDecorator tableView:tableView objectValueForTableColumn:nil row:0 'test' > tableView setDataSource:dataDecorator The main interesting thing about the above example is that it shows the decorator translating argument and return types. The 'numberOfRowsInTableView:' method returns a genuine int, which makes the decorator more suitable for interacting with objective-c code that doesn't use objects. (Sounds like you did this too, Luke.) The class was written before the latest version of FScript came out, so it uses the deprecated Pointer class to do the dirty work. The argument and return types are determined from the method signature of the overridden base method, when there is such a base method. You can use a decorator without a base, in which case it acts more like a dynamically built object. > decorator := KFDecorator decorator > decorator setBlock:[:self :num | num + 4] forSelector:#addFourToNumber: > decorator <KFDecorator[0x4ca8c0] base:(null) maps:{ #addFourToNumber: => [:self :num | num + 4] }> > decorator addFourToNumber:5 9 When adding a block for a method not implemented by the base, all arguments and returns are assumed to be objects. There is a -[KFDecorator setBlock:forSelector:withSignature:] method for when that assumption isn't going to hold. You can also use KFDecorator to switch between existing objective-c methods that you'd like to evaluate. > decorator setBlock:#doStuff_alternateAlgorithm forSelector:#doStuff Where in a normal subclassing situation you would call 'super', with a decorator you can call 'self base'. The following would help you understand under what circumstances the numberOfRowsInTableView: method is called. > dataDecorator setBlock:[:self :table | sys beep. self base numberOfRowsInTableView:table] forSelector:#numberOfRowsInTableView: The two biggest limitations are: (1) You cannot set a block for a method implemented by KFDecorator itself, and KFDecorator is a subclass of NSObject as opposed to NSProxy. This is because I haven't worked around NSProxy's lack of a useful -[NSProxy methodSignatureForSelector:]. (2) Since it's just a wrapper for a base object, you can only catch messages when you interject the decorator between a calling object and the base. That can be a good thing or a bad thing in different situations. Okay, so now that that's out, here are some thoughts. :-) I do think that JGType steals some (most?) of the thunder here, but I haven't had much luck getting JGType to work. My app crashes whenever I try to use the dynamic subclassing. Can we get a little bit of sample code on that? You've probably noticed that the first argument of every block set for a selector is :self, which is a reference to the decorator object. In the first version of KFDecorator I had it set up so that all of the following worked: > decorator setBlock:[4] forSelector:#count > decorator setBlock:[ :self | 4] forSelector:#count > decorator setBlock:[ :self :cmd | 4 ] forSelector:#count I couldn't decide if it was confusing and error prone or useful, but eventually changed it to always require the second form. Just another thing to think about. Luke, I'd definitely be interested to see your version of things. We should exchange ideas. :-) You can download my code from the link above. -Ken |