Runtime之Method Swizzling

Runtime 又叫运行时,iOS 内部的核心之一,底层的 为C 语言 写的API,底层都是基于它来实现的。在没有一个类的实现源码的情况下,想改变其中一个方法的实现,除了继承它重写、和借助类别重名方法暴力抢先之外,还有更加灵活的方法吗?那就是Method Swizzling,利用运行时优雅地实现。

一、说到Method Swizzling先提到几个概念:

SEL:它是selector在 Objc 中的表示(Swift 中是 Selector 类)。selector 是方法选择器,其实作用就和名字一样,日常生活中,我们通过人名辨别谁是谁,注意 Objc 在相同的类中不会有命名相同的两个方法。selector 对方法名进行包装,以便找到对应的方法实现。它的数据结构是:

typedefstruct objc_selector *SEL;

IMP

IMP实际上是一个函数指针,指向方法实现的地址。

其定义如下:

id (*IMP)(id, SEL,...)

Method:Method 代表类中某个方法的类型

typedefstruct objc_method *Method;struct objc_method {

SEL method_name

OBJC2_UNAVAILABLE;char *method_types

OBJC2_UNAVAILABLE;

IMP method_imp

OBJC2_UNAVAILABLE;}

objc_method存储了方法名,方法类型和方法实现:

方法名类型为SEL

方法类型method_types是个 char 指针,存储方法的参数类型和返回值类型

method_imp指向了方法的实现,本质是一个函数指针

二、Method Swizzling原理

Method Swizzing是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzling代码写到任何地方,但是只有在这段Method Swilzzling代码执行完毕之后互换才起作用。

而且Method Swizzling也是iOS中AOP(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP编程。

首先,让我们通过两张图片来了解一下Method Swizzling的实现原理

Runtime之Method Swizzling_第1张图片

图一

Runtime之Method Swizzling_第2张图片

图二

上面图一中selector2原本对应着IMP2,但是为了更方便的实现特定业务需求,我们在图二中添加了selector3和IMP3,并且让selector2指向了IMP3,而selector3则指向了IMP2,这样就实现了“方法互换”。

在OC语言的runtime特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL,这个SEL对应着一个IMP(一个IMP可以对应多个SEL),通过这个IMP找到对应的方法调用。

在每个类中都有一个Dispatch Table,这个Dispatch Table本质是将类中的SEL和IMP(可以理解为函数指针)进行对应。而我们的Method Swizzling就是对这个table进行了操作,让SEL对应另一个IMP。

三、Method Swizzling实现

在实现Method Swizzling时,核心代码主要就是一个runtime的C语言API:

OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2)

__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

而 Method Swizzling 可以交换两个方法的IMP。你可以重写某个方法而不用继承,同时还可以调用原先的实现。通常的做法是在category中添加一个方法(当然也可以是一个全新的class)。可以通过method_exchangeImplementations这个运行时方法来交换实现。来看一个demo,这个demo演示了如何重写addObject:方法来纪录每一个新添加的对象。

#import

@interface NSMutableArray (LoggingAddObject)

- (void)logAddObject:(id)aObject;

@end

@implementation NSMutableArray (LoggingAddObject)

+ (void)load {

Method addobject = class_getInstanceMethod(self, @selector(addObject:));

Method logAddobject = class_getInstanceMethod(self, @selector(logAddObject:));

method_exchangeImplementations(addObject, logAddObject);

}

- (void)logAddObject:(id)aobject {

[self logAddObject:aObject];

NSLog(@"Added object %@ to array %@", aObject, self);

}

@end

我们把方法交换放到了load中,这个方法只会被调用一次,而且是运行时载入。如果指向临时用一下,可以放到别的地方。注意到一个很明显的递归调用logAddObject:。这也是Method Swizzling容易把我们搞混的地方,因为我们已经交换了方法的实现,所以其实调用的是addObject:

你可能感兴趣的:(Runtime之Method Swizzling)