Ошибка компиляции с 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];
Другие вопросы по тегам