3

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

不幸的是,当 ARC 处于活动状态时,我在 clang 中遇到了一些编译错误:

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

如果我在没有 ARC 的情况下使用 gcc 或 clang(通过-fno-objc-arc选项),一切正常,除了警告而不是错误。

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

这是我的代码:

测试.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

测试.h

#import <Cocoa/Cocoa.h>

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

- (NSString*)name;

@end

主文件

#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

与 ARC 铿锵

编译结果

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

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

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

4

2 回答 2

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
于 2012-10-22T09:13:28.113 回答
1

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

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

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

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

[(id)Test dynamic];

或者

[(id)testInstance anotherDynamicName];
于 2012-10-22T19:44:48.047 回答