Правильный способ подмены метода в объекте-C

В настоящее время экспериментирую с методом swizzling в Objective-C, и у меня есть вопрос. Я пытаюсь понять, как правильно использовать метод swizzle, и после исследования в Интернете я наткнулся на эту публикацию NSHipster: http://nshipster.com/method-swizzling/

В посте у автора есть пример кода метода swizzling. Я ищу кого-нибудь, кто лучше объяснит мне, что делает автор .. В частности, меня смущает didAddMethod логика. Почему автор не занимается непосредственно swapping/exchanging реализацией методов? Моя единственная теория на этот счет: возможно, есть шанс, что viewWillAppear: еще не добавлен в UIViewController's method dispatch_table. В частности, если категория загружается в память раньше, чем _7 _... Это причина того, почему? Это кажется довольно странным? Просто ищу больше понимания / ясности, спасибо :)


person AyBayBay    schedule 28.12.2015    source источник


Ответы (2)


В частности, меня смущает логика didAddMethod. Почему автор не просто меняет местами реализации методов?

Ваше замешательство понятно, поскольку эта логика четко не объяснена.

Сначала проигнорируйте тот факт, что пример является категорией в конкретном классе UIViewController, и просто рассмотрите логику, как если бы категория была в каком-то произвольном классе, давайте назовем этот класс TargetClass.

Мы вызовем существующий метод, который хотим заменить existingMethod.

Категория, находящаяся на TargetClass, добавляет метод swizzling, который мы назовем swizzlingMethod, в TargetClass.

Важно: обратите внимание, что функция для получения метода class_getInstanceMethod найдет метод в предоставленном классе или любом из его суперклассов. Однако функции class_addMethod и class_replaceMethod только добавляют / заменяют методы в предоставленном классе.

Теперь необходимо рассмотреть два случая:

  1. TargetClass сам содержит реализацию existingMethod. Это простой случай, все, что нужно сделать, - это обменяться реализациями existingMethod и swizzlingMethod, что можно сделать с помощью method_exchangeImplementations. В статье вызов class_addMethod завершится ошибкой, поскольку уже есть и existingMethod непосредственно в TargetClass, и логика приводит к вызову method_exchangeImplementations.

  2. TargetClass не напрямую содержит реализацию existingMethod, скорее этот метод предоставляется через наследование от одного из классов-предков TargetClass. Это более сложный случай. Если вы просто обмениваетесь реализациями existingMethod и swizzlingMethod, тогда вы будете воздействовать (экземплярами) на класс-предок (и таким образом, который может вызвать сбой - почему это оставлено в качестве упражнения). Вызывая class_addMethod, код статьи проверяет наличие existingMethod в TargetClass, реализация которого является исходной реализацией swizzlingMethod. Затем логика заменяет реализацию swizzlingMethod реализацией existingMethod предка (которая не влияет на предка).

Все еще здесь? Я надеюсь, что это имеет смысл и не вызвало у вас косоглазия!

Еще одно упражнение, если вам крайне любопытно: теперь вы можете спросить, что произойдет, если реализация existingMethod предка содержит вызов _31 _..., если реализация теперь также присоединена к swizzlingMethod в TargetClass, где этот вызов super закончится? Будет ли это реализация в предке, при которой одна и та же реализация метода будет выполняться дважды, или в предке, как изначально предполагалось?

HTH

person CRD    schedule 29.12.2015
comment
Просто потрясающий ответ, все это имеет смысл. Ваши предложения по упражнениям также великолепны, и я подробно остановлюсь на них :) Кажется, вы очень хорошо осведомлены о времени выполнения, я только что разместил еще один вопрос. Было бы здорово, если бы у вас была возможность взглянуть (если у вас есть время). Большое спасибо, чувак! stackoverflow.com/questions/34503563/ - person AyBayBay; 29.12.2015

load вызывается, когда class добавляется в среду выполнения obj-c.

https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSObject_Class/#//apple_ref/occ/clm/NSObject/load

Итак, допустим, если UIViewController будет добавлен в среду выполнения obj-c, которая уже содержит viewWillAppear:, но вы хотите, чтобы он был заменен другой реализацией. Итак, сначала вы добавляете новый метод xxxWillAppear:. Теперь, когда xxxWillAppear: был добавлен в класс ViewController, только тогда вы можете его заменить.

Но автор также сказал:

Например, предположим, что мы хотели отслеживать, сколько раз каждый контроллер представления представлен пользователю в приложении iOS.

поэтому он пытается продемонстрировать случай, когда приложение может иметь много контроллеров представления, но вы не хотите продолжать заменять их для каждой ViewController реализации viewWillAppear:. После замены точки viewWillAppear: вместо добавления нужно будет выполнить только обмен.

Возможно, поможет исходный код среды выполнения Objective C:

/**********************************************************************
* addMethod
* fixme
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static IMP 
addMethod(Class cls, SEL name, IMP imp, const char *types, BOOL replace)
{
IMP result = nil;

rwlock_assert_writing(&runtimeLock);

assert(types);
assert(cls->isRealized());

method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
    // already exists
    if (!replace) {
        result = _method_getImplementation(m);
    } else {
        result = _method_setImplementation(cls, m, imp);
    }
} else {
    // fixme optimize
    method_list_t *newlist;
    newlist = (method_list_t *)_calloc_internal(sizeof(*newlist), 1);
    newlist->entsize_NEVER_USE = (uint32_t)sizeof(method_t) | fixed_up_method_list;
    newlist->count = 1;
    newlist->first.name = name;
    newlist->first.types = strdup(types);
    if (!ignoreSelector(name)) {
        newlist->first.imp = imp;
    } else {
        newlist->first.imp = (IMP)&_objc_ignored_method;
    }

    attachMethodLists(cls, &newlist, 1, NO, NO, YES);

    result = nil;
}

return result;
}


BOOL 
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;

rwlock_write(&runtimeLock);
IMP old = addMethod(cls, name, imp, types ?: "", NO);
rwlock_unlock_write(&runtimeLock);
return old ? NO : YES;
}


IMP 
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;

rwlock_write(&runtimeLock);
IMP old = addMethod(cls, name, imp, types ?: "", YES);
rwlock_unlock_write(&runtimeLock);
return old;
}

Вы можете копать больше, если хотите:

http://www.opensource.apple.com/source/objc4/objc4-437/

person Anand    schedule 29.12.2015