objective-C 中方法调配的正确方法
Proper way of method swizzling in objective-C
目前正在 Objective-C 中试验方法 swizzling
,我有一个问题。我试图了解 method swizzle 的正确方法,在线研究后我偶然发现了这个 NSHipster post:
http://nshipster.com/method-swizzling/
作者在post中有一些method swizzling的示例代码。我正在寻找可以更好地向我解释作者在做什么的人。特别是我对 didAddMethod
逻辑感到困惑。为什么作者不直接swapping/exchanging
方法实现呢?我对此的唯一理论是,viewWillAppear:
尚未添加到 UIViewController's method
dispatch_table
中的可能性很小。特别是如果类别可能在 UIViewController
之前首先加载到内存中...这就是原因吗?这似乎很奇怪?正在寻找更多 insight/clarity,谢谢 :)
load
在 obj-c 运行时添加 class
时调用。
所以假设如果 UIViewController
被添加到已经包含 viewWillAppear:
的 obj-c 运行时中,但您希望它被另一个实现替换。所以首先你添加一个新方法xxxWillAppear:
。
现在一旦 xxxWillAppear:
被添加到 ViewController
class 中,您就可以替换它。
不过作者也说了:
For example, let’s say we wanted to track how many times each view controller is presented to a user in an iOS app
所以他试图演示一个应用程序可能有许多视图控制器但您不想为每个 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;
}
如果你愿意,你可以挖掘更多:
In particular I am confused on the didAddMethod logic. Why is the author not just directly swapping/exchanging method implementations?
你的困惑是可以理解的,因为这个逻辑没有解释清楚。
首先忽略示例是特定 class UIViewController
上的类别这一事实,只考虑逻辑,就好像该类别在某个任意 class 上一样,让我们称之为class TargetClass
.
我们将调用我们希望替换的现有方法 existingMethod
。
该类别在 TargetClass
上,将我们称之为 swizzlingMethod
的 swizzling 方法添加到 TargetClass
。
重要:请注意,获取方法的函数 class_getInstanceMethod
将在提供的 class 或它的任何一个 superclasses。然而,函数 class_addMethod
和 class_replaceMethod
仅在所提供的 class 中使用 add/replace 方法。
现在有两种情况需要考虑:
TargetClass
本身直接包含了existingMethod
的实现。这是最简单的情况,所有需要做的就是交换 existingMethod
和 swizzlingMethod
的实现,这可以用 method_exchangeImplementations
完成。在文章中,对 class_addMethod
的调用将失败,因为 existingMethod
直接在 TargetClass
中,逻辑结果是调用 method_exchangeImplementations
.
TargetClass
不 直接包含 existingMethod
的实现,而是通过继承祖先之一提供的方法class 个 TargetClass
。这是比较棘手的情况。如果您只是交换 existingMethod
和 swizzlingMethod
的实现,那么您将影响祖先 class 的(实例)(并且以一种可能导致崩溃的方式 - 为什么保留为锻炼)。通过调用 class_addMethod
,文章的代码确保 TargetClass
中有一个 existingMethod
- 其实现是 swizzlingMethod
的原始实现。然后逻辑将 swizzlingMethod
的实现替换为祖先的 existingMethod
的实现(对祖先没有影响)。
还在吗?我希望这是有道理的,而不是简单地向您发送 cross-eyed!
另一个练习,如果你极度好奇:现在你可能会问,如果祖先的 existingMethod
实现包含对 super
的调用,会发生什么...如果该实现现在也附加到 swizzlingMethod
在 TargetClass
中对 super
的调用将在哪里结束?是在祖先中实现,这会看到相同的方法实现执行两次,还是像最初预期的那样,在祖先的祖先中实现?
HTH
目前正在 Objective-C 中试验方法 swizzling
,我有一个问题。我试图了解 method swizzle 的正确方法,在线研究后我偶然发现了这个 NSHipster post:
http://nshipster.com/method-swizzling/
作者在post中有一些method swizzling的示例代码。我正在寻找可以更好地向我解释作者在做什么的人。特别是我对 didAddMethod
逻辑感到困惑。为什么作者不直接swapping/exchanging
方法实现呢?我对此的唯一理论是,viewWillAppear:
尚未添加到 UIViewController's method
dispatch_table
中的可能性很小。特别是如果类别可能在 UIViewController
之前首先加载到内存中...这就是原因吗?这似乎很奇怪?正在寻找更多 insight/clarity,谢谢 :)
load
在 obj-c 运行时添加 class
时调用。
所以假设如果 UIViewController
被添加到已经包含 viewWillAppear:
的 obj-c 运行时中,但您希望它被另一个实现替换。所以首先你添加一个新方法xxxWillAppear:
。
现在一旦 xxxWillAppear:
被添加到 ViewController
class 中,您就可以替换它。
不过作者也说了:
For example, let’s say we wanted to track how many times each view controller is presented to a user in an iOS app
所以他试图演示一个应用程序可能有许多视图控制器但您不想为每个 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;
}
如果你愿意,你可以挖掘更多:
In particular I am confused on the didAddMethod logic. Why is the author not just directly swapping/exchanging method implementations?
你的困惑是可以理解的,因为这个逻辑没有解释清楚。
首先忽略示例是特定 class UIViewController
上的类别这一事实,只考虑逻辑,就好像该类别在某个任意 class 上一样,让我们称之为class TargetClass
.
我们将调用我们希望替换的现有方法 existingMethod
。
该类别在 TargetClass
上,将我们称之为 swizzlingMethod
的 swizzling 方法添加到 TargetClass
。
重要:请注意,获取方法的函数 class_getInstanceMethod
将在提供的 class 或它的任何一个 superclasses。然而,函数 class_addMethod
和 class_replaceMethod
仅在所提供的 class 中使用 add/replace 方法。
现在有两种情况需要考虑:
TargetClass
本身直接包含了existingMethod
的实现。这是最简单的情况,所有需要做的就是交换existingMethod
和swizzlingMethod
的实现,这可以用method_exchangeImplementations
完成。在文章中,对class_addMethod
的调用将失败,因为existingMethod
直接在TargetClass
中,逻辑结果是调用method_exchangeImplementations
.TargetClass
不 直接包含existingMethod
的实现,而是通过继承祖先之一提供的方法class 个TargetClass
。这是比较棘手的情况。如果您只是交换existingMethod
和swizzlingMethod
的实现,那么您将影响祖先 class 的(实例)(并且以一种可能导致崩溃的方式 - 为什么保留为锻炼)。通过调用class_addMethod
,文章的代码确保TargetClass
中有一个existingMethod
- 其实现是swizzlingMethod
的原始实现。然后逻辑将swizzlingMethod
的实现替换为祖先的existingMethod
的实现(对祖先没有影响)。
还在吗?我希望这是有道理的,而不是简单地向您发送 cross-eyed!
另一个练习,如果你极度好奇:现在你可能会问,如果祖先的 existingMethod
实现包含对 super
的调用,会发生什么...如果该实现现在也附加到 swizzlingMethod
在 TargetClass
中对 super
的调用将在哪里结束?是在祖先中实现,这会看到相同的方法实现执行两次,还是像最初预期的那样,在祖先的祖先中实现?
HTH