4

I am wondering if there is a way to add an instance method to an "instance" of a class.

The scenario is that I am using EKEventEditViewController and there is a UITableView inside this class with a delegate(UITableViewDelegate) called "EKEventEditor" (non-public AFAIK). It does not implements the tableView:willDisplayCell:forRowAtIndexPath: method, which I am trying to use to disable some cell.

So I am trying to add a method to the instance, but all I can find in the Obj-C runtime is the class_addMethod call which adds method to the class but not the instance. With "EKEventEditor" being private, I can't just extend it and add that method myself.

Any hints?

Here is the code that I am using, the function that i am trying to add (willDisplayCell_) is not getting called.

- (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 Answers 3

3

That's the function you want. That will add the (instance) method to all instances of the class.* There's no way to add a method just to one particular instance.**

You might want to look into using a Category, however, which does let you extend a class whose @implementation you don't control. This will only work if you can import the class's primary @interface, though, and it sounds like that may not be the case.


*If you wanted to add a class method, you would call the function with the metaclass as the first argument.

**Barring some trickery with overriding forwardInvocation: and possibly libffi, which I'm working on.

Sign up to request clarification or add additional context in comments.

6 Comments

You could add a method to one particular instance using the same subclass-and-swizzle trickery that KVO uses to dynamically override the accessors of individual objects.
@JoshCaswell I edited the question with my code, am I doing something wrong such that willDisplayCell_ is not called?
@Herman: I'm not sure. That looks right to me. It may be that the table view isn't sending that message, since it assumes that its delegate doesn't implement it.
@JoshCaswell sorry didn't see the comment until now. hm.... any trickery you think I can do to make this work?
@Herman: You should definitely put a breakpoint on [<#Whatever the class of the delegate is#> tableView:willDisplayCell:forRowAtIndexPath:] to check that out. If it isn't getting sent, I don't know what you could (reasonably) do. You'd have to swap the eventTableView out for your own object -- likely to be extremely difficult. That would probably be worth its own SO question, though; I'm sure someone would have an idea or two.
|
2

This is totally possible, and amazingly useful... Think altering the functionality of an instance without subclassing, etc...

@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

Comments

2

Here is REKit for you. It gives you ability to add/override method at instance level.

With REKit, you can add the method to eventTableView.delegate like below:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.