2

@syntheized(self) ロックをこのメソッドで置き換えていました

void _ThreadsafeInit(Class theClassToInit, void *volatile *theVariableItLivesIn, void(^InitBlock)(void))
{
    //this is what super does :X
    struct objc_super mySuper = {
        .receiver = (id)theClassToInit,
        .super_class = class_getSuperclass(theClassToInit)
    };

id (*objc_superAllocTyped)(struct objc_super *, SEL, NSZone *) = (void *)&objc_msgSendSuper;
//    id (*objc_superAllocTyped)(id objc_super, SEL, NSZone *) = (void *)&objc_msgSend;

    do {
        id temp = [(*objc_superAllocTyped)(&mySuper /*theClassToInit*/, @selector(allocWithZone:), NULL) init];//get superclass in case alloc is blocked in this class;
        if(OSAtomicCompareAndSwapPtrBarrier(0x0, temp, theVariableItLivesIn)) { //atomic operation forces synchronization
            if( InitBlock != NULL ) {
                InitBlock(); //only the thread that succesfully set sharedInstance pointer gets here
            }
            break;
        }
        else
        {
            [temp release]; //any thread that fails to set sharedInstance needs to clean up after itself
        }
    } while (*theVariableItLivesIn == NULL);
}

これはもう少し冗長ですが、競合しないケースではパフォーマンスが大幅に向上します

この小さなマクロと一緒に (形式が悪いのは許してください。これは非常に単純です)。最初の nil チェックの後にブロックを宣言できるようにするために、LLVM が「既に初期化された」パスを非常に高速に保つのに役立つように見えます。それは私が気にする唯一のものです。

#define ThreadsafeFastInit(theClassToInit, theVariableToStoreItIn, aVoidBlockToRunAfterInit) if( theVariableToStoreItIn == nil) { _ThreadsafeInitWithBlock(theClassToInit, (void *)&theVariableToStoreItIn, aVoidBlockToRunAfterInit); }

そのため、最初は objc_superAllocTyped のコメントアウトされたセクションを使用して実装しました (実際には最初に [theClassToInit allocWithZone:NULL] を使用しましたが、これは間違いなく最良のアプローチでした:) )、プロジェクト内のほとんどのシングルトンが allocWithZone をオーバーライドしてシングルトンメソッドを返します...無限ループ。したがって、objc_msgSendSuper を使用するとすぐに解決できるはずですが、このエラーが発生します。

[51431:17c03] +[DataUtils allocWithZone:]: unrecognized selector sent to class 0x4f9584

エラーは実際の問題に関連していないようです...

(lldb) po 0x4f9584

$1 = 5215620 DataUtils

(lldb) print (BOOL)[$1 respondsToSelector:@selector(allocWithZone:)]

(BOOL) $2 = YES

だから私は間違いなく何かが欠けています...空のクラスの[super allocWithZone:NULL]メソッドによって生成されたアセンブリと比較しました...呼び出された関数の名前が異なることを除いてほぼ同じです(おそらく異なるシンボルを使用しているだけで、わかりませんよく読めない)。

何か案は?スーパークラスで class_getClassMethod を使用して IMP を直接呼び出すことができますが、ランタイムの悪用を合理的にしようとしています :)

4

1 に答える 1

7

メタ クラスには、-[self class] または +[self] を介して取得された Class インスタンスのすべてのメソッド情報が含まれていることを思い出すと、これは実際にはそれほどトリッキーではありませんでした -> http://www.cocoawithlove.comに感謝します/2010/01/what-is-meta-class-in-objective-c.html

このエラーは、明らかに allocWithZone: が含まれていない NSObject の一連のインスタンス メソッドでメソッドを検索するようにランタイムに要求したために発生しました。エラー ログの間違いは、レシーバーがメタクラス インスタンスであり、Apple がインターンにエラー ログを実装させていることが原因であると考えられます。

そのため、objc_msgSendSuper を介した通常のインスタンス メソッド呼び出しでは、メタクラス インスタンスを objc_super.super_class として渡しますが、クラス メソッドを呼び出すには、メタクラス自体が必要です (すべてが 1 レベル上です)。

例と、これを理解するのに役立つ図 - ( http://www.sealiesoftware.com/blog/archive/2009/04/14/objc_explain_Classes_and_metaclasses.html )

struct objc_super mySuper;
mySuper.receiver = theClassToInit; //this is our receiver, no doubt about it
//either grab the super class and get its metaclass
mySuper.super_class = object_getClass( class_getSuperclass( theClassToInit ) );
//or grab the metaclass, and get its super class, this is the exact same object
mySuper.super_class = class_getSuperclass( object_getClass( theClassToInit ) );

その後、メッセージを正しく解決できます。私が注意を払い始めた今、完全に理にかなっています:P

とにかく、間違いを見つけたので、Objc ランタイムの理解がレベルアップしたように感じます。また、3 つのプロジェクトと 2 つの静的ライブラリ (Objective-C が大好きです) にまたがる数十のクラスを変更して再テストすることなく、2 年前に会ったことのない人が犯したアーキテクチャ上の誤りを修正することができました。@synchronized コンストラクトを単純な関数呼び出しに置き換えると、これらのメソッドのコンパイル済みコード サイズも半分になりました。おまけとして、すべてのシングルトン アクセサーが (より) スレッドセーフになりました。そのためのパフォーマンス コストが無視できるようになったためです。シングルトンオブジェクトを単純に複数回 (またはループで) 再フェッチしたメソッドは、呼び出しごとに複数回ミューテックスを取得して解放する必要がないため、大幅な高速化が見られました。全体として、すべてが期待どおりに機能したことを非常に嬉しく思います。

NSObject のカテゴリで、このための「通常の」Objective-C メソッドを作成しました。これは、インスタンス オブジェクトとクラス オブジェクトの両方で機能し、メッセージのスーパークラスの実装を外部から呼び出すことができます。警告: これは楽しみ、単体テスト、スウィズル メソッド、または本当にクールなゲームのためだけのものです。

@implementation NSObject (Convenience)

-(id)performSelector:(SEL)selector asClass:(Class)class
{
    struct objc_super mySuper = {
        .receiver = self,
        .super_class = class_isMetaClass(object_getClass(self)) //check if we are an instance or Class
                        ? object_getClass(class)                //if we are a Class, we need to send our metaclass (our Class's Class)
                        : class                                 //if we are an instance, we need to send our Class (which we already have)
    };

    id (*objc_superAllocTyped)(struct objc_super *, SEL) = (void *)&objc_msgSendSuper; //cast our pointer so the compiler can sort out the ABI
    return (*objc_superAllocTyped)(&mySuper, selector);
}

それで

[self performSelector:@selector(dealloc) asClass:[self superclass]];

と同等です

[super dealloc];

ランタイム エクスプローラーを続けましょう。否定論者が手振りやブラック マジック ボックスの世界にあなたを引きずり込まないようにしてください。

※責任を持ってObjective-Cランタイムをお楽しみください。4 時間以上続くバグについては、QA チームに相談してください。

于 2013-02-01T04:05:37.450 に答える