在运行时向Objective-C类的实例添加方法

4
我想知道是否有一种方法可以向类的“实例”添加实例方法。
场景是我正在使用EKEventEditViewController,并且在这个类中有一个带有委托(UITableViewDelegate)的UITableView叫做“EKEventEditor”(非公共AFAIK)。它没有实现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个回答

3

这是你需要的函数。它将向类的所有实例添加(实例)方法。无法仅向特定实例添加方法。

你可能想要查看使用类别,它确实可以让你扩展一个你不控制@implementation的类。但是,只有在您可以导入类的主要@interface时,此方法才有效。听起来这可能并不是你的情况。


如果您想添加一个类方法,您需要将元类作为第一个参数调用该函数。
除了使用覆盖forwardInvocation:和可能的libffi技巧之外,我正在努力解决这个问题。

1
您可以使用与KVO相同的子类和交换技巧将方法添加到特定实例中,以动态覆盖单个对象的访问器。 - Chuck
@JoshCaswell 我编辑了问题并附上了我的代码,我做错了什么导致willDisplayCell_没有被调用吗? - Herman
@Herman:我不确定。在我看来,那似乎是正确的。可能是因为表视图没有发送该消息,因为它假定其委托没有实现它。 - jscs
@JoshCaswell 抱歉直到现在才看到您的评论。嗯...您认为我可以采取什么诡计来使其工作吗? - Herman
@Herman:你一定要在 [<#委托类的名称#> tableView:willDisplayCell:forRowAtIndexPath:] 上设置断点来检查一下。如果它没有被发送,我不知道你能做什么(合理的情况下)。你可能需要将 eventTableView 替换为自己的对象——这可能非常困难。但这可能值得另外发一个SO问题,我相信有人会有一两个想法。 - jscs
@JoshCaswell,是的,我认为我无法设置bp,因为EKEventEditor是私有的。我会发布另一个SO问题,感谢您的回复! - Herman

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
这是完全可能的,而且非常有用... 想象一下在不创建子类的情况下修改实例的功能等。请点击此处查看更多信息。
@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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接