MO_allSubclassesIncludingIndirect: |
+ ( NSArray *) MO_allSubclassesIncludingIndirect: (BOOL ) deepFlag;
Given a flag indicating whether all subclasses or only direct subclasses are wanted, this method returns an array of all the subclasses of the receiving class. If deepFlag is YES then all descendents of the receiving class are returned, if it is NO only direct subclasses are returned. The receiving class is NOT returned as part of the result. This method will not work on non-NSObject including NSProxy and its subclasses or the old Object class and its subclasses.
- deepFlag
- Whether to return all descendents or only direct subclasses.
MO_replaceFactorySelector:withMethodForSelector: |
+ ( IMP ) MO_replaceFactorySelector: (SEL ) replaceSel withMethodForSelector: (SEL ) newSel;
Given a selector to replace (replaceSel) and a selector to replace it with (newSel), this method will take the IMP of the newSel and make the class use it when replaceSel is invoked. The original IMP for replaceSel is returned. The class must respond to both selectors and the methods for them must have identical method signatures. The returned original IMP can be stored and invoked from the implementation of the replacement method to allow override-like calls to "super" within the replacing method. Note that a method could be replaced multiple times and, if all the replacements chain by invoking the original IMP returned by this method, all the methods will execute starting with the last replacement method and ending with the original method that was replaced by the first call to this method. Note also that if, previous to calling this method, a category was loaded that replaced the original class' implementation of the method (a normal "category-override"), then it is the category method's IMP that will be returned as the original IMP. Finally, note that if a bundle is later loaded with a category override of a method that was previously replaced using this method, the newly loaded category method will take effect, with no chaining, as usual. +initialize or +load are good places to invoke this method.
See the documentation for +MO_replaceInstanceSelector:withMethodForSelector: for an example of how these methods can be used.
- replaceSel
- The selector whose IMP is to be replaced.
- newSel
- The selector whose IMP is to replace the original IMP of replaceSel.
MO_replaceInstanceSelector:withMethodForSelector: |
+ ( IMP ) MO_replaceInstanceSelector: (SEL ) replaceSel withMethodForSelector: (SEL ) newSel;
Given a selector to replace (replaceSel) and a selector to replace it with (newSel), this method will take the IMP of the newSel and make the class use it when replaceSel is invoked. The original IMP for replaceSel is returned. Instances of the class must respond to both selectors and the methods for them must have identical method signatures. The returned original IMP can be stored and invoked from the implementation of the replacement method to allow override-like calls to "super" within the replacing method. Note that a method could be replaced multiple times and, if all the replacements chain by invoking the original IMP returned by this method, all the methods will execute starting with the last replacement method and ending with the original method that was replaced by the first call to this method. Note also that if, previous to calling this method, a category was loaded that replaced the original class' implementation of the method (a normal "category-override"), then it is the category method's IMP that will be returned as the original IMP. Finally, note that if a bundle is later loaded with a category override of a method that was previously replaced using this method, the newly loaded category method will take effect, with no chaining, as usual. +initialize or +load are good places to invoke this method.
An example might be useful. This example is simplified from a real use of this API in MOKit's NSView(MOSizing) category. Let's suppose we want to replace NSView's -setFrameSize: method with a new version, implemented under the name -replacementSetFrameSize: as a category on NSView. -replacementSetFrameSize: could modify the size and then call NSView's original -setFrameSize: through the returned original IMP.
First, we need a place to store the original IMP. Not only that, but we should declare the actual type of the IMP so that arguments and return values will work properly (remember that methods have two initial hidden arguments: self and _cmd):
typedef void (*SetFrameSizePrototype)(id self, SEL _cmd, NSSize newSize); static SetFrameSizePrototype _origSetFrameSizeIMP = NULL;
Now we need to implement the replacement method. In this implementation we just make the new size a bit bigger than requested. Notice that at the end of the replacement method the original IMP is invoked. This ensures that the original setFrameSize: gets called and is conceptually similar to calling super in a subclass override (invoking the original IMP does not have to happen at the end of the replacement method just as calling super in an override can be done at any point within the override method):
- (void)replacementSetFrameSize:(NSSize)newSize { newSize.width += 10.0; newSize.height += 10.0; if (_origSetFrameSizeIMP) { _origSetFrameSizeIMP(self, _cmd, newSize); } }
Finally, we need to perform the actual replacement. In this case, since we are implementing all this in a category on an existing class, we will use +load to do the replacement. +load is called separately in every class or category that implements it through special magic in the Objective-C runtime, so even if NSView itself has a +load and we "override" it in this category, both will still be called.
+ (void)load { static _hasReplaced = NO; if (!_hasReplaced) { _hasReplaced = YES; _origSetFrameSizeIMP = (SetFrameSizePrototype) [NSView MO_replaceInstanceSelector:selector(setFrameSize:) withMethodForSelector:selector(replacementSetFrameSize:)]; } }
Admittedly this is all a bit complicated. Method replacement of this sort, however, should be used very sparingly and one should always consider more straight-forward alternatives before doing it. Having said that, this mechanism uses only public API and structures from the Objective-C runtime in its implementation and should be relatively safe if used properly.
- replaceSel
- The selector whose IMP is to be replaced.
- newSel
- The selector whose IMP is to replace the original IMP of replaceSel.
(Last Updated 3/20/2005)