前言
此篇文章是上周看DZNEmptyDataSet源码的时候看到了一段比较有意思的代码,而自己之前关于Swizzling一直看的是mattt大神的Method Swizzling版本(AFNetworking使用的是此种方式进行的Swizzling);刚好DZNEmptyDataSet的作者贴出了源博主的Blog地址,这里翻译出来与大家分享。首先贴出来DZNEmptyDataSet关于Swizzling的源码实现:
// We add method sizzling for injecting -dzn_reloadData implementation to the native -reloadData implementation
[self swizzleIfPossible:@selector(reloadData)];
// Exclusively for UITableView, we also inject -dzn_reloadData to -endUpdates
if ([self isKindOfClass:[UITableView class]]) {
[self swizzleIfPossible:@selector(endUpdates)];
}
- (void)swizzleIfPossible:(SEL)selector
{
// Create the lookup table
Class baseClass = dzn_baseClassToSwizzleForTarget(self);
NSString *key = dzn_implementationKey(baseClass, selector);
NSValue *impValue = [[_impLookupTable objectForKey:key] valueForKey:DZNSwizzleInfoPointerKey];
// If the implementation for this class already exist, skip!!
if (impValue || !key || !baseClass) {
return;
}
// Swizzle by injecting additional implementation
Method method = class_getInstanceMethod(baseClass, selector);
IMP dzn_newImplementation = method_setImplementation(method, (IMP)dzn_original_implementation);
// Store the new implementation in the lookup table
NSDictionary *swizzledInfo = @{DZNSwizzleInfoOwnerKey: baseClass,
DZNSwizzleInfoSelectorKey: NSStringFromSelector(selector),
DZNSwizzleInfoPointerKey: [NSValue valueWithPointer:dzn_newImplementation]};
[_impLookupTable setObject:swizzledInfo forKey:key];
}
void dzn_original_implementation(id self, SEL _cmd)
{
// Fetch original implementation from lookup table
Class baseClass = dzn_baseClassToSwizzleForTarget(self);
NSString *key = dzn_implementationKey(baseClass, _cmd);
NSDictionary *swizzleInfo = [_impLookupTable objectForKey:key];
NSValue *impValue = [swizzleInfo valueForKey:DZNSwizzleInfoPointerKey];
IMP impPointer = [impValue pointerValue];
// We then inject the additional implementation for reloading the empty dataset
// Doing it before calling the original implementation does update the 'isEmptyDataSetVisible' flag on time.
[self dzn_reloadEmptyDataSet];
// If found, call original implementation
if (impPointer) {
((void(*)(id,SEL))impPointer)(self,_cmd);
}
}
/************以下是翻译内容(英文不错的可以直接点击标题处的原文链接) *************/
Swizzling是通过将方法的实现替换为另一个方法实现来改变方法的功能,这一替换通常是在运行时执行的。有很多不同的需求可能需要使用到Swizzling,如:自省(introspection)、重写默认的行为(overriding default behavior)、或者动态加载方法(maybe even dynamic method loading)。我已经看了很多博客讨论Objective-C的Swizzling,并且他们很多人都推荐了一些非常糟糕的做法。如果您正在编写独立的应用程序程序,这些不好的做法并不是什么大不了的事,但如果您正在为第三方人员编写框架,那么Swizzling可能会弄乱一些可以正常运行的基础执行。那么在Objective-C中Swizzling的正确方法是什么呢?
让我们从基础开始。当我们说Swizzling的时候通常意味着用自定义的方法去替换源方法的行为,并且通常我们会在自己定义的方法内部调用源方法。Objective-C允许使用Objective-C运行时提供的方法进行上面的操作。在运行时中,Objective-C方法表示了一个名为Method的C结构体;这个结构体的定义如下:
struct objc_method
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
method_name是方法选择器,*method_types是参数和返回值类型编码的C字符串,method_imp是指向实际函数的函数指针(关于IMP稍后会有更多的讨论)。
我们可以使用下面的方法访问此对象:
Method class_getClassMethod(Class aClass, SEL aSelector);
Method class_getInstanceMethod(Class aClass, SEL aSelector);
通过访问对象的Method结构体可以访问更改其底层实现。method_imp是IMP类型,他被定义为id(*IMP)(id, SEL, ...)或者是一个以对象指针、选择器和一个附加变量列表作为参数,并返回一个对象指针的函数。这可以通过IMP method_setImplementation(Method method,IMP imp)来改变。通过method_setImplementation()传递替换的实现,imp,并传入一个你想要改变的方法的结构体,method,同时此方法会返回与method相关联的源IMP实现。这才是Swizzling的正确方式。
什么是不正确的swizzle方式呢?
这是一种常用的调配方法。 虽然看起来很简单 - 将一种方法的实现与另一种方法交换 - 但有一些非显而易见的影响。
void method_exchangeImplementations(Method m1, Method m2)
为了理解这些影响,我们来看看在调用这个函数之前和之后m1和m2的结构。
Method m1 { //this is the original method. we want to switch this one with
//our replacement method
SEL method_name = @selector(originalMethodName)
char *method_types = “v@:“ //returns void, params id(self),selector(_cmd)
IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
}
Method m2 { //this is the swizzle method. We want this method executed when [MyClass
//originalMethodName] is called
SEL method_name = @selector(swizzle_originalMethodName)
char *method_types = “v@:”
IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
}
这些是我们调用函数之前的方法结构。 生成这些结构的Objective-C代码将如下所示:
@implementation MyClass
- (void) originalMethodName //m1
{
//code
}
- (void) swizzle_originalMethodName //m2
{
//…code?
[self swizzle_originalMethodName];//call original method
//…code?
}
@end
然后我们调用下面代码:
m1 = class_getInstanceMethod([MyClass class], @selector(originalMethodName));
m2 = class_getInstanceMethod([MyClass class], @selector(swizzle_originalMethodName));
method_exchangeImplementations(m1, m2)
现在方法结构体将会如下所示:
Method m1 { //this is the original Method struct. we want to switch this one with
//our replacement method
SEL method_name = @selector(originalMethodName)
char *method_types = “v@:“ //returns void, params id(self),selector(_cmd)
IMP method_imp = 0x1234AABA (MyBundle`[MyClass swizzle_originalMethodName])
}
Method m2 { //this is the swizzle Method struct. We want this method executed when [MyClass
//originalMethodName] is called
SEL method_name = @selector(swizzle_originalMethodName)
char *method_types = “v@:”
IMP method_imp = 0x000FFFF (MyBundle`[MyClass originalMethodName])
}
注意,如果我们想要执行原始方法代码,我们必须调用 - [self swizzle_originalMethodName],但这会导致_cmd值传递给原始方法代码,现在变为@selector(swizzle_originalMethodName),如果方法代码 取决于_cmd是方法的原始名称(originalMethodName)。 这种混乱的方式(下面的例子)阻碍了程序的正常运行,这应该避免。
- (void) originalMethodName //m1
{
assert([NSStringFromSelector(_cmd) isEqualToString:@“originalMethodNamed”]); //this fails after swizzling //using
//method_exchangedImplementations()
//…
}
现在让我们看看使用method_setImplementation()以适当的方式进行Swizzling。
正确的swizzle方式
创建一个符合IMP定义的C函数,来替代创建一个Objective-C的函数-[(void) swizzle_originalMethodName]。
void __Swizzle_OriginalMethodName(id self, SEL _cmd)
{
//code
}
我们可以将这个函数作为IMP来执行:
IMP swizzleImp = (IMP)__Swizzle_OriginalMethodName;
并且这允许我们将这个IMP传递给method_setImplementation():
method_setImplementation(method, swizzleImp);
并且method_setImplementation()返回原始的IMP:
IMP originalImp = method_setImplementation(method,swizzleImp);
现在,originalImp可以用于调用原始的方法:
originalImp(self,_cmd);
下面是全部例子的实现:
@interface SwizzleExampleClass : NSObject
- (void) swizzleExample;
- (int) originalMethod;
@end
static IMP __original_Method_Imp;
int _replacement_Method(id self, SEL _cmd)
{
assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
//code
int returnValue = ((int(*)(id,SEL))__original_Method_Imp)(self, _cmd);
return returnValue + 1;
}
@implementation SwizzleExampleClass
- (void) swizzleExample //call me to swizzle
{
Method m = class_getInstanceMethod([self class],
@selector(originalMethod));
__original_Method_Imp = method_setImplementation(m,
(IMP)_replacement_Method);
}
- (int) originalMethod
{
//code
assert([NSStringFromSelector(_cmd) isEqualToString:@"originalMethod"]);
return 1;
}
@end
执行下面的代码进行验证:
SwizzleExampleClass* example = [[SwizzleExampleClass alloc] init];
int originalReturn = [example originalMethod];
[example swizzleExample];
int swizzledReturn = [example originalMethod];
assert(originalReturn == 1); //true
assert(swizzledReturn == 2); //true
总之,为了避免与其他第三方SDK发生冲突,请不要使用Objective-C方法和method_swapImplementations()混合使用,而应使用C函数和method_setImplementation(),将这些C函数转换为IMP。 这样可以避免与Objective-C方法一起提供的所有额外信息包裹,例如新的选择器名称。 如果你想调整,最好的结果是不留痕迹。 †不要忘记,所有Objective-C方法都会传递2个隐藏参数:对self(id self)和方法的选择器(SEL _cmd)的引用。 如果它返回一个空白,您可能不得不对IMP调用进行处理。 这是因为ARC假定所有的IMP都返回一个id,并且会试图保留void和原始类型。
IMP anImp; //represents objective-c function
// -UIViewController viewDidLoad;
((void(*)(id,SEL))anImp)(self,_cmd); //call with a cast to prevent
// ARC from retaining void.