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
во время выполнения, что должно привести к тому, что вызов вашего метода будет работать как ожидалось.