【问题标题】:Error compiling with ARC when runtime programming dynamic method运行时编程动态方法时使用 ARC 编译时出错
【发布时间】:2012-10-22 08:48:35
【问题描述】:

我正在尝试在 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

ma​​in.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:警告:找不到类方法“+动态”(返回类型 默认为'id')

    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

Clang 与 ARC

编译结果

main.m:13:36: 错误:选择器“动态”没有已知的类方法

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

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

【问题讨论】:

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


    【解决方案1】:

    在您的通话中

    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
    

    【讨论】:

    • 我知道 ARC 对内存管理的需求,但在我的测试中使用 performSelector 不是一个选项。事实上,我发现这个解决方案非常难看。类扩展的使用使我的方法不再是动态方法,因为我必须在编译期间知道它的名称。我尝试动态添加一个方法,其签名在编译期间可能未知。
    • @sprohaszka:我只是试图解释编译器错误。为了完整起见,我添加了类扩展方法。如果在编译时签名未知,则可以使用NSInvocation 在运行时调用方法。我知道它很丑,但我不知道更好的选择。
    • @sprohaszka:如果你在 main() 中调用 [Test dynamic],那么你在编译时假设 Test 有一个 dynamic 方法,所以这可能只是一个例子?
    • 是的,这只是一个例子。我想要做的是调用 [Test dynamic] 或 [Test DYNAMIC] 或 [Test Dynamic]。无论调用是什么,它都必须“链接”到同一个函数。
    • @sprohaszka:另见lists.apple.com/archives/objc-language/2012/Jul/msg00110.html 和后续行动。还解释了 ARC 需要知道签名(或更准确地说:返回值的所有权类型)。特别是在lists.apple.com/archives/objc-language/2012/Jul/msg00114.html 中,据说这是一个深思熟虑的决定,将其设为错误而不是警告。
    【解决方案2】:

    ARC 无疑为一些有趣的运行时方法解析机制带来了麻烦。但是,仍有一些选择。与您提到的 performSelector: 技术同样丑陋的是显式的 objc_msgSend() function 调用。该函数需要使用其返回和参数类型进行强制转换,如下所示:

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

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

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

    [(id)Test dynamic];
    

    [(id)testInstance anotherDynamicName];
    

    【讨论】:

    • 如果编译器“完全不知道”该方法(至少在 ARC 中),则会出现错误。所以[(id)testInstance lastObject] 编译,因为编译器知道lastObject 方法。但是[(id)testInstance xyz] 无法编译。
    • 哦,该死的,你是对的。我忘记了。我一直在使用协议中声明的方法来执行此操作。不过,objc_msgSend() 版本仍然有效。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-12
    • 1970-01-01
    • 2014-04-11
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    • 2012-01-16
    相关资源
    最近更新 更多