0

I am working on an old app and I am converting its memory handling to ARC (automatic reference counting) and in one of my classes I got this warning: PerformSelector may cause a leak because its selector is unknown. I researched this problem and came upon this great find as explained by wbyoung. To mitigate my warning I changed my original code from:

- (void)launchExecution {
    @autoreleasepool {
    // Start executing the requested task
        [targetForExecution performSelector:methodForExecution withObject:objectForExecution]; //--WARNING happens here...

        // Task completed, update view in main thread (note: view operations should
        // be done only in the main thread)
        [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];      
    }
}

and I modified it to:

- (void)launchExecution {
    @autoreleasepool {
        if(!targetForExecution) return;
        SEL selector = methodForExecution;
        IMP imp = [targetForExecution methodForSelector:selector];
        void (*func)(id, SEL) = (void *)imp;
        func(targetForExecution, selector);

        // Task completed, update view in main thread (note: view operations should
        // be done only in the main thread)
        [self performSelectorOnMainThread:@selector(cleanUp) withObject:nil waitUntilDone:NO];

    }
}

Now my issue is this: while it doesn't give me any errors (yay!), I still have to pass along my ObjectForExecution value into this new configuration. How is this done? I know there are some workarounds where people have used pragma to ignore this type of warning but I would like to properly fix this warning.

user2529011
  • 557
  • 3
  • 7
  • 17
  • 2
    What you mention as a "great find" is an accepted answer with lots of upvotes but the explanation there is completely mistaken. The correct explanation can be found in this answer https://stackoverflow.com/a/7017537/669586 and the solution is to silence the error. Not to make the code more complicated. – Sulthan Aug 21 '18 at 20:52

2 Answers2

2

I would just use objc_msgSend directly, like this:

// At top of file:
#import <objc/message.h>

// To send the message:
((void (*)(id, SEL, id))objc_msgSend)(targetForExecution, methodForExecution, objectForExecution);

Note that it's important that the method actually returns void. If it returns an object, that object will probably be leaked. If it returns a struct, this code will crash. But if the method returns void, nothing will be leaked and the call won't crash.

rob mayoff
  • 342,380
  • 53
  • 730
  • 766
2

You should replace targetForExecution, methodForExecution and objectForExecution with block and use GCD:

- (void)launchExecution {
    blockForExecution();
    dispatch_async(dispatch_get_main_queue(), ^{
     [self cleanUp];    
    });
}
Cy-4AH
  • 4,051
  • 2
  • 13
  • 20