在运行时将方法添加到objective-c类的实例

时间:2012-07-05 23:45:20

标签: objective-c runtime

我想知道是否有办法将实例方法添加到类的“实例”中。

场景是我正在使用EKEventEditViewController,并且在此类中有一个UITableView,其中有一个名为“EKEventEditor”(非公开AFAIK)的委托(UITableViewDelegate)。它没有实现tableView:willDisplayCell:forRowAtIndexPath:方法,我试图用来禁用某些单元格。

所以我试图在实例中添加一个方法,但是我在Obj-C运行时中找到的只是class_addMethod调用,它将方法添加到类而不是实例。由于“EKEventEditor”是私有的,我不能只是扩展它并自己添加该方法。

任何提示?

这是我正在使用的代码,我试图添加的函数(willDisplayCell_)没有被调用。

- (void)navigationController:(UINavigationController *)navigationController 
  willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {

if ([navigationController isKindOfClass:[EKEventEditViewController class]]) {

UITableViewController *rootController = (UITableViewController*)[(UINavigationController*)navigationController visibleViewController];
if ([rootController isKindOfClass:[UITableViewController class]]) {
  UITableView *eventTableView = (UITableView*)[rootController view];

  class_addMethod([eventTableView.delegate class], 
                  @selector(tableView:willDisplayCell:forRowAtIndexPath:), 
                  (IMP)willDisplayCell_, "v@:@@@");
}}} 


void willDisplayCell_(id self, SEL cmd, UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
  NSLog(@"CALLED?");
}

3 个答案:

答案 0 :(得分:3)

这就是你想要的功能。这会将(实例)方法添加到类的所有实例。*无法将方法添加到一个特定实例。**

您可能希望使用Category,但 允许您扩展您无法控制@implementation的类。这只有在你可以导入类的主要@interface时才有效,但听起来可能并非如此。


*如果要添加类方法,可以使用元类作为第一个参数调用该函数。

**禁止覆盖forwardInvocation:以及可能是libffi的一些技巧。我正在努力。

答案 1 :(得分:2)

以下是REKit。它使您能够在实例级别添加/覆盖方法。

使用REKit,您可以将方法添加到eventTableView.delegate,如下所示:

[eventTableView.delegate
    respondsToSelector:@selector(tableView:willDisplayCell:forRowAtIndexPath:)
    withKey:nil
    usingBlock:^(id receiver, UITableView *tableView, UITableViewCell *cell, NSIndexPath *indexPath) {
        // Do something…
    }
];

答案 2 :(得分:2)

This is totally possible, and amazingly useful ...考虑改变实例的功能而不进行子类化等...

@interface NSObject (AZOverride)
/** Dynamically overrides the specified method on this particular instance.
* The block's parameters and return type must match those of the method you 
* are overriding. However, the first parameter is always "id _self", which 
* points to the object itself.
* You do have to cast the block's type to (__bridge void *), e.g.:
*
*    [self az_overrideSelector:@selector(viewDidAppear:)
*            withBlock:(__bridge void *)^(id _self, BOOL animated) { ... }];
*/
- (BOOL)az_overrideSelector:(SEL)selector withBlock:(void *)block;
/** To call super from the overridden method, do the following:
*    SEL sel = @selector(viewDidAppear:);
*    void (*superIMP)(id, SEL, BOOL) = [_self az_superForSelector:sel];
*    superIMP(_self, sel, animated);
* This first gets a function pointer to the super method and then you call it.
*/
- (void *)az_superForSelector:(SEL)selector;
@end