我正在尝试在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的情况下编译此代码。
答案 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];