1

次のクラス階層を想定します。クラスAは公に宣言されています:

@interface A : NSObject

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

@end

クラス_Bは のプライベート サブクラスですA:

@interface _B : A

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

@end

クラス のオブジェクトは、 のインスタンスを作成して返すAファクトリ メソッド を使用してのみ作成する必要があると仮定します。createInstance_B

Aのインスタンスの機能をインスタンスごとに強化したいと考えています。そこで、次のことを達成するために ISA スウィズルを実行することにしました。

@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]実行時のスーパークラスが実際に_B.

への呼び出しを次のコードに置き換えるとsuper機能しますが、見苦しく、開発者による知識と作業が必要です。

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

呼び出し時にコンパイラは何を発行し、superこの発行されたコードを変更する方法はありますか?

4

1 に答える 1

2

違いはわずかですが、重要です。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実装がそれを行います。

ただし、この重要な違いにより、1 つの重大な問題が発生します。呼び出しを行うと、superが呼び出されませ[self class]。代わりに、現在の実装のクラスを使用します。もちろん、ExtA.

したがって、これを「修正」する唯一の方法はExtA、実行時に のスーパークラスを変更することです。これにより、メソッド呼び出しが期待どおりに実行されるようになります。

于 2015-02-19T18:08:14.293 に答える