Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ARC: how to inject custom dealloc IMP in object which in turn calls original dealloc without causing malloc error

I'm trying to do the following:

  1. get a hold to a class'dealloc IMP
  2. inject into said class a custom IMP which essentially calls the original dealloc IMP
  3. When an instance of said class gets deallocated, both IMPs should run.

This is my attempt:

@implementation ClassB

- (void)dealloc
{
    NSLog(@"\n%@ | %@", self, NSStringFromSelector(_cmd));
}

@end


@implementation ClassC

- (void)swizzleMe:(id)target
{
    SEL originalDeallocSelector = NSSelectorFromString(@"dealloc");
    __block IMP callerDealloc = [target methodForSelector:originalDeallocSelector];
    const char *deallocMethodTypeEncoding = method_getTypeEncoding(class_getInstanceMethod([target class], originalDeallocSelector));
    IMP newCallerDealloc = imp_implementationWithBlock(^(id _caller) {
        NSLog(@"\n new dealloc | calling block %p for %@", callerDealloc, _caller);
        callerDealloc(_caller, originalDeallocSelector);
    });

    NSLog(@"\nswapping %p for %p", newCallerDealloc, callerDealloc);

    class_replaceMethod([target class],
                        originalDeallocSelector,
                        newCallerDealloc,
                        deallocMethodTypeEncoding);

}

@end

To be used like so:

ClassB *b = [[ClassB alloc] init];
ClassC *c = [[ClassC alloc] init];
[c swizzleMe:b];

But the results are:

zombie objects disabled:

2013-07-03 13:24:58.368 runtimeTest[38626:11303] 
swapping 0x96df020 for 0x2840
2013-07-03 13:24:58.369 runtimeTest[38626:11303] 
 new dealloc | calling block 0x2840 for <ClassB: 0x93282f0>
2013-07-03 13:24:58.370 runtimeTest[38626:11303] 
<ClassB: 0x93282f0> | dealloc
2013-07-03 13:24:58.370 runtimeTest[38626:11303] 
 new dealloc | calling block 0x2840 for <ClassB: 0x93282f0>
2013-07-03 13:24:58.371 runtimeTest[38626:11303] 
<ClassB: 0x93282f0> | dealloc
runtimeTest(38626,0xac55f2c0) malloc: *** error for object 0x93282f0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
2013-07-03 13:24:58.371 runtimeTest[38626:11303] 
 new dealloc | calling block 0x2840 for <ClassB: 0x93282f0>
2013-07-03 13:24:58.372 runtimeTest[38626:11303] 
<ClassB: 0x93282f0> | dealloc

zombie objects enabled (Line 11 is a EXC_BAD_ACCESS in the picture)

2013-07-03 13:34:37.466 runtimeTest[38723:11303] 
swapping 0x97df020 for 0x2840
2013-07-03 13:34:37.467 runtimeTest[38723:11303] 
 new dealloc | calling block 0x2840 for <ClassB: 0x715a920>
2013-07-03 13:34:37.468 runtimeTest[38723:11303] 
<ClassB: 0x715a920> | dealloc

Any thoughts on what am I doing wrong?

Line 11 is a EXC_BAD_ACCESS

like image 562
SaldaVonSchwartz Avatar asked Jul 03 '13 20:07

SaldaVonSchwartz


2 Answers

If you really want to know when an object is deallocated, then use associated objects.

Specifically, associate an object with the object you want to observe such that the object being observed has the only strong reference -- the only retain'd reference -- to the object. Then, you can override dealloc and know that when it is called the object being observed has been (or is just about to be) deallocated.

Do not mess with the object being deallocated, though!! It will already have had all of its dealloc methods invoked (by inheritance) and, thus, the internal state will be completely undefined.

Note that if your goal is to try and clean up something in the system frameworks, then... don't. Down that path is nothing instability and pain.


like I mentioned in the comments to nielsbot, I am NOT trying to know when an object is deallocated.

It would be helpful to know exactly what is in your injected dealloc implementation. On the face of it, the only reason I can think of that couldn't be solved through the use of associated objects to detect deallocation is exactly because you are trying to change the behavior of a class's dealloc, which is a really bad thing to do.

like image 88
bbum Avatar answered Nov 15 '22 17:11

bbum


After some time I finally found the real cause of the problem: I was overlooking the signature of the IMP type. From Apple's Objective-C Runtime Reference:

IMP A pointer to the start of a method implementation.

id (*IMP)(id, SEL, ...)

Particularly, IMP has a return type of id and so in ARCLandia ARC tries to manage this id, causing the objc_retain crash. So, assuming you have an IMP to -dealloc, explicitly casting it to a C function pointer with return type void makes it where ARC won't try to manage the return value anymore:

void (*deallocImp)(id, SEL) = (void(*)(id, SEL))_originalDeallocIMP;
deallocImp(self,NSSelectorFromString(@"dealloc"));
like image 24
SaldaVonSchwartz Avatar answered Nov 15 '22 17:11

SaldaVonSchwartz