【问题标题】:ISA swizzling and calls to `super`ISA swizzling 并调用“super”
【发布时间】:2015-04-02 15:42:24
【问题描述】:

假设以下类层次结构。 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

我在 NSObject 类别上使用以下方法进行 ISA swizzling(此处显示的幼稚实现):

- (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]; 的行为与预期不符,如果运行时中的超类实际上是_B,则实际上调用了-[A a] 的实现。

用以下代码替换对super 的调用是可行的,但很难看,并且需要开发人员的知识和工作:

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

编译器在调用super 时会发出什么以及更改此发出的代码的任何方法?

【问题讨论】:

    标签: objective-c superclass objective-c-runtime swizzling isa-swizzling


    【解决方案1】:

    区别很小,但很重要。编译器发出一个函数调用,不是对objc_msgSendSuper,而是对objc_msgSendSuper2

    你可能会问有什么区别?这是次要的,但很重要。

    来自apple's open source

    /********************************************************************
     *
     * 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 的超类,这应该会导致您的方法调用按预期执行。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-05-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多