6

目前正在使用swizzlingObjective-C 中的方法进行试验,我有一个问题。我试图了解方法调酒的正确方法,在网上研究后,我偶然发现了这篇 NSHipster 帖子: http ://nshipster.com/method-swizzling/

在帖子中,作者有一些方法调配示例代码。我正在找人更好地向我解释作者在做什么。特别是我对didAddMethod逻辑感到困惑。为什么作者不只是直接swapping/exchanging方法实现?我对此的唯一理论是可能还有一些viewWillAppear:尚未添加的机会UIViewController's method dispatch_table。特别是如果该类别可能先加载到内存中UIViewController......这是原因吗?好像比较奇怪?只是寻找更多的洞察力/清晰度,谢谢:)

4

2 回答 2

7

特别是我对 didAddMethod 逻辑感到困惑。为什么作者不只是直接交换/交换方法实现?

你的困惑是可以理解的,因为这个逻辑没有解释清楚。

首先忽略该示例是特定类上的类别这一事实,UIViewController只考虑逻辑,就好像该类别在某个任意类上一样,让我们​​调用该类TargetClass

我们将调用我们希望替换的现有方法existingMethod

类别 onTargetClass将 swizzling 方法添加swizzlingMethodTargetClass.

重要提示:请注意,获取方法的函数将在提供的类或其任何超类class_getInstanceMethod中找到该方法。但是,这些函数仅在提供的类中添加/替换方法。class_addMethodclass_replaceMethod

现在有两种情况需要考虑:

  1. TargetClass本身直接包含existingMethod. 这是一个简单的例子,所有需要做的就是交换 和 的实现existingMethodswizzlingMethod这可以用method_exchangeImplementations. 在文章中,调用class_addMethod将失败,因为已经existingMethod 直接 in TargetClass并且逻辑导致调用method_exchangeImplementations.

  2. TargetClass直接包含 的实现existingMethod,而是通过从 的祖先类之一继承来提供该方法TargetClass。这是更棘手的情况。如果您只是交换的实现,existingMethod那么swizzlingMethod您将影响祖先类的(实例)(并且以一种可能导致崩溃的方式 - 为什么留下作为练习)。通过调用class_addMethod文章的代码,确保有一个existingMethodin TargetClass- 的实现是swizzlingMethod. 然后,该逻辑将 的实现替换为swizzlingMethod祖先的实现existingMethod(这对祖先没有影响)。

还在?我希望这是有道理的,而不是简单地让你眼花缭乱!

如果您非常好奇,可以进行另一个练习:现在您可能会问,如果祖先的existingMethod实现包含对super...swizzlingMethod的调用,TargetClass会发生什么情况super?是在祖先中实现,这将看到相同的方法实现执行两次,还是在祖先的祖先中,如最初预期的那样?

高温高压

于 2015-12-29T01:56:41.233 回答
1

loadclass在 obj-c 运行时添加 a 时调用。

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

因此,假设 aUIViewController被添加到已经包含viewWillAppear:但您希望它被另一个实现替换的 obj-c 运行时中。所以首先你添加一个新方法xxxWillAppear:。现在一旦xxxWillAppear:ViewController课堂上添加了,你就可以替换它。

但作者也说:

例如,假设我们想要跟踪每个视图控制器在 iOS 应用程序中呈现给用户的次数

所以他试图演示一个应用程序可能有很多视图控制器但你不想为每个实现都替换ViewControllerviewWillAppear:情况。一旦点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/

于 2015-12-29T00:28:13.260 回答