Ошибка компиляции с ARC при динамическом программировании во время выполнения
Я пытаюсь сделать некоторое программирование во время выполнения на Objective-C. Для этого я переопределил метод resolClassMethod.
К сожалению, я обнаружил ошибку при компиляции clang, когда ARC активен:
ошибка: неизвестный метод класса для селектора 'dynamic'
Все работает нормально, если я использую gcc или clang без ARC (передана опция -fno-objc-arc), кроме предупреждения вместо ошибки.
Мне известно, что ARC нужно знать имя вызываемого метода, чтобы выяснить, как управлять памятью с возвращаемым значением (следуя соглашению имен методов). Но как решить эту проблему без уродливого вызова executeSelector вместо прямого вызова метода?
Вот мой код:
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 или лязг без ARC
Результат компиляции
main.m:13:36: предупреждение: метод класса '+dynamic' не найден (тип возвращаемого значения по умолчанию равен '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
Лязг с дугой
Результат компиляции
main.m:13:36: ошибка: нет известного метода класса для селектора 'dynamic'
NSLog(@"How are you , %@", [Test dynamic]);
PS: мне сейчас наплевать на управление памятью, так как моя цель - скомпилировать этот код с активированной ARC.
2 ответа
В вашем звонке
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:
Техника, которую вы упомянули, является явным 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];