ISA свистит и зовет в "супер"

Предположим следующую иерархию классов. Учебный класс A публично объявлено:

@interface A : NSObject

+ (A)createInstance;
- (void)a;

@end

Учебный класс _B это частный подкласс A:

@interface _B : A

- (void)a;
- (void)b;

@end

Предположим, объекты класса A должен быть создан только с использованием фабричного метода createInstance, который создает и возвращает экземпляр _B,

Я хочу улучшить функциональность экземпляра A на индивидуальной основе. Поэтому я решил сделать ISA Swizzling для достижения:

@interface ExtA : A

- (void)a;

@end

@implementation ExtA

- (void)a
{
    NSLog("ExtA_a");
    [super a];
}

@end

И я делаю ISA Swizzling, используя следующий метод на NSObject категория (наивная реализация показана здесь):

- (void)changeToSubclass:(Class)cls prefix:(NSString*)prefix suffix:(NSString*)suffix
{
    NSString* className = [NSString stringWithFormat:@"%@%@%@", prefix ? prefix : @"", NSStringFromClass(object_getClass(self)), suffix ? suffix : @""];

    if([className isEqualToString:NSStringFromClass(object_getClass(self))])
    {
        className = [NSString stringWithFormat:@"%@(%@)", NSStringFromClass(object_getClass(self)), NSStringFromClass(cls)];
    }

    Class newSubclass = objc_getClass(className.UTF8String);

    if(newSubclass == nil)
    {
        newSubclass = objc_allocateClassPair(object_getClass(self), className.UTF8String, 0);
        objc_registerClassPair(newSubclass);

        unsigned int listCount = 0;
        Method *list = class_copyMethodList(cls, &listCount);

        for(int i = 0; i < listCount; i++)
        {
            class_addMethod(newSubclass, method_getName(list[i]), method_getImplementation(list[i]), method_getTypeEncoding(list[i]));
        }
        free(list);

        listCount = 0;
        list = class_copyMethodList(objc_getMetaClass(class_getName(cls)), &listCount);

        for(int i = 0; i < listCount; i++)
        {
            class_addMethod(objc_getMetaClass(class_getName(newSubclass)), method_getName(list[i]), method_getImplementation(list[i]), method_getTypeEncoding(list[i]));
        }
        free(list);
    }

    object_setClass(self, newSubclass);
}

Все вроде бы работает, но я заметил, что [super a]; не ведет себя так, как ожидалось, на самом деле реализация -[A a] вызывается, если на самом деле суперкласс во время выполнения _B,

Замена звонка на super со следующим кодом работает, но уродливо, и требует знаний и работы разработчиков:

struct objc_super superInfo = {
    self,
    [self superclass]
};
objc_msgSendSuper(&superInfo, @selector(a));

Что выдает компилятор при вызове super и есть ли способ изменить этот излучаемый код?

1 ответ

Решение

Разница незначительна, но важна. Компилятор выдает вызов функции, а не objc_msgSendSuper, но objc_msgSendSuper2,

В чем разница, спросите вы? Это незначительно, но важно.

Из открытого исходного кода яблока:

/********************************************************************
 *
 * id objc_msgSendSuper(struct objc_super *super, SEL _cmd,...);
 *
 * struct objc_super {
 *      id  receiver;
 *      Class   class;
 * };
 ********************************************************************/

    ENTRY   _objc_msgSendSuper
    MESSENGER_START

// search the cache (objc_super in %a1)
    movq    class(%a1), %r11    // class = objc_super->class
    CacheLookup SUPER       // calls IMP on success

/* Snipped code for brevity */

/********************************************************************
 * id objc_msgSendSuper2
 ********************************************************************/

    ENTRY _objc_msgSendSuper2
    MESSENGER_START

    // objc_super->class is superclass of class to search

// search the cache (objc_super in %a1)
    movq    class(%a1), %r11    // cls = objc_super->class
    movq    8(%r11), %r11       // cls = class->superclass
    CacheLookup SUPER2      // calls IMP on success

Для тех, кто читает незнакомые с x86_64 сборки, важная строка кода здесь:

movq    8(%r11), %r11       // cls = class->superclass

Вы можете спросить, что это делает? Это довольно просто - вместо того, чтобы вызывающий передавал суперкласс для поиска, objc_msgSend реализация делает это.

Тем не менее, это важное различие вызывает одну важную проблему - при выполнении super вызов, это не вызывает [self class], Вместо этого он использует класс текущей реализации, который, конечно, ExtA,

Следовательно, единственный способ исправить это - изменить суперкласс ExtA во время выполнения, что должно привести к тому, что вызов вашего метода будет работать как ожидалось.

Другие вопросы по тегам