19

I have a category on NSObject which supposed to so some stuff. When I call it on an object, I would like to override its dealloc method to do some cleanups.

I wanted to do it using method swizzling, but could not figure out how. The only examples I've found are on how to replace the method implementation for the entire class (in my case, it would override dealloc for ALL NSObjects - which I don't want to).

I want to override the dealloc method of specific instances of NSObject.

@interface NSObject(MyCategory)
-(void)test;
@end

@implementation NSObject(MyCategory)
-(void)newDealloc
{
  // do some cleanup here
  [self dealloc]; // call actual dealloc method
}
-(void)test
{
  IMP orig=[self methodForSelector:@selector(dealloc)];
  IMP repl=[self methodForSelector:@selector(newDealloc)];
  if (...)   // 'test' might be called several times, this replacement should happen only on the first call
  {
     method_exchangeImplementations(..., ...);
  }
}
@end
Gilad Novik
  • 4,546
  • 4
  • 41
  • 58

3 Answers3

20

You can't really do this since objects don't have their own method tables. Only classes have method tables and if you change those it will affect every object of that class. There is a straightforward way around this though: Changing the class of your object at runtime to a dynamically created subclass. This technique, also called isa-swizzling, is used by Apple to implement automatic KVO.

This is a powerful method and it has its uses. But for your case there is an easier method using associated objects. Basically you use objc_setAssociatedObject to associate another object to your first object which does the cleanup in its dealloc. You can find more details in this blog post on Cocoa is my Girlfriend.

Mike Morearty
  • 9,953
  • 5
  • 31
  • 35
Sven
  • 22,475
  • 4
  • 52
  • 71
  • Thanks for the answer. So basically, for each call - I allocate a new object (which I implement) and set it as the associated object and I do the cleanup inside its dealloc? – Gilad Novik Feb 29 '12 at 18:37
  • Exactly. Look at that blog post, it even has a nice category on `NSObject` that let's you register blocks to be called during dealloc of any object. – Sven Feb 29 '12 at 18:41
12

Method selection is based on the class of an object instance, so method swizzling affects all instances of the same class - as you discovered.

But you can change the class of an instance, but you must be careful! Here is the outline, assume you have a class:

@instance MyPlainObject : NSObject

- (void) doSomething;

@end

Now if for just some of the instances of MyPlainObject you'd like to alter the behaviour of doSomething you first define a subclass:

@instance MyFancyObject: MyPlainObject

- (void) doSomething;

@end

Now you can clearly make instances of MyFancyObject, but what we need to do is take a pre-existing instance of MyPlainObject and make it into a MyFancyObject so we get the new behaviour. For that we can swizzle the class, add the following to MyFancyObject:

static Class myPlainObjectClass;
static Class myFancyObjectClass;

+ (void)initialize
{
   myPlainObjectClass = objc_getClass("MyPlainObject");
   myFancyObjectClass = objc_getClass("MyFancyObject");
}

+ (void)changeKind:(MyPlainObject *)control fancy:(BOOL)fancy
{
   object_setClass(control, fancy ? myFancyObjectClass : myPlainObjectClass);
}

Now for any original instance of MyPlainClass you can switch to behave as a MyFancyClass, and vice-versa:

MyPlainClass *mpc = [MyPlainClass new];

...

// masquerade as MyFancyClass
[MyFancyClass changeKind:mpc fancy:YES]

... // mpc behaves as a MyFancyClass

// revert to true nature
[MyFancyClass changeKind:mpc: fancy:NO];

(Some) of the caveats:

You can only do this if the subclass overrides or adds methods, and adds static (class) variables.

You also need a sub-class for ever class you wish to change the behaviour of, you can't have a single class which can change the behaviour of many different classes.

CRD
  • 52,522
  • 5
  • 70
  • 86
  • But in that case, I have to know in advance what would be the class of that instance. In my case, the object can be anything (even an object created by the SDK itself), so I can't assume I'll know the type. – Gilad Novik Feb 29 '12 at 18:43
  • Yes, that's one of the caveats! If that is needed go the associated object route. But it if you are targeting specific classes swizzling is a bit (subjective of course) simpler than associated objects and more general as you can override any method easily. – CRD Feb 29 '12 at 18:48
  • Even though I've used an associated object as my solution, I appreciate your answer. I do have a question about it: since it's only useful to override/add methods - what's the different between this and a category? – Gilad Novik Feb 29 '12 at 19:03
  • A category adds methods to all instances of a class. The above method changes the behaviour of a single instance of a class by making it into a subclass of that class (subject to the caveats). – CRD Feb 29 '12 at 19:58
1

I made a swizzling API that also features instance specific swizzling. I think this is exactly what you're looking for: https://github.com/JonasGessner/JGMethodSwizzler

It works by creating a dynamic subclass for the specific instance that you're swizzling at runtime.

JonasG
  • 9,274
  • 12
  • 59
  • 88