Objective-C中方法调配的正确方法

Posted

技术标签:

【中文标题】Objective-C中方法调配的正确方法【英文标题】:Proper way of method swizzling in objective-C 【发布时间】:2015-12-28 23:18:42 【问题描述】:

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

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

【问题讨论】:

【参考方案1】:

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

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

首先忽略该示例是特定类 UIViewController 上的一个类别这一事实,而只是将逻辑视为该类别位于某个任意类上,我们称该类为 TargetClass

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

TargetClass 上的类别将 swizzling 方法(我们称之为swizzlingMethod)添加到TargetClass

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

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

    TargetClass 本身直接包含existingMethod 的实现。这是简单的情况,只需交换existingMethodswizzlingMethod 的实现,这可以通过method_exchangeImplementations 完成。在文章中,对class_addMethod 的调用将失败,因为existingMethod 直接在 TargetClass 中,逻辑导致对method_exchangeImplementations 的调用。

    李>

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

还在这里吗?我希望这是有道理的,而不仅仅是让你眼花缭乱!

如果你非常好奇,另一个练习:现在你可能会问,如果祖先的 existingMethod 实现包含对 super 的调用...如果该实现现在也附加到 TargetClass 中的 swizzlingMethod 会发生什么打给super 的电话会在哪里结束?是在祖先中实现,这将看到相同的方法实现被执行两次,还是在祖先的祖先中,如最初预期的那样?

HTH

【讨论】:

简直太棒了,这一切都说得通。你的练习建议也很棒,我会深入研究这些:) 你似乎对运行时非常了解,我刚刚发布了另一个问题。如果您有机会看一看(如果您有时间),那就太棒了。非常感谢人! ***.com/questions/34503563/…【参考方案2】:

load 在 obj-c 运行时添加 class 时被调用。

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

假设 UIViewController 被添加到已经包含 viewWillAppear: 的 obj-c 运行时中,但您希望它被另一个实现替换。所以首先你添加一个新方法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/

【讨论】:

以上是关于Objective-C中方法调配的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

在objective-c中,后台线程中的重复方法是不是正确?

在 Swift 中使用 Objective-C 类别的正确方法是啥?

在objective-c中刷新Gmail API访问令牌的正确方法

Swift 中的方法调配

将json调用语法从Objective-C转换为Swift的正确方法是啥?

无法调配类方法