Objective-C中方法混淆的正确方式

7

我目前正在尝试使用Objective-C中的方法swizzling,但是我有一个问题。我试图理解正确的方法交换方式,并在在线研究后,我偶然发现了这篇NSHipster文章:http://nshipster.com/method-swizzling/

在这篇文章中,作者提供了一些方法交换的示例代码。我想请别人更好地向我解释作者在做什么。特别是我对didAddMethod逻辑感到困惑。为什么作者不直接交换方法实现?我唯一的理论是可能viewWillAppear:还没有被添加到UIViewController的方法调度表中。特别是如果类别先于UIViewController加载到内存中...这是原因吗?这似乎相当奇怪?只是寻求更多见解/清晰度,谢谢:)

2个回答

7
特别是关于didAddMethod的逻辑,我感到困惑。为什么作者不直接交换/交换方法实现?
您的困惑是可以理解的,因为这个逻辑没有被清楚地解释。
首先忽略这个示例是特定类UIViewController的类别,并将逻辑视为该类别在某个任意类上(称为TargetClass)时的情况。
我们将要替换的现有方法称为existingMethod。
该类别在TargetClass上添加了swizzling方法(我们将其称为swizzlingMethod)。
重要提示:请注意,获取方法的函数class_getInstanceMethod将在提供的类或其任何超类中找到该方法。但是,class_addMethod和class_replaceMethod函数仅在提供的类中添加/替换方法。
现在有两种情况需要考虑:
1. TargetClass本身直接包含existingMethod的实现。这是简单的情况,需要做的就是交换existingMethod和swizzlingMethod的实现,可以使用method_exchangeImplementations完成。在文章中,调用class_addMethod的调用将失败,因为在TargetClass中已经有一个existingMethod,所以逻辑结果会调用method_exchangeImplementations。
2. TargetClass不直接包含existingMethod的实现,而是该方法通过从TargetClass的祖先类继承而来。这是更棘手的情况。如果您只是交换existingMethod和swizzlingMethod的实现,则会影响(实例化)祖先类(以一种可能导致崩溃的方式,留给您作为练习)。通过调用class_addMethod,文章中的代码确保TargetClass中存在existingMethod - 其实现是swizzlingMethod的原始实现。然后逻辑替换了swizzlingMethod的实现,将祖先的existingMethod的实现替换为其实现(这对祖先没有影响)。
还在这里吗?我希望这有道理,没有让您发晕!
如果你还有兴趣了解,可以进行另一个练习:如果祖先类的existingMethod实现包含对super的调用... 如果该实现现在也附加到TargetClass中的swizzlingMethod,那么这个对super的调用会发生什么?它会调用祖先类中的实现,导致相同的方法实现被执行两次,还是按照最初的想法调用祖先的祖先类?希望对你有所帮助。

1
简直是惊人的答案,这一切都完全有道理。您的练习建议也很好,我会深入研究 :) 您似乎非常了解运行时间,我刚刚发布了另一个问题。如果您有时间,能够看一下就太棒了。非常感谢!http://stackoverflow.com/questions/34503563/method-swizzling-with-method-setimplementation - AyBayBay

1

load 是在 obj-c 运行时添加一个 class 时被调用的。

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

假设在 obj-c 运行时中添加了一个 UIViewController,其中已包含 viewWillAppear: 方法,但您希望将其替换为另一种实现。因此,首先添加一个新方法 xxxWillAppear:。只有在 ViewController 类中添加了 xxxWillAppear: 方法之后,才能进行替换。
但作者也说:
例如,假设我们想要跟踪 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/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接