Использование objc_msgSendSuper для вызова метода класса

Я проходил и заменял блокировки @synthesized(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);
}

который, хотя и немного более подробный, демонстрирует значительно лучшую производительность в неоспоримых случаях.

вместе с этим маленьким макросом (извините за плохое форматирование, это очень просто). Чтобы блок мог быть объявлен после начальной нулевой проверки, LLVM помогает 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, но я пытаюсь быть разумным в своем злоупотреблении средой выполнения :)


person Steazy    schedule 31.01.2013    source источник
comment
Не делай этого. Просто… не надо.   -  person Jonathan Grynspan    schedule 01.02.2013
comment
Ну, ИМХО, никто никогда не должен переопределять allocWithZone, но мы не всегда можем получить то, что хотим. Мне было бы интересно услышать какие-либо причины, по которым это недействительно, кроме страха перед средой выполнения Objective-C (которая является вашим другом).   -  person Steazy    schedule 01.02.2013
comment
Потому что это чертовски бессмысленный код, который портит среду выполнения ни по какой причине, ни по уважительной причине. Если вы расскажете нам, почему, по вашему мнению, вам нужно это сделать, может быть, мы сможем предложить более разумный подход?   -  person Jonathan Grynspan    schedule 01.02.2013
comment
черт возьми, почему возвращение отправить комментарий? Как я отметил в посте, многие одноэлементные классы в проекте (а он большой) переопределили метод allocWithZone: для вызова сборщика одноэлементных элементов, поэтому вызов [self allocWithZone:] из сборщика одноэлементных элементов вызовет бесконечный цикл. Методы категории в NSObject не могут вызывать super, иначе это было бы очень просто. Я знаю, как обойти эту проблему полдюжиной способов, и уже сделал это. Мой вопрос: почему эта, казалось бы, действительная операция нарушает среду выполнения? Почему objc_msgSend можно использовать в классе, но не objc_msgSendSuper?   -  person Steazy    schedule 01.02.2013
comment
Вы так и не объяснили, что вы на самом деле пытаетесь сделать. Сделайте шаг назад от среды выполнения Objective-C и объясните, какую проблему вы пытаетесь решить в более широком смысле.   -  person Jonathan Grynspan    schedule 01.02.2013
comment
Хорошо, я хочу вызвать стандартную реализацию allocWithZone: используя произвольный класс, без модификации среды выполнения. Я знаю, что получение IMP непосредственно из NSObject и отправка объекта подкласса в качестве получателя работает нормально, я не понимаю, почему objc_msgSendSuper не работает, поскольку, насколько я понимаю, это в основном то, что он делает. На самом деле нет проблемы, которую нужно решить, с моей стороны, просто непонимание того, как работает среда выполнения.   -  person Steazy    schedule 01.02.2013
comment
Но почему вы хотите это сделать?   -  person Jonathan Grynspan    schedule 01.02.2013
comment
Потому что я ботаник и хочу узнать, как это работает :)   -  person Steazy    schedule 01.02.2013


Ответы (1)


Хорошо, на самом деле это было не так сложно, как только я вспомнил, что метакласс содержит всю информацию о методах для экземпляра класса, полученную с помощью -[self class] или +[self] -> спасибо http://www.cocoawithlove.com/2010/01/what-is-meta-class-in-objective-c.html

Эта ошибка произошла из-за того, что я попросил среду выполнения найти метод в наборе методов экземпляра NSObject, который, очевидно, не содержит allocWithZone: . Ошибка в журнале ошибок, предположительно, возникла из-за того, что приемник был экземпляром метакласса, а стажеры Apple реализовали журналы ошибок.

поэтому, хотя при обычном вызове метода экземпляра через objc_msgSendSuper вы должны передать экземпляр метакласса как objc_super.super_class, для вызова метода класса необходим сам метакласс (все на один уровень выше).

Пример и диаграмма, которая помогла мне понять это - (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). Замена конструкции @synchronized простым вызовом функции также вдвое уменьшила размер скомпилированного кода этих методов. В качестве бонуса все наши одноэлементные методы доступа теперь (более) потокобезопасны, потому что затраты производительности на это теперь незначительны. Методы, которые наивно повторно извлекали одноэлементный объект несколько раз (или в циклах), теперь получили огромное ускорение, поскольку им не нужно захватывать и освобождать мьютекс несколько раз за вызов. В целом я очень рад, что все сработало, как я и надеялся.

Для этого я создал «обычный» метод Objective-C для категории NSObject, который будет работать как для экземпляров, так и для объектов класса, чтобы позволить вам вызывать реализацию сообщения суперкласса извне. Предупреждение: это только для развлечения, или модульных тестов, или swizzled методов, или, может быть, действительно классной игры.

@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);
}

so

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

будет эквивалентно

[super dealloc];

Продолжайте исследовать время выполнения! Не позволяйте скептикам затащить вас в свою страну маханий руками и черных магических ящиков, там сложно делать бескомпромиссно крутые программы*.

* Пожалуйста, используйте среду выполнения Objective-C ответственно. Проконсультируйтесь с вашей командой QA о любых ошибках, длящихся более четырех часов.

person Steazy    schedule 01.02.2013
comment
Искал такое решение несколько дней :) !!! полностью согласен с вами по поводу скептиков по такого рода темам: p спасибо, что поделились, и да благословит бог objc ^^ - person polo987; 18.08.2015