简体   繁体   中英

Objective C: swizzle object method after creation

I would like to swizzle an object method after the object has been created.

Example:

@interface MyObj: NSObject
-(NSString*) foo;
-(NSString*) bar;
@end

@implementation MyObj
-(NSString*) foo {return @"foo";}
-(NSString*) bar {return @"bar";}
@end

void Swizzle(object, SEL source, SEL, dest);

...

MyObj* obj = [[[MyObj alloc] init] autorelease];

NSLog(@"%@", [obj foo]);
Swizzle(obj, @selector(foo), @selector(bar));
NSLog(@"%@", [obj foo]);

Output:

foo
bar

I have looked at lots of examples of swizzling before an object is created. However as you can see I want to swizzle after an object is created.

This is because I want to track objects that are going to release (like NSThread). I don't want to swizzle NSObject dealloc because that seems overkill. I would rather swizzle only the objects I am trying to track.

This honestly seems like an ill-advised attempt to avoid using Instruments. If so, the correct answer is "Just use Instruments."

But to actually answer the question: You can't do this with individual objects. Methods — even instance methods — are defined per-class, not per-object. The only way to override a method on a per-object basis is to dynamically create a subclass of the object's class that overrides the method you're interested in and isa-swizzle the object so it uses the subclass's lookup table. (This is basically how KVO does its magic notifications under the hood.) Again, this is a bit of a hack and generally a lot more trouble than it is worth, so before you do this, I would really think twice (or three times) about whether this is really necessary. For almost any use case I can come up with, there's a cleaner way to accomplish the same goal that does not involve runtime voodoo.

You cannot swizzle an object, only a specific class. Also, the implementation for a selector is cached, and that cache might not be cleared automatically when you swizzle, which would mean the new method is not used.

I would suggest overriding (or swizzling) dealloc in the class you want to track, and use a property to determine if your tracking code should be performed.

@property (nonatomic) BOOL shouldTrackDealloc;

@synthesize shouldTrackDealloc;
- (void)dealloc {
    if(self.shouldTrackDealloc) {
        NSLog(@"dealloc of %@",self);
    }
    ...
}

If you are tracking framework classes, you won't be able to use @synthesize , since the instance variable won't be in the right class, but you can use associated references instead.

The solution I ended up with was to use objc_setAssociatedObject(thread, key, object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

That way I could detect when the thread was being released and do the cleanup I needed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM