运行时编程动态方法时使用ARC编译时出错

时间:2012-10-22 08:48:35

标签: objective-c automatic-ref-counting clang objective-c-runtime

我正在尝试在Objective-C上进行一些运行时编程。 为此,我重写 resolveClassMethod 方法。

不幸的是,当ARC处于活动状态时,我想出了一些关于clang的编译错误:

  

错误:没有已知的选择器类方法'动态'

如果我使用没有ARC的gcc或clang(传递 -fno-objc-arc 选项),除了警告而不是错误之外,一切正常。

我知道ARC需要知道被调用方法的名称,以找出如何使用返回值管理内存(遵循方法名称约定)。但是如何在没有丑陋的 performSelector 调用而不是直接方法调用的情况下解决这个问题?

这是我的代码:

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;
}

没有ARC的Gcc或clang

编制结果

  

main.m:13:36:警告:课堂方法&#39; +动态&#39;找不到(返回类型   默认为&#39; id&#39;)

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

输出

  

2012-10-22 10:33:15.563 test-clang [957:707]你好,约翰2012-10-22

     

2012-10-22 10:33:15.565 test-clang [957:707]动态方法称为2012-10-22

     

2012-10-22 10:33:15.565 test-clang [957:707]你好吗,dynamicImp

与ARC结合

编制结果

  

main.m:13:36:错误:选择器没有已知的类方法&#39;动态&#39;

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

PS:我并不关心内存管理,因为我的目标是在激活ARC的情况下编译此代码。

2 个答案:

答案 0 :(得分:3)

在你的电话中

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

ARC编译器不知道该方法的返回类型。但ARC需要知道该方法是否返回一个对象以添加适当的retain / release调用来管理生命周期。

即使没有ARC,您也会收到编译器警告

  

未找到类方法'+ dynamic'(返回类型默认为'id')

但ARC编译器更严格。

你可以打电话

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

因为performSelector返回id。对于返回除对象以外的任何内容的函数,您可以使用NSInvocation

或者,您可以使用类扩展声明dynamic方法:

@interface Test (DynamicMethods)
+ (NSString *)dynamic;
@end

答案 1 :(得分:1)

ARC肯定会让一些有趣的运行时方法解析机器陷入困境。但是,仍有一些选择。与您提到的performSelector:技术一样丑陋同样是明确的objc_msgSend() 函数调用。该函数需要使用其返回和参数类型进行强制转换,如下所示:

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

(现在您将收到有关隐式声明的警告;只需在某处声明extern id objc_msgSend(id, SEL, ...);。)

更好的选择是在发送消息时将对象强制转换为id(或将其存储在id变量中以开始)。编译器永远不会知道id响应的消息,因此它不能也不会抱怨发送任意消息。您可以像对待实例一样将类对象强制转换为id

[(id)Test dynamic];

[(id)testInstance anotherDynamicName];