ISA swizzling 并调用 `super`
ISA swizzling and calls to `super`
假定以下 class 层次结构。 Class A
公开声明:
@interface A : NSObject
+ (A)createInstance;
- (void)a;
@end
Class _B
是 A
:
的私有子 class
@interface _B : A
- (void)a;
- (void)b;
@end
假设 class A
的对象只能使用工厂方法 createInstance
创建,该方法会创建 returns _B
的实例。
我想在每个实例的基础上增强 A
实例的功能。所以我决定做一些 ISA swizzling 来实现:
@interface ExtA : A
- (void)a;
@end
@implementation ExtA
- (void)a
{
NSLog("ExtA_a");
[super a];
}
@end
然后我在 NSObject
类别上使用以下方法进行 ISA 调配(此处显示的是简单的实现):
- (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]
的实现,如果运行时的 superclass 实际上是 _B
.
用以下代码替换对 super
的调用是可行的,但是很丑陋,并且需要开发人员的知识和工作:
struct objc_super superInfo = {
self,
[self superclass]
};
objc_msgSendSuper(&superInfo, @selector(a));
调用 super
时编译器会发出什么以及更改此发出的代码的任何方法?
差异很小,但很重要。编译器发出函数调用,不是 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
您可能会问,这是做什么用的?这相当简单 - 不是调用者传递 superclass 来搜索,而是 objc_msgSend
实现。
但是,这一重要区别导致了一个关键问题 - 在进行 super
调用时,它 不会 调用 [self class]
。相反,它使用当前实现的 class,当然是 ExtA
.
因此,'fix' 的唯一方法是在 运行 时更改 ExtA
的 superclass,这应该会导致您的方法调用执行不出所料。
假定以下 class 层次结构。 Class A
公开声明:
@interface A : NSObject
+ (A)createInstance;
- (void)a;
@end
Class _B
是 A
:
@interface _B : A
- (void)a;
- (void)b;
@end
假设 class A
的对象只能使用工厂方法 createInstance
创建,该方法会创建 returns _B
的实例。
我想在每个实例的基础上增强 A
实例的功能。所以我决定做一些 ISA swizzling 来实现:
@interface ExtA : A
- (void)a;
@end
@implementation ExtA
- (void)a
{
NSLog("ExtA_a");
[super a];
}
@end
然后我在 NSObject
类别上使用以下方法进行 ISA 调配(此处显示的是简单的实现):
- (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]
的实现,如果运行时的 superclass 实际上是 _B
.
用以下代码替换对 super
的调用是可行的,但是很丑陋,并且需要开发人员的知识和工作:
struct objc_super superInfo = {
self,
[self superclass]
};
objc_msgSendSuper(&superInfo, @selector(a));
调用 super
时编译器会发出什么以及更改此发出的代码的任何方法?
差异很小,但很重要。编译器发出函数调用,不是 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
您可能会问,这是做什么用的?这相当简单 - 不是调用者传递 superclass 来搜索,而是 objc_msgSend
实现。
但是,这一重要区别导致了一个关键问题 - 在进行 super
调用时,它 不会 调用 [self class]
。相反,它使用当前实现的 class,当然是 ExtA
.
因此,'fix' 的唯一方法是在 运行 时更改 ExtA
的 superclass,这应该会导致您的方法调用执行不出所料。