1

I am making a program for iOS 7, but because I use nearly the same method in several views, I would like to create a common class which covers the most of the method and uses a callback method for the finishing touch which is separate for every view. I have the following code in the view:

// On top of the view .m
#import "Common.h"

// Method which will be called.
-(void)doSomething
{
  NSLog(@"doSomething...");

  [Common target:self method:@selector(callbackMethod)];
}

// Method I want to be called.
+(void)callbackMethod
{
  NSLog(@"Should come here...");
}

The Common.h is as following:

#import <Foundation/Foundation.h>

@interface Common : NSObject

+(void)target:(Class)object method:(SEL)selector;

@end

The Common.m is as following:

+(void)target:(Class)object method:(SEL)selector
{
    NSLog(@"target...");
    if ([object respondsToSelector:@selector(selector)])
    {
        [object performSelector:@selector(selector)];
    } else {
        NSLog(@"%@", object);
    }
}

On runtime, it only outputs: doSomething..., target..., <ParentViewController: 0x8d24bb0>

The problem here is with calling the callback method. The program will reach the method in Common.m but can't call the method from the parent.

Popeye
  • 11,839
  • 9
  • 58
  • 91
Sietse
  • 623
  • 1
  • 8
  • 23
  • Sounds like you need subclassing: create a super class (that is an UIView subclass) that implement all the common bits. Then create subclasses of that class and possibly override the methods you need to customise. `performSelector` always has a strange smell to it. Or use a class category for the common bits. – verec Feb 24 '14 at 14:58
  • @selector() is the literal for creating a SEL object. Since you are passed a SEL, you don't need to wrap that argument with @selector(), you may just use it directly. – Patrick Goley Feb 24 '14 at 15:53

1 Answers1

2

The problem is that

@selector(selector)

searches for a method named "selector." @selector() retrieves the selector with the name contained inside the parenthesis.

What you want to is to use the selector that is passed in, rather than retrieving a different one. You do this by passing it in to the selector methods as you would any other variable:

+(void)target:(Class)object method:(SEL)selector
{
    NSLog(@"target...");
    if ([object respondsToSelector:selector])
    {
        [object performSelector:selector];
    } else {
        NSLog(@"%@", object);
    }
}

When using ARC, this will cause a warning because the ARC system won't be able to tell what's going on. To inform the compiler that you are calling performSelector: intentionally and that you are sure there is no leak, you may turn off the warning for just this one line. Only do this when you are absolutely sure there are no leaks.

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [object performSelector:selector];
#pragma clang diagnostic pop

Finally, let's look at your select, callbackMethod:

+(void)callbackMethod
{
  NSLog(@"Should come here...");
}

This is a class method, not an instance method, therefore your instance of your class does not respond to the selector, while the class itself does. Changing it from a class method to an instance method (by replacing the '+' with a '-') will fix this issue:

-(void)callbackMethod
{
  NSLog(@"Should come here...");
}
BergQuester
  • 6,167
  • 27
  • 39
  • Got the following warning: _PerformSelector may cause a leak because its selector is unknown_. Besides the warning, it still doesn't enter the callbackMethod. Any other ideas? – Sietse Feb 24 '14 at 15:05
  • 1
    in `- doSomething` you pass `self` which refers to an *instance*. Yet in `+ target` you document the parameter as `(Class)`, not as `(id)`. And it does turn out that you seem to want to send an instance method selector as a class method selector: `+ callbackMethod` is a _class_ method, not an _instance_ method. But this whole code really smells... – verec Feb 24 '14 at 15:29
  • I missed those issues, I've updated the answer. I also agree with @verec, if this is code to experiment with the feature of ObjC, you're fine, but if this is intended to be production code, it is quite smelly. There's too many layers of indirection that would need to be refactored out. – BergQuester Feb 24 '14 at 15:50
  • Your last code in your update works beautiful. I let the warning where it is since the code works and I need to update it in the future as this is not the best way it seems reading the comments. – Sietse Feb 24 '14 at 16:05
  • It is never a good idea to turn off that warning. It exists for a reason. There are ways around this. Please see: http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown – Mike Mar 18 '15 at 22:48