Method Swizzlingを適用する場合の注意

以前にNSDictionaryへのNull挿入の強制をMethod Swizzlingで行うというエントリを書いた。
[Objective-C][iOS]Method SwizzlingでNull挿入パターンを実装する / Kazzzの日記

このときは上手く行ったので調子にのって、以下のようにMethod Swizzlingをユーティリティとして外だしにして

BBRuntimeUtil.m (抜粋)
+ (void)swizzleMethod:(Class)clazz from:(SEL)originalSelector to:(SEL)newSelector
{
    Method origMethod = class_getInstanceMethod(clazz, originalSelector);
    Method newMethod = class_getInstanceMethod(clazz, newSelector);
    
    if(class_addMethod(clazz, originalSelector, method_getImplementation(newMethod)
                       , method_getTypeEncoding(newMethod)))
    {
        class_replaceMethod(clazz, newSelector, method_getImplementation(origMethod)
                            , method_getTypeEncoding(origMethod));
    }
    else
    {
        method_exchangeImplementations(origMethod, newMethod);
    }
}

次は、そもそもClassに属するMethodを置き換えるのだからMethod Swizzlingもインスタンス単位ではなくクラスの初期化時に一度行えば良いと、NSDictionaryのloadクラスメソッドをオーバライドして以下のように記述するように変更した。

NSDictionary+BBDictionaryUtil.m
+ (void)load
{
    //Method Swizzling
    [BBRuntimeUtil swizzleMethod:[self class] 
                            from:@selector(setObject:forKey:) 
                              to:@selector(bb_setObject:forKey:)];
}
- (void)bb_setObject:(id)anObject forKey:(id)aKey
{
    if (anObject)
    {
        [self bb_setObject:anObject forKey:aKey];
    }
    else
    {
        [self bb_setObject:[NSNull null] forKey:aKey];
    }
}

これでコードも見通しが良くなったし、Method Swizzlingも無駄な処理が省けて見通しがよくなった。と思ったのだが、これ以降テストが通らなくなった。具体的にはNSMutableDictionary-setObject:forKeyでSwizzlingで置き換えたはずのメソッドが一切呼ばれないのである。

何度もコードを見直してテストをしてみたが一向に結果が変わらない。いい加減MethodSizzlingは中止しようとも思ったのだが、それも癪である。

クラスクラスタ

Cocoa Fundamentals Guide: クラスクラスタ
クラスクラスタJavaC#で言うところの「テンプレートパターン」の実装である。

NSDictionary/NSMutableDictionaryはクラスクラスタに属しており、それぞれのクラスはJavaC#の抽象クラスであり実際の具象クラス(実装クラスは他にあるのであるのだ。

試しに上記-loadメソッドではなく、インスタンスメソッドでどのようなクラスが具象クラスなのかを確認してみよう。

NSMutableDictionaryのクラスクラスタにおける内部クラスはNSMutableDictionaryではなく__NSCFDictionaryなのである。
では、カテゴリを__NSCFDictionaryクラスに対して用意してMethod Swizzlingを実行すれば良いのだろうか?
これは駄目。__NSCFDictionaryは外部に公開されていないからだ。

Cannot find inteface declaration for '__NSCFDictionary'; did you mean 'NSDictionary'?

と怒られてしまう。クラスクラスタ対象クラスはインスタンスメソッドでMethod Swizzlingするしかないようだ。

※プレフィクスBBは、私がiOSで書くライブラリィに便宜上付けているもの。無視して構わない。