3

I am trying to do some runtime programmation on Objective-C. In order to do this I override the resolveClassMethod method.

Unfortunately I come up with some compilation error with clang when ARC is active :

error: no known class method for selector 'dynamic'

Everything works fine if I use gcc or clang without ARC (-fno-objc-arc option passed), except a warning instead of the error.

I am aware that ARC need to know the name of the method called to figure out how to manage the memory with returned value (following method name convention). But how to this issue without an ugly performSelector call instead of a direct method call ?

Here is my code :

Test.m

#import "Test.h"
#import <objc/runtime.h>

NSString* dynamicImp(id slef, SEL _cmd)
{
    NSLog(@"Dynamic method called");
    return @"dynamicImp";
}

@implementation Test

- (NSString*)name
{
    return @"John";
}

+ (BOOL)resolveClassMethod:(SEL)name
{
    if (name == @selector(dynamic))
    {
        Class metaClass = objc_getMetaClass([NSStringFromClass([self class]) UTF8String]);
        class_addMethod(metaClass, name, (IMP) dynamicImp, "@@:");
        return YES;
    }
    return NO;
}

+ (IMP)methodForSelector:(SEL)aSelector
{
    if (aSelector == @selector(dynamic))
    {
        return (IMP) dynamicImp;
    }
    else
    {
        return [super methodForSelector:aSelector];
    }
}

- (BOOL)respondsToSelector:(SEL)aSelector
{
    if (aSelector == @selector(dynamic))
    {
        return YES;
    }
    else
    {
        return [NSObject respondsToSelector:aSelector];
    }
}

@end

Test.h

#import <Cocoa/Cocoa.h>

@interface Test : NSObject <NSObject> {
    NSString *_name;
}

- (NSString*)name;

@end

main.m

#import <Cocoa/Cocoa.h>
#import <stdio.h>
#import "Test.h"

int main(int argc, char* argv[])
{
    @autoreleasepool {
        Test *test = [[Test alloc] init];
        NSLog(@"Hello, %@", [test name]);
        NSLog(@"How are you , %@", [Test dynamic]);
    }
    return 0;
}

Gcc or clang without ARC

Compilation result

main.m:13:36: warning: class method '+dynamic' not found (return type defaults to 'id')

    NSLog(@"How are you , %@", [Test dynamic]);

Output

2012-10-22 10:33:15.563 test-clang[957:707] Hello, John 2012-10-22

2012-10-22 10:33:15.565 test-clang[957:707] Dynamic method called 2012-10-22

2012-10-22 10:33:15.565 test-clang[957:707] How are you , dynamicImp

Clang with ARC

Compilation result

main.m:13:36: error: no known class method for selector 'dynamic'

    NSLog(@"How are you , %@", [Test dynamic]);

PS : I didn't care for the moment on memory management as my goal is to compile this code with ARC activated.

Toon Krijthe
  • 52,876
  • 38
  • 145
  • 202
sprohaszka
  • 43
  • 5

2 Answers2

3

In your call

NSLog(@"How are you , %@", [Test dynamic]);

the ARC compiler does not know the return type of the method. But ARC needs to know if the method returns an object to add the appropriate retain/release calls for managing the lifetime.

Even without ARC you get a compiler warning

class method '+dynamic' not found (return type defaults to 'id')

but the ARC compiler is more strict.

You can call

NSLog(@"How are you , %@", [[Test class] performSelector:@selector(dynamic)]);

because performSelector returns an id. For functions returning anything other than an object you can use NSInvocation.

Alternatively, you can declare the dynamic method using a Class Extension:

@interface Test (DynamicMethods)
+ (NSString *)dynamic;
@end
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • I know about the ARC needs for memory management, but the use of performSelector in my test is not an option. I find this solution quite ugly in fact. The use of a Class Extension make my method no more a dynamic method as I have to know during the compilation time its name. I try to dynamically add a method with a signature that maybe unknown during the compilation time. – sprohaszka Oct 22 '12 at 10:46
  • @sprohaszka: I have only tried to explain the compiler error. I have added the Class Extension method only for sake of completeness. If the signature is unknown at compilation time than you could use `NSInvocation` to call a method at runtime. I know that it is ugly, but I don't know a better alternative. – Martin R Oct 22 '12 at 10:57
  • @sprohaszka: If you call `[Test dynamic]` in main(), then you assume at compile time that `Test` has a `dynamic` method, so this was probably just an example? – Martin R Oct 22 '12 at 11:13
  • Yes, it's just an example. What I want to be able to do is call [Test dynamic] or [Test DYNAMIC] or [Test Dynamic]. Whatever the call was, it has to be "linked" to the same function. – sprohaszka Oct 22 '12 at 11:31
  • @sprohaszka: See also http://lists.apple.com/archives/objc-language/2012/Jul/msg00110.html and the follow-ups. There is is also explained that ARC needs to know the signature (or more precisely: the kind of ownership of the return value). In particular in http://lists.apple.com/archives/objc-language/2012/Jul/msg00114.html it is said that it was a deliberate decision to make this an error instead of a warning. – Martin R Oct 22 '12 at 12:19
  • Thank you for the links. So if I correctly understood, I can't do it the "old" way now with ARC. It has must lead to some incompatibility with libraries using this technique. – sprohaszka Oct 22 '12 at 12:27
  • @sprohaszka: Yes, that's how I understand it. You cannot just compile `[Test fooBar]` with an unknown selector signature. For a library you would get problems if you recompile it with ARC. A compiled library continues to work because you can mix ARC and non-ARC code. – Martin R Oct 22 '12 at 12:34
  • @sprohaszka: I understand that you don't "like" my answer (-:, but I think that I have correctly explained why what you want to do is not possible. So you might consider to "accept" this answer by clicking on the check mark. – Martin R Nov 10 '12 at 14:31
  • `[[Test class] performSelector:@selector(dynamic)]` can be written as `[Test performSelector:@selector(dynamic)]` – user102008 Dec 03 '12 at 22:30
  • @sprohaszka: "as I have to know during the compilation time its name" Well, in order to call it like `[Test dynamic]` or `[Test DYNAMIC]` or `[Test Dynamic]`, you have to explicitly write the name at compile time, so you know exactly the name at compile time. If you don't call it like `[Test dynamic]`, then you won't have this problem in the first place. – user102008 Dec 03 '12 at 22:35
1

ARC has definitely thrown a wrench into some of the fun runtime method resolution machinery. There are still a few options, however. Equally as ugly as the performSelector: technique you mentioned is an explicit objc_msgSend() function call. The function needs to be cast with its return and argument types, like so:

(void (*)(id, SEL)objc_msgSend)([Test class], @selector(dynamic)));

(You'll now get a warning about implicit declaration; just declare extern id objc_msgSend(id, SEL, ...); somewhere.)

A better option is to cast the object to id when you make the message send (or store it in an id variable to begin with). The compiler never knows anything about the messages that ids respond to, so it can't and doesn't complain about sending arbitrary messages. You can cast a class object to id just as you can an instance.

[(id)Test dynamic];

or

[(id)testInstance anotherDynamicName];
jscs
  • 63,694
  • 13
  • 151
  • 195
  • You get an error if the method is "complete unknown" to the compiler (at least with ARC). So `[(id)testInstance lastObject]` compiles, because the compiler knows about a `lastObject` method. But `[(id)testInstance xyz]` does not compile. – Martin R Oct 22 '12 at 20:58
  • Oh, damn, you're right. I forgot about that. I've been doing this with methods that are declared in a protocol. The `objc_msgSend()` version still works, however. – jscs Oct 22 '12 at 22:12