第四周
完成状态
- 进行中
- 已完成
- 维护中
日期
2019/08/19-2019/08/25
目录
Knowledge Merge
Method Swizzling
- 常用方式通过
Category
实现
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
//
BOOL didAddMethod =
class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
});
}
#pragma mark - Method Swizzling
- (void)xxx_viewWillAppear:(BOOL)animated {
[self xxx_viewWillAppear:animated];
NSLog(@"viewWillAppear: %@", self);
}
@end
- 在Category 的
load
方法中添加Method Swizzling 代码,在类初始化加载时自动调用。load方法按照父类到子类,类自身到Category的顺序被调用. dispatch_once
保证代码只会调用一次(load 方法也只会调用一次,此处可以不需要此操作)- 尝试先调用class_addMethod方法,以保证即便originalSelector只在父类中实现,也能达到Method Swizzling的目的.
- xxx_viewWillAppear:方法中[self xxx_viewWillAppear:animated];代码并不会造成死循环,因为Method Swizzling之后, 调用xxx_viewWillAppear:实际执行的代码已经是原来viewWillAppear中的代码了.
+ (void)load {
Class class = [self class];
SEL originalSelector = @selector(viewWillAppear:);
SEL swizzledSelector = @selector(xxx_viewWillAppear:);
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
if (!originalMethod || !swizzledMethod) {
return;
}
IMP originalIMP = method_getImplementation(originalMethod);
IMP swizzledIMP = method_getImplementation(swizzledMethod);
const char *originalType = method_getTypeEncoding(originalMethod);
const char *swizzledType = method_getTypeEncoding(swizzledMethod);
// 这儿的先后顺序是有讲究的,如果先执行后一句,那么在执行完瞬间方法被调用容易引发死循环
class_replaceMethod(class,swizzledSelector,originalIMP,originalType);
class_replaceMethod(class,originalSelector,swizzledIMP,swizzledType);
}
重点: 由于class_replaceMethod
方法能够覆盖到 class_addMethod
和method_setImplementation
; 对于第一个 class_replaceMethod
, 如果viewWillApplear
实现在父类中,则执行class_addMthod
,否则执行method_setImplementation
将原方法的IMP指向新的代码块,而第二个 class_replaceMethod
是将新方法的IMP 指向原代码块
注意: 此处应该特别注意交换顺序,应该优先把新方法指向原IMP,再修改原有方法的IMP
在不同类之间实现 Method Swizzing
+ (void)load {
Class originalClass = NSClassFromString(@"Car");
Class swizzledClass = [self class];
SEL originalSelector = NSSelectorFromString(@"run:");
SEL swizzledSelector = @selector(xxx_run:);
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
// 向Car类中新添加一个xxx_run:的方法
BOOL registerMethod = class_addMethod(originalClass,
swizzledSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (!registerMethod) {
return;
}
// 需要更新swizzledMethod变量,获取当前Car类中xxx_run:的Method指针
swizzledMethod = class_getInstanceMethod(originalClass, swizzledSelector);
if (!swizzledMethod) {
return;
}
// 后续流程与之前的一致
BOOL didAddMethod = class_addMethod(originalClass,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));
if (didAddMethod) {
class_replaceMethod(originalClass,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
- (void)xxx_run:(double)speed {
if (speed < 120) {
[self xxx_run:speed];
}
}
重点:
- 利用 runtime 向目标类中添加一个新的方法, 此时,目标类和当前类都存在相同的方法。当前类的左右就是提供在
load
方法中利用 runtime 给目标类提供这个方法 - 需要更新
swizzledMethod
变量为目标类的 添加的方法对应的(初始时指向的是当前类)
简化方式
+ (void)load {
Class originalClass = NSClassFromString(@"Car");
Class swizzledClass = [self class];
SEL originalSelector = NSSelectorFromString(@"run:");
SEL swizzledSelector = @selector(xxx_run:);
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
IMP originalIMP = method_getImplementation(originalMethod);
IMP swizzledIMP = method_getImplementation(swizzledMethod);
const char *originalType = method_getTypeEncoding(originalMethod);
const char *swizzledType = method_getTypeEncoding(swizzledMethod);
class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}
与Catetory 的方法无差异,但内部方法作用变了
- 第一个
class_replaceMethod
在目标类中注册对应方法(xxx_run:
),并指定 IMP 为当前 被替换方法(run:
)的 IMP - 第二个
class_replaceMethod
与之前的逻辑一致,当run:
方法是实现在Car
或其父类,分别执行method_setImplementation
或class_addMethod
类方法的 Method Swizzling
+ (void)load {
Class cls = [self class];
SEL originalSelector = @selector(dictionary);
SEL swizzledSelector = @selector(xxx_dictionary);
// 使用class_getClassMethod来获取类方法的Method
Method originalMethod = class_getClassMethod(cls, originalSelector);
Method swizzledMethod = class_getClassMethod(cls, swizzledSelector);
if (!originalMethod || !swizzledMethod) {
return;
}
IMP originalIMP = method_getImplementation(originalMethod);
IMP swizzledIMP = method_getImplementation(swizzledMethod);
const char *originalType = method_getTypeEncoding(originalMethod);
const char *swizzledType = method_getTypeEncoding(swizzledMethod);
// 类方法添加,需要将方法添加到MetaClass中
Class metaClass = objc_getMetaClass(class_getName(cls));
class_replaceMethod(metaClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(metaClass,originalSelector,swizzledIMP,swizzledType);
}
+ (id)xxx_dictionary {
id result = [self xxx_dictionary];
return result;
}
重点:
- 获取 Method 的方式变为
class_getClassMethod(Class cls, SEL name)
- 对于类方法的动态添加,需要添加到 MetaClass中,__实例方法记录在class的
method-list
中,类方法记录在meta-class
的letho-list
中
在类簇中实现
类簇: 例如常见的__NSArrayI
,__NSCFConstantString
等。class cluster
针对类簇的 Method Swizzling 问题就转化为如何对这些类簇中的私有类做 Method Swizzling
+ (void)load {
// 类簇类
Class originalClass = NSClassFromString(@"__NSDictionaryM");
// 当前自定义类
Class swizzledClass = [self class];
SEL originalSelector = @selector(setObject:forKey:);
SEL swizzledSelector = @selector(safe_setObject:forKey:);
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
IMP originalIMP = method_getImplementation(originalMethod);
IMP swizzledIMP = method_getImplementation(swizzledMethod);
const char *originalType = method_getTypeEncoding(originalMethod);
const char *swizzledType = method_getTypeEncoding(swizzledMethod);
class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
}
- (void)safe_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
if (anObject && aKey) {
[self safe_setObject:anObject forKey:aKey];
}
else if (aKey) {
[(NSMutableDictionary *)self removeObjectForKey:aKey];
}
}
恢复
方案一 通过一个开关标识符, 在实现中判定如果设定了该标识符, 逻辑就直接调用原方法的实现
方案二
针对方法交换的各种场景,主要做了两件事:class_addMehod
: 添加一个新的方法.将其他类的实现添加到目标类中; 将父类方法添加一份到子类中;添加实例方法; 添加类方法- 交换 IMP, 可以通过
method_exchangeImplementations
交换,也可以通过method_setImplementation
单独修改某个方法的IMP, 或者是采用在调用class_addMethod
方法中设定了IMP而直接就完成了IMP的交换, 总之就是对IMP的交换.
- 解决方案
class_copyMethodList
方法, 可以直接取出Method列表, 然后逐个遍历找到IMP所对应的Method
#import <objc/runtime.h>
@interface MySafeDictionary : NSObject
@end
static NSLock *kMySafeLock = nil;
static IMP kMySafeOriginalIMP = NULL;
static IMP kMySafeSwizzledIMP = NULL;
@implementation MySafeDictionary
+ (void)swizzlling {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
kMySafeLock = [[NSLock alloc] init];
});
[kMySafeLock lock];
do {
if (kMySafeOriginalIMP || kMySafeSwizzledIMP) break;
Class originalClass = NSClassFromString(@"__NSDictionaryM");
if (!originalClass) break;
Class swizzledClass = [self class];
SEL originalSelector = @selector(setObject:forKey:);
SEL swizzledSelector = @selector(safe_setObject:forKey:);
Method originalMethod = class_getInstanceMethod(originalClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(swizzledClass, swizzledSelector);
if (!originalMethod || !swizzledMethod) break;
IMP originalIMP = method_getImplementation(originalMethod);
IMP swizzledIMP = method_getImplementation(swizzledMethod);
const char *originalType = method_getTypeEncoding(originalMethod);
const char *swizzledType = method_getTypeEncoding(swizzledMethod);
kMySafeOriginalIMP = originalIMP;
kMySafeSwizzledIMP = swizzledIMP;
class_replaceMethod(originalClass,swizzledSelector,originalIMP,originalType);
class_replaceMethod(originalClass,originalSelector,swizzledIMP,swizzledType);
} while (NO);
[kMySafeLock unlock];
}
+ (void)restore {
[kMySafeLock lock];
do {
if (!kMySafeOriginalIMP || !kMySafeSwizzledIMP) break;
Class originalClass = NSClassFromString(@"__NSDictionaryM");
if (!originalClass) break;
Method originalMethod = NULL;
Method swizzledMethod = NULL;
unsigned int outCount = 0;
Method *methodList = class_copyMethodList(originalClass, &outCount);
for (unsigned int idx=0; idx < outCount; idx++) {
Method aMethod = methodList[idx];
IMP aIMP = method_getImplementation(aMethod);
if (aIMP == kMySafeSwizzledIMP) {
originalMethod = aMethod;
}
else if (aIMP == kMySafeOriginalIMP) {
swizzledMethod = aMethod;
}
}
// 尽可能使用exchange,因为它是atomic的
if (originalMethod && swizzledMethod) {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
else if (originalMethod) {
method_setImplementation(originalMethod, kMySafeOriginalIMP);
}
else if (swizzledMethod) {
method_setImplementation(swizzledMethod, kMySafeSwizzledIMP);
}
kMySafeOriginalIMP = NULL;
kMySafeSwizzledIMP = NULL;
} while (NO);
[kMySafeLock unlock];
}
- (void)safe_setObject:(id)anObject forKey:(id<NSCopying>)aKey {
if (anObject && aKey) {
[self safe_setObject:anObject forKey:aKey];
}
else if (aKey) {
[(NSMutableDictionary *)self removeObjectForKey:aKey];
}
}
@end
load 和 initialize 等方法调用时机
Load
- 所有类和分类的
load
方法都是在被加入到运行时调用 - 父类优先于子类加载
通过递归找到最根类然后依次加载 在所有本类加载完毕后,才会加载分类
[所有本类] -----> [所有类分类]同一个类的各个分类的加载顺序是按照 编译顺序决定的(Build Phases ----> Compile Sources)
测试
@implementation Car
+ (void)load {
NSLog(@"Car===>load");
}
+(void)initialize {
NSLog(@"Car===>initialize");
}
@end
// Dog
@implementation Dog
+ (void)load {
NSLog(@"Dog===>load");
}
+(void)initialize {
NSLog(@"Dog===>initialize");
}
@end
// Car 子类
@implementation SmallCar
+ (void)load {
NSLog(@"SmallCar===>load");
}
+(void)initialize {
NSLog(@"Car =====>SmallCar===>initialize");
}
@end
结果
// log
2019-08-24 15:45:58.543374+0800 OCTest[15023:5027880] Car===>load
2019-08-24 15:45:58.543763+0800 OCTest[15023:5027880] SmallCar===>load
2019-08-24 15:45:58.543799+0800 OCTest[15023:5027880] Dog===>load
2019-08-24 15:45:58.543923+0800 OCTest[15023:5027880] Main
类加载顺序(Build Phases ---> Compile Sources)
SmallCar.m
Dog.m
Car.m
main.m
总结
- 所有的load 方法都会在main函数之前执行
- 会先执行父类的 load 方法再执行子类的
- 遇到 子类 在
Complie Sources
前列,会先执行对应的父类,然后按照顺序再往下执行,遇到子类在父类之前,先执行父类,再执行子类
initialize
- 不能主动通过
[super initialize]
调用initialize
方法 ,此方法系统调用 runtime 会在每个类及其子类收到第一个消息之前调用
initialize
方法父类早于子类调用
- 初始化的时候,会在安全线程调用,尽量不要写复杂逻辑(容易引起死锁)
- 如果子类没有实现
initialize
方法,其父类该方法会被调用多次 通过以下方法避免 调用多次
+(void)initialize { if (self == [ClassName self]) { // do something } }
此方法每个类只会调用一次, 如果需要对某个类或分类进行独立初始化, 可以通过
load
方法实现分类会会覆盖本类中的方法。
此处可查看runtime源码
objc-runtime-new.mm ----> lookUpImpOrForward
方法
应用
- 可以在此方法中对部分静态变量赋值
测试
+ (void)load {
NSLog(@"Car===>load");
}
+(void)initialize {
NSLog(@"Car===>initialize");
}
+(void)carTest {
NSLog(@"Car===> class function test");
}
+ (void)load {
NSLog(@"Dog===>load");
}
+(void)initialize {
NSLog(@"Dog===>initialize");
}
+(void)dogTest {
NSLog(@"Dog===> class function test");
}
+ (void)load {
NSLog(@"SmallCar===>load");
}
+(void)initialize {
NSLog(@"Car =====>SmallCar===>initialize");
}
+(void)test{
NSLog(@"SmallCar=====>class foundation test");
}
结果
调用子类的方法 ,两种情况
- 子类实现,父类也实现
先调用父类, 再调用子类2019-08-24 15:58:53.764653+0800 OCTest[16463:5036604] Car===>load 2019-08-24 15:58:53.765056+0800 OCTest[16463:5036604] SmallCar===>load 2019-08-24 15:58:53.765076+0800 OCTest[16463:5036604] Dog===>load 2019-08-24 15:58:53.765154+0800 OCTest[16463:5036604] Main 2019-08-24 15:58:53.765178+0800 OCTest[16463:5036604] Car===>initialize 2019-08-24 15:58:53.765200+0800 OCTest[16463:5036604] Car =====>SmallCar===>initialize 2019-08-24 15:58:53.765222+0800 OCTest[16463:5036604] Car===> class function test
- 子类未实现, 父类实现
父类的会被调用两次2019-08-24 16:00:35.230411+0800 OCTest[16734:5037852] Car===>load 2019-08-24 16:00:35.231204+0800 OCTest[16734:5037852] SmallCar===>load 2019-08-24 16:00:35.231232+0800 OCTest[16734:5037852] Dog===>load 2019-08-24 16:00:35.231320+0800 OCTest[16734:5037852] Main 2019-08-24 16:00:35.231347+0800 OCTest[16734:5037852] Car===>initialize 2019-08-24 16:00:35.231369+0800 OCTest[16734:5037852] Car===>initialize 2019-08-24 16:00:35.231389+0800 OCTest[16734:5037852] Car===> class function test
子类实现,父类未实现
子类调用,父类的无2019-08-24 16:03:01.106076+0800 OCTest[17017:5039492] Car===>load 2019-08-24 16:03:01.106563+0800 OCTest[17017:5039492] SmallCar===>load 2019-08-24 16:03:01.106586+0800 OCTest[17017:5039492] Dog===>load 2019-08-24 16:03:01.106673+0800 OCTest[17017:5039492] Main 2019-08-24 16:03:01.106705+0800 OCTest[17017:5039492] Car =====>SmallCar===>initialize 2019-08-24 16:03:01.106726+0800 OCTest[17017:5039492] Car===> class function test
- 子类实现,父类也实现
特殊情况
此种情况下会造成
initialize
在main
函数之前执行
+ (void)load {
NSLog(@"Car===>load");
[Car carTest];
}
+(void)initialize {
NSLog(@"Car===>initialize");
}
+(void)carTest {
NSLog(@"Car===> class function test");
}
在 类的 load
方法中调用类方法.
但这种情况并没违背整体调用顺序, 不过 initialize方法在 load之前调用了
总结
load 方法在 main 方法之前调用,
initialize
方法是在第一个消息之前调 用 (都是在对象初始化之前调用)
参见特殊情况如果在A类的 load 方法中调用 B 类的类方法,那么A 类的 load 方法之后会调用 B 类的
initialize
方法,然后执行调用的类方法, 最后, B类的 Load 方法按照Compile Source
顺序调用+ (void)load { NSLog(@"Dog===>load"); [Car carTest]; }
注意,此处需要调整
Compile Source
的顺序,让 Dog 类先加载, 不同的加载顺序将导致不同的结果结果
2019-08-24 16:08:33.182855+0800 OCTest[17636:5043261] Dog===>load 2019-08-24 16:08:33.183289+0800 OCTest[17636:5043261] Car===>initialize 2019-08-24 16:08:33.183313+0800 OCTest[17636:5043261] Car===> class function test 2019-08-24 16:08:33.183332+0800 OCTest[17636:5043261] Car===>load 2019-08-24 16:08:33.183351+0800 OCTest[17636:5043261] SmallCar===>load 2019-08-24 16:08:33.183475+0800 OCTest[17636:5043261] Main
此处有个问题,如果采用方法 Method Swizzing 方法,在 load 的方法中进行方法交换(交换类方法), 那么可能调用到的是未交换的方法
所有类都有
load
方法,都会被调用,分类也是,initalize
方法会被分类覆盖,子类父类分类只会执行一个load 方法内部一般用来实现 Method Swizzle,Initialize方法一般用来初始化全局变量或者静态变量
两个方法都不能主动调用,也不需要通过 super 继承父类方法,但是 Initialize 方法会在子类没有实现的时候调用父类的该方法,而 load 不会
图片总结