ios 黑魔法Swizzling的应用---分解ZFPlayer
ios黑魔法实际上就是方法之间的调换
Method_Swizzling是发生在运行时的,主要用于在运行时将两个Method进行交换,我们可以将Method Swizzle代码写到任何地方,但是只有在Method_Swizzling这段Method Swizzle代码执行完毕之后互换才起作用。Method_Swizzling交换时机:尽可能在+load方法中实现
SEL 和 IMP 扩展
SEL: 类成员方法的指针,不同于C的函数指针,函数指针保存着对应方法的内存地址,而SEL仅仅是一个方法对应编号
IMP:函数指针,保存着对应方法的内存地址
他们的关系是:每一个继承NSObject的类都可以获得runtime的支持,class类的本质是一个结构体,该结构体中有一个isa的指针(指向对象的类),这个结构体是编译器在类编译时为其创建的,这个结构体中还包含着一个Dispatch table(是一张SEL和IM对应的表),so,通过SEL的方法变化在Dispatch table找到编号对应的IMP,并执行对应的方法。
Swizzling就可以利用了SEL编号可以对应不同的IMP来实现其功能。
// // NSArray+Swizzling.m // MDHSwizzling // // Created by Apple on 2018/10/25. // Copyright © 2018年 马大哈. All rights reserved. // #import "NSArray+Swizzling.h" #import <objc/runtime.h> #import <UIKit/UIKit.h> @implementation NSArray (Swizzling) // load类方法: 创建分类的时候系统会自动调用 + (void)load { // dispatch_once 用于保证swizzling效果只执行一次,反之函数多次调换(防止调换回来的情况)就无法起到应用作用。 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ /* NSArray __NSArrayI & __NSCFArray NSMutableArray __NSArrayM NSDictionary __NSDictionaryI NSMutableDictionary __NSDictionaryM */ // OC在编译时,会根据每个方法的名字,参数序列,生成一个唯一的整型标识(int类型的地址),这个标识就是SEL SEL originalSelector = @selector(objectAtIndex:); SEL swizzledSelector = NSSelectorFromString(@"safe_ObjectAtIndex:"); // 获取实例方法 (类方法用 class_getClassMethod) Method originalMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), originalSelector); Method swizzledMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), swizzledSelector); SEL originalSelector1 = @selector(objectAtIndexedSubscript:); SEL swizzledSelector1 = NSSelectorFromString(@"safe_objectAtIndexedSubscript:"); Method originalMethod1 = class_getInstanceMethod(objc_getClass("__NSArrayI"), originalSelector1); Method swizzledMethod1 = class_getInstanceMethod(objc_getClass("__NSArrayI"), swizzledSelector1); /* 这里为什么不能 两个需要替换的方法全都使用 swizzledMethod 同一个呢 ? method_exchangeImplementations(originalMethod1, swizzledMethod); 这回造成 objectAtIndexedSubscript 指向 objectAtIndex,而无法起到应用的作用 原因如下: 第一次交换,发送objectAtIndex消息却执行safe_ObjectAtIndex方法,发送safe_ObjectAtIndex消息却执行objectAtIndex 第二次交换,objectAtIndexedSubscript发送执行safe_ObjectAtIndex,但是第一次的时候safe_ObjectAtIndex已经执行了objectAtIndex就会有问题 所以,必须另起炉灶 method_exchangeImplementations: 向array发送objectAtIndex执行的是safe_ObjectAtIndex替代函数进行异常处理,crash规避。 */ method_exchangeImplementations(originalMethod, swizzledMethod); method_exchangeImplementations(originalMethod1, swizzledMethod1); if (([UIDevice currentDevice].systemVersion.floatValue < 10.0f)){ // ios10 以后解析的网络数组数据由 __NSCFArray 变成了 __NSArrayI Method oldObjectAtIndex3 = class_getInstanceMethod(objc_getClass("__NSCFArray"), @selector(objectAtIndex:)); Method newObjectAtIndex3 = class_getInstanceMethod(objc_getClass("__NSCFArray"), @selector(safe_ObjectAtIndex:)); method_exchangeImplementations(oldObjectAtIndex3, newObjectAtIndex3); } }); } - (id)safe_ObjectAtIndex:(NSInteger)index { if (index > self.count - 1 || index < 0) { // 异常处理 @try { return [self safe_ObjectAtIndex:index]; } @catch (NSException *exception) { // 在崩溃后会打印崩溃信息 NSLog(@"---------- %s Crash Because Method %s ----------\n %@", class_getName(self.class), __func__,[exception callStackSymbols]); return @""; } @finally {} } else { return [self safe_ObjectAtIndex:index]; } } - (id)safe_objectAtIndexedSubscript:(NSInteger)index { if (index > self.count - 1 || index < 0) { // 异常处理 @try { return [self safe_ObjectAtIndex:index]; } @catch (NSException *exception) { // 在崩溃后会打印崩溃信息 NSLog(@"---------- %s Crash Because Method %s ----------\n %@", class_getName(self.class), __func__,[exception callStackSymbols]); return @""; } @finally {} } else { return [self safe_ObjectAtIndex:index]; } } @end