前言
这篇文章紧接上一篇, runtime
理论我想大家都知道的, 上一篇是站在个人理解的角度, 探讨了相关的一些动态原理,和基本实现思路,最后拓展到runtime框架, 而且也简单写了动态添加属性和KVO简单实现的demo,如果有兴趣可以去看深入理解OC的运行时(Runtime)
编码相关
具体可通过编译指令 @encode(type) typeof()
@encode(type)
必须传入类型 不能是具体的变量
typeof()
传入的可以是变量, 可以是类型, 通过typeof
获取到类型然后传给@encode
,最后会返回对应的const char*
, 这个就是类型的编码, 同样可以获取到block
和函数
的(相当于没有获取到), 这里举出一些例子:
** 先定义一个Log输出的宏(来自RAC宏的运用改编,有时间会专门写一篇宏相关的东西) ,这里设置的最多10个参数, 如果不够,可以自己看着往后加
**
static inline NSString* _LOG_GET_FORMAT(const char* typeCode,size_t size){
NSString* result;
///无符号
if (strcmp(typeCode, "I") == 0) result = @"%u";
else if (strcmp(typeCode, "Q") == 0) result = @"%lu";
else if (strcmp(typeCode, "C") == 0) result = @"%d";
else if (strcmp(typeCode, "S") == 0) result = @"%d";
else if (strcmp(typeCode, "i") == 0) {
/// 可能是 'a' 也可能是 10
if (size == sizeof(char)) {
result = @"%c";
}else result = @"%d";
}else if(strcmp(typeCode, "q") == 0){
result = @"%ld";
}else if(strcmp(typeCode, "c") == 0){
result = @"%c";
}else if(strcmp(typeCode, "s") == 0){
result = @"%d";
}else if(strcmp(typeCode, "*") == 0){
/// char*
result = @"%s";
}else if(strcmp(typeCode, "f") == 0){
result = @"%f";
}else if(strcmp(typeCode, "d") == 0){
result = @"%f";
}else{
NSString* tmp = [NSString stringWithFormat:@"%s",typeCode];
if ([tmp hasSuffix:@"c]"] || [tmp hasSuffix:@"C]"]) {
result = @"%s";
}else if([tmp hasPrefix:@"^"]){ ///指针
result = @"%p";
}else if([tmp isEqualToString:@"@"]) {
result = @"%@";
}else {
result = [NSString stringWithFormat:@"%%%s",typeCode];
}
}
return result;
}
#if __OBJC__
#define getTypeStr(_V) {\
NSString* _LogFormatStr = _LOG_GET_FORMAT(@encode(__typeof(_V)),sizeof(_V));\
NSLog(@"after: %@",_LogFormatStr);\
if([_LogFormatStr isEqualToString:@"%@"]) {\
NSLog(@"对象的打印 %@",_LogFormatStr);\
NSLog(_LogFormatStr,_V);\
}\
else {\
printf(_LogFormatStr.UTF8String,_V);\
printf("\n");\
}\
}
#define LogMaxArg 10
#define LogArgList0 10,9,8,7,6,5,4,3,2,1
#define Log(...) Log_args(LogMaxArg,__VA_ARGS__,LogArgList0)(__VA_ARGS__)
#define Log_args(N,...) Log_Find_index(__VA_ARGS__,LogArgList0)
#define Log_Find_index(_0,_1,_2,_3,_4,_5,_6,_7,_8,_9,...) LogFun(Log_,__VA_ARGS__)
#define LogFun(Fun,F,...) Fun##F
#define Log_1(_V) getTypeStr(_V)
#define Log_2(_V,...) getTypeStr(_V);Log_1(__VA_ARGS__)
#define Log_3(_V,...) getTypeStr(_V);Log_2(__VA_ARGS__)
#define Log_4(_V,...) getTypeStr(_V);Log_3(__VA_ARGS__)
#define Log_5(_V,...) getTypeStr(_V);Log_4(__VA_ARGS__)
#define Log_6(_V,...) getTypeStr(_V);Log_5(__VA_ARGS__)
#define Log_7(_V,...) getTypeStr(_V);Log_6(__VA_ARGS__)
#define Log_8(_V,...) getTypeStr(_V);Log_7(__VA_ARGS__)
#define Log_9(_V,...) getTypeStr(_V);Log_8(__VA_ARGS__)
#define Log_10(_V,...) getTypeStr(_V);Log_9(__VA_ARGS__)
#endif
使用
////打印整型浮点,基本指针
int a = 100;
float b = 1.23432e3;
char ch = 'A';
Log(a,b, 'a', ch, 919123L);
//// 打印结果
100
1234.319946
97
A
919123
////打印指针
float* fp = &b;
Log(fp, *fp, &b);
///打印结果
0x7ffee56ce8e8
1234.319946
0x7ffee56ce8e8
///打印c字符串
char *s = "ssssss";
char array[] = "array";
Log("aaa","bbb","ccc", s, array, 123, 890);
///打印结果:
aaa
bbb
ccc
ssssss
array
123
890
///打印oc对象 如果直接在宏的参数里填写字典,数组, 加上括号,整体括起来
NSArray* tmp = @[@"xxxx",@"bbbb",@{@"array":@123}];
Log(@"123",
(@{@"1233":@123}),
tmp,
"这个是c的字符串",
324.12331f
);
///打印结果:
{
1233 = 123;
}
(
xxxx,
bbbb,
{
array = 123;
}
)
这个是c的字符串
324.123322
各种数字(整型,浮点)编码
char: c
short: s
int: i
long: q
NSInteger: q
long long: q
float: f
double: d
unsigned char: C
unsigned short: S
unsigned int: I
unsigned long: Q
NSUInteger: Q
unsigned long long: Q
各种指针的编码 就是在对应的类型前面加上^
char* : *
short*: ^s
int*: ^i
long*: ^q
NSInteger*: ^q
long long*: ^q
float*: ^f
double*: ^d
unsigned char*: *
unsigned short*: ^S
unsigned int*: ^I
unsigned long*: ^Q
NSUInteger*: ^Q
unsigned long long*: ^Q
int**: ^^i
void* ^V
void** ^^V
c语言里数组的编码
short[]: ^s
int[10]: [10i]
long[5]: [5q]
long long[8]: [9q]
NSInteger[7]: [7q]
unsigned short[]: ^S
....///规律自己寻找
const相关的编码
const int a; i
int const b; i
int* const c; ^i
int const* d; r^i
const int* e; r^i
const int a[] = {3}; [1i]
const int b[2]; [2i]
const int* c[] = { NULL }; [1^i]
int* const d[2] = { NULL,NULL }; [2^i]
函数指针和block相关 ?代表着匿名
////局部的函数指针和block
void (*funtest)(void) = NULL; ^?
void (^blocktest)(void) = NULL; @?
int (*funtest1)(int) = NULL; ^?
int (^blocktest1)(int) = NULL;@?
结构体,联合体相关
///系统的
@encode(typeof(CGRect)) {CGRect={CGPoint=dd}{CGSize=dd}}
///自己定义的
struct tmp{
int a;
struct{
int b;
NSString* c;
};
};
///编码格式结果
{tmp=i{?=i@}}
///联合体
union{
int a;
int b;
}d;
////d的编码是 ?代表匿名 d这里是一个变量
(?=ii)
union tmpU{
int a;
int b;
}d;
////此时d的编码是
(tmpU=ii)
oc相关的编码
@encode(typeof(instance)) @
@encode(typeof(NSObject)) {NSObject=#}
@encode(typeof(UIViewController)) {UIViewController=#}
@interface Person :NSObject{
int* age;
NSString* name;
}
@end
@encode(typeof(Person)) {UIViewController=#}
////类里方法的编码通过runtime函数获取到method, 然后再获取编码
方法签名
1. 在NSObject里有一个签名的类NSMethodSignature
2. 获取NSMethodSignature
的途径有
2.1 通过NSObject的接口
例子如下:
@implementation VC :UIViewController
+ (void)classMT{}
- (void)objcMT{}
@end
/**
-[NSObject methodSignatureForSelector:SEL]这个方法在头文件里声明的是对象方法, 但是以类方法的调用形式去调用也可以, 不知道为什么
*/
///1 class 获取 object 方法
NSMethodSignature* obj = [ViewController methodSignatureForSelector:@selector(signatest1)];
NSLog(@"class get -: %p",obj);
///2 object 获取 object
obj = nil;
obj = [self methodSignatureForSelector:@selector(signatest1)];
NSLog(@"object get -: %p",obj);
////3 class 获取 class
obj = nil;
obj = [ViewController methodSignatureForSelector:@selector(signatest2)];
NSLog(@"class get +: %p",obj);
////4 object 获取 class
obj = nil;
obj = [self methodSignatureForSelector:@selector(signatest2)];
NSLog(@"objcet get +: %p",obj);
////5 class 获取 object 方式2
obj = nil;
obj = [ViewController instanceMethodSignatureForSelector:@selector(signatest1)];
NSLog(@"class get -: %p",obj);
////5 class 获取 class 方式2
obj = nil;
obj = [ViewController instanceMethodSignatureForSelector:@selector(signatest2)];
NSLog(@"class get +: %p",obj);
日志
0x0
0x600000228b00
0x600000228b00
0x0
0x600000228b00
0x0
总结: 1.如果是获取对象方法的签名, 就用对象去调用methodSignatureForSelector
或者用类去调用instanceMethodSignatureForSelector
2.如果是获取类方法的签名, 就用类去调用methodSignatureForSelector
获取签名对象后, 函数签名代码如下:
- (void)signatest3:(int)a arg2:(NSString*)b{
// return 1;
}
- (void)viewDidLoad {
[super viewDidLoad];
NSMethodSignature* obj = [self methodSignatureForSelector:@selector(signatest3:arg2:)];
for (NSInteger i = 0; i < obj.numberOfArguments; ++i) {
NSLog(@"%s",[obj getArgumentTypeAtIndex:i]);
}
NSLog(@"%s",obj.methodReturnType);
}
////@ : i @ V
////对应 IMP的调用参数表
//// 第0个 id @
//// 第1个SEL 对应 :
//// 第2个开始是参数 上面的是 int 所以是 i
//// 第3个 对应上面的 NSString* @
//// 返回值要单独获取, 这里是void 所以是V
2.2 通过runtime的接口
还是上面的那个类定义
Method m = class_getInstanceMethod(ViewController.class, @selector(signatest3:arg2:));
NSLog(@"%s",method_getTypeEncoding(m));
/// i28@0:8i16@20 这些数字代表什么含义,没懂, 但是去掉数字就和上面对应了,而且最开始的是从返回算的
3. 自己签名
创建签名对象
NSMethodSignature
的时候,给的签名类型,注意创建签名的时候, 要把返回值也带上
NSMethodSignature* method = [NSMethodSignature signatureWithObjCTypes:"i@:i@"];
for (NSInteger i = 0; i < method.numberOfArguments; ++i) {
NSLog(@"%s",[method getArgumentTypeAtIndex:i]);
}
NSLog(@"%s",method.methodReturnType);
/// @:i@ 对应的方法 - (int)name:(int)a arg:(id)obj;
/// 第一是返回值 所以创建的时候要指定返回值i
消息转发(利用系统提供的接口转发)
在oc里如果对对象发送消息时, 最后没有找到方法的实现, 系统会自动进入消息转发流程, 而且在oc的层面提供了接口,在这些接口里我们可以转发消息, 这里拿对象方法来说
1. 进入+(bool)resolveInstanceMethod:(SEL)
这里系统会将之前传入的SEL传递过来,我们知道, SEL是oc接收消息后去寻找方法匹配的标识,和IMP是分开的, 这里把SEL方法的标识传过来, 表示当前类没有与SEL对应的实现IMP, 所以这里就为这个SEL指定一个实现地址, 然后返回YES告诉系统处理了, 系统就不会往下走了
@implementation VC : UIViewController
- (void)forwardTestFun1{}
- (void)viewDidLoad{
[super viewDidLoad];
////这里瞎调用了一个方法, 写成这样是为了消除警告
objc_msgSend(self, sel_registerName("noMethodTest"));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel{
if (sel_isEqual(sel, sel_getUid("noMethodTest"))) {
class_addMethod(self, sel, imp_implementationWithBlock(^(id obj){
Log("已经被改变指向了,来到了这里执行");
}), "v@:");
return 1;
}
return [super resolveInstanceMethod:sel];
}
@end
这一步主要的用途是在本类中动态为sel添加一个Method, 如果这里返回false, 或什么都不管, 就会去下一步. 这里要说明一点, 就算将当前没有实现的
noMethodTest
的sel 改为forwardTestFun1
的sel传给父类,还是会崩溃
2. 消息转发的第2步:- (id)forwardingTargetForSelector:(SEL)aSelector
这一步要求给系统传一个对象, 要求这个对象有sel的实现, 比如传一个Person对象, 他实现了这sel
3. 如果2不处理, 即返回了nil,或者没有实现会进入到这里- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
, 这个方法要求对sel提供一个签名对象 eg:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [NSMethodSignature signatureWithObjCTypes:"V@:"];
}
这里的签名一定要和传入的sel对应, 这里表示
- (void)funName{ }
, v==void, @ == id : == sel 因为 objc_msgsend()的参数是 id, sel, 所以这里要提供@和:
4. 如果3不实现或者,返回nil,崩溃, 如果返回了签名则会执行: - (void)forwardInvocation:(NSInvocation *)anInvocation
, 这个方法会传递一个NSInvocation对象, 签名已经设置好了, 就是上面的一步我们设置的签名, 这个方法要求提供一个对象供anInvocation调用, 而且要求对象必须实现了 同名的sel的实现, 因为这里的anInvocation
的selector
系统赋值的是传过来的sel
,所以一定会调用新对象同名的方法,如果想指定新对象别的方法,这里需要更改anInvocation
的selector
属性值,但是对应实现的方法签名必须和当前参数sel
的一致。代码如下
@implementation Son :NSObject
- (void)test{
Log("消息会不会被转发来呢?");
}
- (void)noMethodTest{
Log("消息一定会转发到这里");
}
@end
VC中:
- (void)forwardInvocation:(NSInvocation *)anInvocation{
///如果上一步提供的签名不对, 也是崩溃的
[anInvocation invokeWithTarget:Son.new];
///这里可以获取到参数信息
NSMethodSignature* si = anInvocation.methodSignature;
for (NSInteger i = 2; i < si.numberOfArguments; ++i) {
@autoreleasepool {
__autoreleasing NSObject* s;
[anInvocation getArgument:&s atIndex:i];
NSLog(@"%@",s);
}
}
}
///如果不实现noMethodTest, 崩溃, 方法找不到,如果实现了会调用noMethodTest
手动触发消息转发(方法监听AOP编程)
1. 手动触发消息转发还是和原理一样的, 要实现即使 当前调用的sel有对应的IMP, 也要触发转发流程. 这里要说一点就是, 处理的操作肯定是在 转发流程的其中一步, 因为既然是转发, 一定会走到转发流程的函数,所以我们处理的地方就是在那几步当中(针对当前类来说,后面拓展到通用的时候实现方案又不一样的)
场景1 假设现在的需求是 项目里所有Person对象用到的地方, 只要调用了- [Person runFrom: to:]
都必须在调用前打印 hello world
解决方案选择
方案1
- 找到工程里所有的
Person实例化的对象
, 然后查找哪里调用了- [Person runFrom: to:]
, 在每一个调用前都加上输出语句
方案2
-
修改Person中对应方法
的实现, 在- [Person runFrom: to:]
的实现里, 最开始加上输出语句
方案3
-
修改Person
类的实现, 在Person.m文件里加入私有的类比如_PersonHelloWord
继承自Person
,修改Person的初始化操作
返回_PersonHelloWord
, 然后_PersonHelloWord
的实现里重写- [Person runFrom: to:]
, 加入输出语句, 再调用父类的方法
方案4
-
不修改Person类, 也不创建子类
, 利用runtime的方案, 这里可以为Person添加一个分类, 在分类里写一个新的方法, 然后利用方法交换的技术实现需求, 当然这不算是消息转发,注意即使他妈的输出语句是不需要参数的,但是交互的方法也必须提供
方案5
-
修改Person
, 用消息转发的技术去处理, 具体的介绍在方案4的代码之后
方案6
-
KVO
, 自己实现KVO的过程, 上一篇深入理解OC的运行时(Runtime)里详细的代码.
1,2种我就不说了, 至于第3种,我想没有人会这样搞, 这种方案可能会用在别的情况下, 总之既然是方案, 那就没有好坏之分, 只有适用和不适用之说, 第4种我下面简单给出伪代码, 第5种要重点探讨的, 第6种会介绍一下,给出核心代码
方案4 的部分代码
@interface Person :NSObject
- (void)runFrom:(NSString*)addressStart to:(NSString*)addressEnd;
@end
@implementation Person
- (void)runFrom:(NSString*)addressStart to:(NSString*)addressEnd{
Log("走啊走,走啊走,走啊走!");
}
@end
@interface Person(HelloWorld)
+ (void)helloWorldInit;
@end
@implementation Person(HelloWorld)
+ (void)helloWorldInit{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Method originM = class_getInstanceMethod(self, @selector(runFrom:to:));
Method newM = class_getInstanceMethod(self, @selector(helloworld: to:));
////这里不需要考虑 replace的问题([详见上一篇里方法交换的部分)
method_exchangeImplementations(originM,newM);
});
}
- (void)helloworld:(NSString*)cao to:(NSString*)nima{
Log("产品有病啊, 输出一句hello world, 日!");
[self helloworld:cao to:nima];
}
@end
方案5 的实现方案介绍
0
1 要实现消息转发, 在正常sel存在IMP的情况下, 如果不做处理, 是不会进入转发流程的, 所以要做的事情是手动代码控制, 进入转发流程
手动进入转发流程有多种方案, 但原理都是修改sel的IMP(这个runtime提供了接口)
1.1 让当前的sel指向一个 和sel原始IMP签名不一样的函数或block, 因为签名不对, 找不到实现进入转发
1.2 直接让sel指向 系统转发函数 _objc_msgForward, 强制进入转发
2 进入转发流程后, 在第三步进行操作-[NSObject forwardInvocation:invo]
3 -[NSObject forwardInvocation:invo]
会将函数签名和参数类型,返回类型, 消息接收者等包装到invo
中
4 我们要将 当前的sel
, 即runFrom:to:
通过 invo
指定一个新的 newTarget
对象, 让这个newTarget
去执行剩下的操作
5 newTarget
执行 runFrom:to:
, 首先 输出 hello wrold
, 然后再想办法回调原来的实现
6 所以 第4步中, 在指定给newTarget
的时候, 要将当前对象eg:originalTarget
传递给newTarget
, 因为假设 newTarget
能调用到原来的 runFrom:to:, 但是原始的runFrom:to:里可能通过 originalTarget
来获取相关的成员变量等信息, 这就要求了 newTarget
在回调原来的方法时要原封不动的通过 [originalTarget runFrom:arg0 to:arg1]
的方式去调用,这不需要传递参数, 因为转发给newTarge
的 runFrom:to:
时,参数也传递了过来
7 第6步的分析又出现新的问题, 就是 [originalTarget runFrom:arg0 to:arg1]
会直接进入消息转发, 因为上面已经说了, originalTarget
的 runFrom:to:
已经被替换成了_objc_msgForward, 那怎么解决呢
8 第7步解决的方案, 那只有在将 runFrom:to
的IMP替换为 _objc_msgForward
之前, 就先记录下来, 然后在 4步里创建newTarget
的时候, 将IMP赋值给newTarget
, 这样newTarget
可以直接调用
IMP(newTarget.originalTarget, NSSelectorFromeString(@"runFrom:to:"), arg0,arg1)
9 反思, 上述流程里是静态创建了一个转发对象newTarget
, 那么newTarget
的类型,即类的设计可以考虑静态xcode的方式去创建, 也可以考虑动态创建, 那既然这里讲的是runtime, 那就用runtime来创建类, 岂不是逼格更高?!!
10 选择方案, 动态创建当前类Person的子类, 然后添加 子类的 runFrom:to:
的IMP, 而且动态创建的子类要有扩充的内存, 用来添加一个元素的对象originalTarget
, 这里就用到了 runtime的 add_Var的技巧了, 具体流程图如下:(有点丑,但不要纠结)
核心代码
@interface Person : NSObject
@property (nonatomic,strong) NSString* name;
- (void)runFrom:(NSString*)startAddStr to:(NSString*)endAddStr;
@end
@implementation Person
- (void)runFrom:(NSString*)startAddStr to:(NSString*)endAddStr{
NSLog(@"%@正在run... 从%@ 到 %@",self.name,startAddStr,endAddStr);
}
+ (void)initialize{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self helloRegisterMethod];
});
}
#pragma mark - 每个对象都调换
+ (void)helloRegisterMethod{
////找到目标方法
SEL tarSEL = sel_getUid("runFrom:to:");
Method tarM = class_getInstanceMethod(self, tarSEL);
////如果找到
if (tarM) {
///子类的类名 当前类的名字 + _ + subClass
NSString* subClass = [NSStringFromClass(self) stringByAppendingFormat:@"_subClass"];
///创建子类 顺便给一个var的空间, 用来保存转发前的父类的实例对象, 目的是调用父类的实现的时候, 将这个对象传给IMP, 保证原来的方法中做的事情不会被更改
Class sub = objc_allocateClassPair(self, subClass.UTF8String, sizeof(void*));
if (sub) {
/////这里先获取到IMP的地址, 如果在block内部获取, 不知道为什么获取的是 _objc_msgForward的地址
IMP org = method_getImplementation(tarM);
////添加一个var
NSString* varName = [NSStringFromClass(self) stringByAppendingFormat:@"_sub_%@",NSStringFromSelector(tarSEL)];
///这里解释下第3g 参数, 内存对齐的问题, 他的值与 要添加var的类型有关, 也和cpu的架构有关,如果是指针, 则传log2(sizeof(type*)), 官方文档说的很清楚
class_addIvar(sub,
varName.UTF8String,
sizeof(void*),
log2(sizeof(void*)),
@encode(NSString*));
////为子类添加 一个同样的sel的实现, 相当于是覆盖了父类
bool addMethod = class_addMethod(sub, tarSEL, imp_implementationWithBlock(^void(id obj,NSString* arg1,NSString* arg2){
///先输出hello world
NSLog(@"hello world!");
NSString* varName = [NSStringFromClass(self.class) stringByAppendingFormat:@"_sub_%@",NSStringFromSelector(tarSEL)];
Ivar var = class_getInstanceVariable(sub, varName.UTF8String);
if (var) {
///再调用原始的imp
org(object_getIvar(obj, var),tarSEL,arg1,arg2);
}
}), method_getTypeEncoding(tarM));
if (addMethod) {
NSLog(@"添加成功");
}
///注册子类
objc_registerClassPair(sub);
class_replaceMethod(self, tarSEL, _objc_msgForward, method_getTypeEncoding(tarM));
}
}
}
- (void)forwardInvocation:(NSInvocation *)anInvocation{
NSString* subClass = [NSStringFromClass(self.class) stringByAppendingFormat:@"_subClass"];
Class sub = objc_getClass(subClass.UTF8String);
if (sub) {
id subObj = [sub new];
NSString* varName = [NSStringFromClass(self.class) stringByAppendingFormat:@"_sub_%@",NSStringFromSelector(anInvocation.selector)];
Ivar var = class_getInstanceVariable(sub, varName.UTF8String);
if (var) {
object_setIvar(subObj, var, anInvocation.target);
}
return [anInvocation invokeWithTarget:subObj];
}
}
@end
方案6 的实现
- 在
Person里加一个类方法
, 最好在Person初始化或者第一次用的的时候调用
- 在这个类方法中像上一篇实现KVO的原理一样,
只不过这里不是替换setter,这里是替换 runFrom:to:
- 新的实现里 先输出
hello wrold
, 然后调用父类的实现 - 这里要注意的一点是,怎么调用父类的代码, 这里先插入一段关于super调用的问题
super代表的仅仅是编译器指令, 并不是一个实体的对象, 比如
[super callFun:arg0]
, 代码解析如下
///我们写的代码
[super callFun:arg0];
///最后编译器编译成
objc_msgSendSuper(tmpSuper->receiver, callFun, arg0)
///查看objc_msgSendSuper的定义
objc_msgSendSuper(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
///查看接头体的定义
struct objc_super {
__unsafe_unretained _Nonnull id receiver;
#if !defined(__cplusplus) && !__OBJC2__
__unsafe_unretained _Nonnull Class class;
#else
__unsafe_unretained _Nonnull Class super_class;
#endif
};
//参数1 代表消息接收者, 参数2代表superClass的类对象
//编译器创建临时的tmpSuper伪代码如下
struct objc_super* tmpSuper = malloc(sizeof(struct objc_super));
tmpSuper->receiver = self;
tmpSuper->super_class = class_getSuperclass( object_getClass(self) );
///然后将创建好的 tmpSuper传给 objc_msgSendSuper
objc_msgSendSuper(tmpSuper, callFun, arg0);
///objc_msgSendSuper的内部会根据 tmpSuper的 super_class, 找到父类类对象的地址
/// 然后去方法列表里寻找方法, 找到方法之后eg:findIMP, 就会直接通过IMP调用
findIMP(tmpSuper->receiver, callFun, arg0);
///所以很多面试里问道 [super class], 返回的class是什么类型, 其实就像上面说的过程一样,最后接收消息的对象始终没有变, 所以还是当前的类
///有的文档说objc_msgSendSuper的内部在找到方法的时候, 会再次调用 objc_msgSend(tmpSuper->receiver, callFun, arg0)
///我想应该不是这样, 这样和 [self callFun:arg0] 没有区别了, 因为super表示到父类里找实现
///调用父类里处理的逻辑, 主要是和self覆盖的callFun: 分开来, 所以我认为是找到findIMP后, 直接执行了
free(tmpSuper);
需求实现核心代码
Class reallyClass = object_getClass(self);
NSString* tmpClassName = [_ClassPrefix stringByAppendingString:NSStringFromClass(reallyClass)];
///创建新的class
Class newClass = objc_allocateClassPair(reallyClass, tmpClassName.UTF8String, 0);
///将Person的isa指向新建的类
object_setClass(self, newClass);
class_addMethod(newClass, NSSelectorFromString(@"runFrom:to:"), imp_implementationWithBlock(^void(__weak id obj, id value1, id value2){
NSLog(@"hello world");
////自己构建objc_msgSendSuper的第1个结构体
struct objc_super tmpSuper = {
obj,
class_getSuperclass( object_getClass(obj) );
};
objc_msgSendSuper(&tmpSuper, NSSelectorFromString(@"runFrom:to:"), value1, value2)
}), "v@:@");
objc_registerClassPair(newClass);
上述是为整个类替换掉! 能不能只指定某个对象, 其他对象不影响呢!!, 其实上一篇的KVO实现就是针对某个对象的, 其他对象不受影响
场景2 和上述需求一样, 监听某个方法, 在方法执行之后或之前做事情, 但是不同的是, 场景2要求只监听某些对象, 没有被监听的对象不影响
比如
Car
这个类, 监听方法runFrom:to:
, 希望在这个方法执行后, 再输出一句洗车
, 但是只是Car
的实例 car1会改变动作, 其他的car2, car3. car4等不受影响
对比场景1
和场景1不一样的地方是, 这里只针对某些对象, 有点像上一篇的KVO(深入理解OC的运行时(Runtime)), 这里也有 几种对应关系:
1VS1
比如 vc1
想监听car1
的这个动作, 然后在car1
执行完方法后, 做自己的事情
1VS多
, 比如view1也想像VC
一样监听 car1
, 做自己的操作
多VS1
VC
想同时监听 car1, car2
.... 总之和kvo的性质一样, 但是这里的实现和kvo有所不同
解决方案选择
利用runtime的进行转发, 用实现KVO一样的原理, 但是这里是不再改变原有的类, 代码如下
Car
定义
@interface Car : NSObject
/** 车牌号*/
@property(strong, nonatomic) NSString* licence;
- (void)runFrom:(NSString*)start to:(NSString*)end;
@end
@implementation
- (void)runFrom:(NSString*)start to:(NSString*)end{
NSLog(@"车牌是:%@ 从 %@ 开到了 %@",self.licence, start, end);
}
@end
全局字典的格式
@{
@"对象1的hash字符串":@{ 对象1相关的信息字典
@"sel1字符串":@{ 对象1的 sel1 相关信息的字
@"before": @[block1, block2, ...].multable
@"replace": @[block1, block2, ...].multable
@"after":@[block1,block2, ... ].multable
}.mutable,
@"sel2字符串":@{ 对象1的 sel2 相关信息的字典
///和sel1同样的结构
}.mutable,
...
}.multable,
@"对象2的hash字符串":@{
和对象1同样的结构
}.multable
}.multable
转发分类 相关宏的头文件
#ifndef LBForwardToolMacro_h
#define LBForwardToolMacro_h
#ifdef __OBJC__
#define LB_CAT(A,B) A##B
#pragma mark - LBF error macro
#define LBFEC(_C) LB_CAT(LBFEC,_C)
#define LBFOT(_V) LB_CAT(LBFOT, _V)
#endif
错误类的定义
#import <Foundation/Foundation.h>
#import "LBForwardType.h"
#define TimeBegin_ 2000
/** LBForwardErrorCode */
typedef NS_ENUM(int,LBFEC) {
/** 其他错误 */
LBFEC(Other) = INT_MIN,
/** 回调不能空 */
LBFEC(EmptyOperation) = TimeBegin_,
/** sel不能空 */
LBFEC(EmptySEL),
/** delloc的监听时间点必须是调用dealloc之前 */
LBFEC(DellocTime),
/** 禁止监听alloc */
LBFEC(AllocForbid),
/** 禁止监听initXXXX */
LBFEC(InitForbid),
/** 禁止监听ARC相关的 内存管理 */
LBFEC(ARCForbid),
/** 禁止监听转函数 forwardInvocation: */
LBFEC(SYSForward),
/** 没有错误 */
LBFEC(NoError),
};
#undef TimeBegin_
NS_ASSUME_NONNULL_BEGIN
@interface LBForwardError : NSObject
/** 错误码 */
@property (nonatomic,readonly) LBFEC eCode;
/** 错误描述 */
@property (nonatomic,strong) NSString* eDes;
+ (instancetype)lbEWithCode:(LBFEC)code;
@property (nonatomic,copy,readonly) void (^otherErrorDes)(NSString* _Nonnull des);
+ (instancetype)lbOEWithDes:(NSString*)des;
@end
NS_ASSUME_NONNULL_END
#define LBError(_C) [LBForwardError lbEWithCode:(_C)]
@implementation LBForwardError
+ (instancetype)lbEWithCode:(LBFEC)code{
return [[self alloc] initWithCode:code];
}
- (instancetype)initWithCode:(LBFEC)code{
if (self = [super init]) {
self->_eDes = [self switchCode:code];
}
return self;
}
- (NSString*)switchCode:(LBFEC)code{
_eCode = code;
return [LBForwardError switchCode:code];
}
+ (NSString*)switchCode:(LBFEC)code{
switch (code) {
case LBFECEmptyOperation: return @"回调不能空";
case LBFECEmptySEL: return @"原SEL不能空";
case LBFECDellocTime:return @"回调必须在调用dealloc之前";
case LBFECAllocForbid:return @"alloc 不能监听";
case LBFECInitForbid: return @"initXXX 不能监听";
case LBFECARCForbid: return @"ARC(retain release, autoRelease) 不能监听";
case LBFECSYSForward:return @"禁止监听forwardInvocation:";
case LBFECNoError: return @"no error";
default:return @"其他错误";
}
}
+ (instancetype)lbOEWithDes:(NSString*)des{
LBForwardError* error = [self lbEWithCode:LBFECOther];
if (des.length)
error->_eDes = [des copy];
return error;
}
@end
这个类主要的作用是注册监听方法的时候, 出错的描述
转发的分类头文件
#import "LBForwardType.h"
#define TimeBegin_ 3000
/** LBForwardOperationTime */
typedef enum: NSInteger{
/** 在调用方法之前 回调 */
LBFOT(Before) = TimeBegin_,
/** 替换之前的方法 */
LBFOT(Replace),
/** 调用完之前的方法后 再回调 */
LBFOT(After)
}LBFOT;
#undef TimeBegin_
@class LBForwardError;
typedef void(^LBFEBlock)(LBForwardError* _Nonnull error);
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (LBForward)
/**
对self的方法进行监听, 在指定的time做自己的操作
@param opBlock 你的操作 block, 签名格式要对应sel block的第一个参数 == self, 后面开始是 sel的参数列表, block里没有传sel出来
@param oSel 原方法的sel
@param time 操作的时间点
@param error 错误说明
PS: 如果error为空 可能会抛异常, 异常里的 userInfo[@"error"] == LBForwardError
*/
- (void)lbf_hookOperationBlock:(id)opBlock
originalSel:(SEL)oSel
executeTime:(LBFOT)time
catch:(LBFEBlock _Nullable)error;
- (void)lbf_resign;
- (void)lbf_resignSEL:(SEL)oSel;
- (void)lbf_resignSEL:(SEL)oSel
executeTime:(LBFOT)time;
@end
NS_ASSUME_NONNULL_END
转发的分类的实现
#import "NSObject+LBForward.h"
#import <objc/message.h>
#import "LBForwardError.h"
#pragma mark - macors
#define LB_FORWARD_SET_OP_B_KEY @"before"
#define LB_FORWARD_SET_OP_R_KEY @"replace"
#define LB_FORWARD_SET_OP_A_KEY @"after"
#define LB_FORWARD_SET_SELF_CAPACITY 5
#define LB_FORWARD_SET_SEL_CAPACITY 3
#define LB_FORWARD_SET_OP_CAPACITY 2
#define LB_HOOK_LOCK_KEY @"registerLock"
#define SYS_SELF_DEALLOC @"dealloc"
#define SYS_SELF_ARC_RT @"retain"
#define SYS_SELF_ARC_RC @"retainCount"
#define SYS_SELF_ARC_RL @"release"
#define SYS_SELF_ARC_AR @"autorelease"
#define SYS_SELF_FORWARD @"forwardInvocation:"
#define SYS_SELF_PREFIX_INIT @"init"
#define LB_HOOK_CLASS_PREFIX @"LBF_CLASS_"
#define LB_HOOK_SEL_PREFIX @"LBF_SEL_"
#define LB_ERROR_TITLE @"出错, 请看异常里的userinfo[@\"error\"] LBForwardError相关的报错"
#define LB_Throw(_info) @throw [NSException exceptionWithName:LB_ERROR_TITLE reason:@"" userInfo:@{@"error":_info}]
#define LB_FORWARD_PARAM_NAME_OP opBlock
#define LB_FORWARD_PARAM_NAME_SEL oSel
#define LB_FORWARD_PARAM_NAME_TIME workTime
#define LB_FORWARD_PARAM_NAME_E registerError
#define LB_FORWARD_PARAM_NAME_E_TYPE (void(^)(LBForwardError*))LB_FORWARD_PARAM_NAME_E
#pragma mark - block的结构
typedef struct LBBlockLayout LBBlockLayout;
struct LBBlockLayout{
void *isa; ///block指向的类, 这里oc是为了统一, 因为当一个参数是id的时候, 可以传任意的block,而且还可以通过 NSInvocation 指定target, 设置好签名参数调用block的实现
int flags; ///标记
int reserved;
void (*invoke)(void *, ...);
struct block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src); // (1<<25)
void (*dispose)(void *src);
const char *signature; // (1<<30)
} *descriptor;
};
typedef enum:int{
LBBlockFlagsCopyDispose = (1 << 25),
LBBlockFlagsIsGlobal = (1 << 28),
LBBlockFlagsHasSignature = (1 << 30)
} LBBlockFlag;
typedef NSNumber* LBForwardIMPAdd;
#pragma mark - 全局的Map
#define LB_FORWARD_SET_CAPACITY 20
static NSMutableDictionary* LB_FORWARD_SET(){
static NSMutableDictionary* dic_;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dic_ = [NSMutableDictionary dictionaryWithCapacity:LB_FORWARD_SET_CAPACITY];
});
return dic_;
}
#define LB_FORWARD_SET LB_FORWARD_SET()
#pragma mark - self相关信息的key
static inline NSString* LB_FORWARD_SET_SELF_KEY(id self){
if(!object_isClass(self))
return [NSString stringWithFormat:@"%ld",[self hash]];
return [NSString stringWithFormat:@"%p",self];
}
#define _LB_FORWARD_SET_SELF_KEY LB_FORWARD_SET_SELF_KEY(self)
#define _LB_FORWARD_SET_SELF LB_FORWARD_SET[_LB_FORWARD_SET_SELF_KEY]
#pragma mark - sel 相关信息的key
static inline NSString* LB_FORWARD_SET_SEL_KEY(SEL LB_FORWARD_PARAM_NAME_SEL){
return NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL);
}
#define _LB_FORWARD_SET_SEL_KEY LB_FORWARD_SET_SEL_KEY(LB_FORWARD_PARAM_NAME_SEL)
#define _LB_FORWARD_SET_SEL _LB_FORWARD_SET_SELF[_LB_FORWARD_SET_SEL_KEY]
#define _LB_FORWARD_SET_OP_B _LB_FORWARD_SET_SEL[LB_FORWARD_SET_OP_B_KEY]
#define _LB_FORWARD_SET_OP_R _LB_FORWARD_SET_SEL[LB_FORWARD_SET_OP_R_KEY]
#define _LB_FORWARD_SET_OP_A _LB_FORWARD_SET_SEL[LB_FORWARD_SET_OP_A_KEY]
#pragma mark - inline functions
#pragma mark - 快速根据code创建错误信息
static inline LBForwardError* _LB_FORWARD_ERROR(LBFEC code){
return [LBForwardError lbEWithCode:code];
}
#pragma mark - 快速根据des创建 其他错误信息的error
static inline LBForwardError* _LB_FORWARD_OTHER_E(NSString* des){
return [LBForwardError lbOEWithDes:des];
}
#pragma mark - 检查当前的对象是否是动态创建的class
static inline bool _LB_FORWARD_CLASS_IS_DYNAMIC(id self){
return [NSStringFromClass(object_getClass(self)) hasPrefix:LB_HOOK_CLASS_PREFIX];
}
#define _LB_FORWARD_CLASS_IS_DYNAMIC_VALUE _LB_FORWARD_CLASS_IS_DYNAMIC(self)
#pragma mark - 快速获取sel的签名对象(对象方法)
static inline NSMethodSignature* _LB_FORWARD_SEL_ORIGINAL_SIGNA(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
if (object_isClass(self))
return [self instanceMethodSignatureForSelector:LB_FORWARD_PARAM_NAME_SEL];
return [self methodSignatureForSelector:LB_FORWARD_PARAM_NAME_SEL];
}
#define _LB_FORWARD_SEL_ORIGINAL_SIGNA_VALUE _LB_FORWARD_SEL_ORIGINAL_METHOD(self,LB_FORWARD_PARAM_NAME_SEL)
#pragma mark - 快速获取sel的Method(对象方法)
static inline Method _LB_FORWARD_SEL_ORIGINAL_METHOD(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
return class_getInstanceMethod(object_getClass(self), LB_FORWARD_PARAM_NAME_SEL);
}
#define _LB_FORWARD_SEL_ORIGINAL_METHOD_VALUE _LB_FORWARD_SEL_ORIGINAL_METHOD(self,LB_FORWARD_PARAM_NAME_SEL)
#pragma mark - 快速获取sel的签名(c字符串)
static inline const char* _LB_FORWARD_SEL_ORIGINAL_ENCODE(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
return method_getTypeEncoding(_LB_FORWARD_SEL_ORIGINAL_METHOD_VALUE);
}
#define _LB_FORWARD_SEL_ORIGINAL_ENCODE_VALUE _LB_FORWARD_SEL_ORIGINAL_ENCODE(self,LB_FORWARD_PARAM_NAME_SEL)
#pragma mark - 判断是不是 initXXX的方法
static inline bool _LB_FORWARD_SEL_IS_INITIALIZE(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
NSString* selString = NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL);
if (selString.length)
if ([selString hasPrefix:SYS_SELF_PREFIX_INIT])
return true;
return false;
}
#pragma mark - 快速获取当前对象 要动态被创建的类名
static inline NSString* _LB_FORWARD_DYNAMIC_CLASS_NAME(id self){
return [LB_HOOK_CLASS_PREFIX stringByAppendingString:NSStringFromClass(object_getClass(self))];
}
#define _LB_FORWARD_DYNAMIC_CLASS_NAME_VALUE _LB_FORWARD_DYNAMIC_CLASS_NAME(self)
#pragma mark - 快速获取当前动态创建的子类 被添加的新的sel的字符串
static inline NSString* _LB_FORWARD_DYNAMIC_SEL_NAME(SEL LB_FORWARD_PARAM_NAME_SEL){
return [LB_HOOK_SEL_PREFIX stringByAppendingString:NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL)];
}
#define _LB_FORWARD_DYNAMIC_SEL_NAME_VALUE _LB_FORWARD_DYNAMIC_SEL_NAME(LB_FORWARD_PARAM_NAME_SEL)
#pragma mark - 根据操作的枚举值返回对应的key
static inline NSString* _LB_FORWARD_SEL_OP_TIME(LBFOT LB_FORWARD_PARAM_NAME_TIME){
switch (LB_FORWARD_PARAM_NAME_TIME) {
case LBFOTBefore:return LB_FORWARD_SET_OP_B_KEY;
case LBFOTReplace:return LB_FORWARD_SET_OP_R_KEY;
case LBFOTAfter:return LB_FORWARD_SET_OP_A_KEY;
default:return nil;
}
}
#define _LB_FORWARD_SEL_OP_TIME_VALUE _LB_FORWARD_SEL_OP_TIME(LB_FORWARD_PARAM_NAME_TIME)
#pragma mark - 获取sel的imp 这时候self已经指向动态的子类了
static inline IMP _LB_FORWARD_SEL_ORIGINAL_IMP(id self, SEL LB_FORWARD_PARAM_NAME_SEL){
IMP oImp = class_getMethodImplementation(object_getClass(self), LB_FORWARD_PARAM_NAME_SEL);
oImp = !oImp ? nil:class_getMethodImplementation(object_getClass(self), LB_FORWARD_PARAM_NAME_SEL);
return oImp;
}
#define _LB_FORWARD_SEL_ORIGINAL_IMP_VALUE _LB_FORWARD_SEL_ORIGINAL_IMP(self,LB_FORWARD_PARAM_NAME_SEL)
#pragma mark - 辅助函数
#pragma mark - 判断block是否是合法的签名
static bool _LB_FORWARD_OP_AVAILABLE_SIGNATURE(id self, SEL LB_FORWARD_PARAM_NAME_SEL, id LB_FORWARD_PARAM_NAME_OP);
static void LB_FORWARD_ENTRANCE(id self, SEL invoSel, NSInvocation* sysInVo);
#pragma mark - 类实现
@implementation NSObject (LBForward)
- (void)lbf_hookOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME
catch:LB_FORWARD_PARAM_NAME_E_TYPE{
@synchronized (LB_HOOK_LOCK_KEY) {
if (LB_FORWARD_PARAM_NAME_E_TYPE)
return [self _lbf_hookOperationBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME
catch:LB_FORWARD_PARAM_NAME_E];
[self _lbf_hookOperationBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
}
}
#pragma mark - 主方法里registerError 不为空的时候的流程
- (void)_lbf_hookOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(int)LB_FORWARD_PARAM_NAME_TIME
catch:LB_FORWARD_PARAM_NAME_E_TYPE{
[self checkParmsWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME
catch:LB_FORWARD_PARAM_NAME_E];
////注册相关信息
[self registerInfoWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
}
#pragma mark - 主方法里registerError为空的时候的流程
- (void)_lbf_hookOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
[self checkParmsWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME
catch:nil];
////注册相关信息
[self registerInfoWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
}
#pragma mark - 参数相关检查
- (void)checkParmsWithOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME
catch:LB_FORWARD_PARAM_NAME_E_TYPE{
///空的操作
if (!LB_FORWARD_PARAM_NAME_OP){
!LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(_LB_FORWARD_ERROR(LBFECEmptyOperation));
if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(_LB_FORWARD_ERROR(LBFECEmptyOperation));
}
///空的sel
if (!LB_FORWARD_PARAM_NAME_SEL){
!LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(_LB_FORWARD_ERROR(LBFECEmptySEL));
if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(_LB_FORWARD_ERROR(LBFECEmptySEL));
}
///没有具体的实现
if(!_LB_FORWARD_SEL_ORIGINAL_SIGNA(self, LB_FORWARD_PARAM_NAME_SEL)){
NSString* errorStr = [NSString stringWithFormat:@"对象:%p(%@) 的%@ 没有对应的实现",
self,object_getClass(self),
NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL)];
!LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(_LB_FORWARD_OTHER_E(errorStr));
if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(_LB_FORWARD_OTHER_E(errorStr));
}
///不能监听的方法
LBForwardError* fobidMethod = [self fileterSEL:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
if (fobidMethod){
!LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(fobidMethod);
if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(fobidMethod);
}
///签名不符合
if(!_LB_FORWARD_OP_AVAILABLE_SIGNATURE(self, LB_FORWARD_PARAM_NAME_SEL, LB_FORWARD_PARAM_NAME_OP)){
NSString* errorStr = [NSString stringWithFormat:@"对象:%p(%@) 的%@ 签名不符合, 请阅读 \"lbf_hookOperationBlock:originalSel:executeTime:catch:\"说明",
self,object_getClass(self),
NSStringFromSelector(LB_FORWARD_PARAM_NAME_SEL)];
!LB_FORWARD_PARAM_NAME_E ? : LB_FORWARD_PARAM_NAME_E(_LB_FORWARD_OTHER_E(errorStr));
if(!LB_FORWARD_PARAM_NAME_E)LB_Throw(_LB_FORWARD_OTHER_E(errorStr));
}
}
#pragma mark - 注册相关信息
- (void)registerInfoWithOperationBlock:(id)LB_FORWARD_PARAM_NAME_OP
originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
Class reallyClass = [self isAvaiableClass];
///创建子类
if (!reallyClass)
[self createSubClassWithSEL:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
///缓存相关的信息
[self cacheOPWithBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
}
#pragma mark - 当前类是否创建过子类
- (Class)isAvaiableClass{
if (_LB_FORWARD_CLASS_IS_DYNAMIC(self)) return object_getClass(self);
NSString* dynamicClassName = _LB_FORWARD_DYNAMIC_CLASS_NAME_VALUE;
return objc_getClass(dynamicClassName.UTF8String);
}
#pragma mark - 获取self_info(内部会设置isa)
- (NSMutableDictionary*)cacheSelfSet{
NSString* key = _LB_FORWARD_SET_SELF_KEY;
NSMutableDictionary* result = [LB_FORWARD_SET objectForKey:key];
if (!result) {
result = [NSMutableDictionary dictionaryWithCapacity:LB_FORWARD_SET_SELF_CAPACITY];
[LB_FORWARD_SET setObject:result forKey:key];
}
///这个时候isAvaiableClass一定有值
if (!_LB_FORWARD_CLASS_IS_DYNAMIC_VALUE)
object_setClass(self, [self isAvaiableClass]);
return result;
}
#pragma mark - 获取sel_info
- (NSMutableDictionary*)cacheSelSetWith:(SEL)LB_FORWARD_PARAM_NAME_SEL
superSet:(NSMutableDictionary*)superSet{
NSString* key = _LB_FORWARD_SET_SEL_KEY;
NSMutableDictionary* result = [superSet objectForKey:key];
if (!result) {
result = [NSMutableDictionary dictionaryWithCapacity:LB_FORWARD_SET_SEL_CAPACITY];
[superSet setObject:result forKey:key];
}
return result;
}
#pragma mark - 缓存相关信息
- (void)cacheOPWithBlock:(id)LB_FORWARD_PARAM_NAME_OP
originalSel:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
///sel_info
NSMutableDictionary* superSet = [self cacheSelSetWith:LB_FORWARD_PARAM_NAME_SEL
superSet:[self cacheSelfSet]];
///添加方法 交换方法
[self tryAddMethodWithSEL:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
NSString* key = _LB_FORWARD_SEL_OP_TIME_VALUE;
if (key) {
NSMutableArray* result = [superSet objectForKey:key];
if (!result) {
result = [NSMutableArray arrayWithCapacity:LB_FORWARD_SET_OP_CAPACITY];
[superSet setObject:result forKey:key];
}
[result addObject:LB_FORWARD_PARAM_NAME_OP];
}
}
#pragma mark - 过滤方法
- (LBForwardError*)fileterSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
///dealloc
if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_DEALLOC)))
if(LB_FORWARD_PARAM_NAME_TIME != LBFOTBefore)
return _LB_FORWARD_ERROR(LBFECDellocTime);
///arc
if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_ARC_RT)))
return _LB_FORWARD_ERROR(LBFECARCForbid);
if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_ARC_RL)))
return _LB_FORWARD_ERROR(LBFECARCForbid);
if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_ARC_RC)))
return _LB_FORWARD_ERROR(LBFECARCForbid);
if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_ARC_AR)))
return _LB_FORWARD_ERROR(LBFECARCForbid);
if(sel_isEqual(LB_FORWARD_PARAM_NAME_SEL, NSSelectorFromString(SYS_SELF_FORWARD)))
return _LB_FORWARD_ERROR(LBFECARCForbid);
///initXXX
if(_LB_FORWARD_SEL_IS_INITIALIZE(self, LB_FORWARD_PARAM_NAME_SEL))
return _LB_FORWARD_ERROR(LBFECInitForbid);
return nil;
}
#pragma mark - 动态创建类
- (Class)createSubClassWithSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
NSString* dynamicClassName = _LB_FORWARD_DYNAMIC_CLASS_NAME_VALUE;
Class dynamicClass = objc_allocateClassPair(object_getClass(self), dynamicClassName.UTF8String, 0);
if (dynamicClass) {
///将self 的 isa 指向 新建的子类
// object_setClass(self, dynamicClass);
///添加一个 class方法 返回父类
#warning class方法
objc_registerClassPair(dynamicClass);
}
return dynamicClass;
}
#pragma mark - 方法注册相关
- (void)tryAddMethodWithSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
Class reallyClass = object_getClass(self);
SEL subSel = NSSelectorFromString(_LB_FORWARD_DYNAMIC_SEL_NAME_VALUE);
///第二次虽然获取到的是 objc_msgforward的IMP, 但是class_addMethod会添加失败, 因为第1次的时候已经正确填充信息了, 所以这里不要纠结
IMP selOImp = _LB_FORWARD_SEL_ORIGINAL_IMP_VALUE;
///这里的签名 就是 原始的sel的签名
const char* selOEncode = _LB_FORWARD_SEL_ORIGINAL_ENCODE_VALUE;
if (class_addMethod(reallyClass,
subSel,
selOImp,
selOEncode)) {
///将原来的方法 强制指向系统调用函数, 这样外界调用的时候就会进入转发
class_replaceMethod(reallyClass,
LB_FORWARD_PARAM_NAME_SEL,
_objc_msgForward,
selOEncode
);
///将当前class的 转发函数替换到 自定义的, 方便处理
class_replaceMethod(reallyClass,
@selector(forwardInvocation:),
(IMP)LB_FORWARD_ENTRANCE,
"v@:");
}
}
#pragma mark - 注销
- (void)lbf_resign{
[LB_FORWARD_SET removeObjectForKey:_LB_FORWARD_SET_SELF_KEY];
}
- (void)lbf_resignSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL{
if (!LB_FORWARD_PARAM_NAME_SEL) return[self lbf_resign];
NSMutableDictionary* self_info = _LB_FORWARD_SET_SELF;
[self_info removeObjectForKey:_LB_FORWARD_SET_SEL_KEY];
if(self_info.allKeys.count == 0) [self lbf_resign];
}
- (void)lbf_resignSEL:(SEL)LB_FORWARD_PARAM_NAME_SEL
executeTime:(LBFOT)LB_FORWARD_PARAM_NAME_TIME{
if (!LB_FORWARD_PARAM_NAME_SEL) return[self lbf_resignSEL:LB_FORWARD_PARAM_NAME_SEL];
NSMutableDictionary* sel_info = _LB_FORWARD_SET_SEL;
[sel_info removeObjectForKey:_LB_FORWARD_SEL_OP_TIME_VALUE];
if (!sel_info.allKeys.count) [self lbf_resignSEL:LB_FORWARD_PARAM_NAME_SEL];
}
@end
static void LB_FORWARD_EXCUTE_OI(id self, NSInvocation* sysInvo, SEL LB_FORWARD_PARAM_NAME_SEL){
sysInvo.selector = NSSelectorFromString(_LB_FORWARD_DYNAMIC_SEL_NAME_VALUE);
sysInvo.target = self;
[sysInvo invoke];
}
#define _LB_FORWARD_EXCUTE_OI() LB_FORWARD_EXCUTE_OI(self,sysInvo,LB_FORWARD_PARAM_NAME_SEL)
static void LB_FORWARD_EXCUTE_OP(NSArray* ops, NSInvocation* sysInvo){
void* arg = NULL;
[sysInvo getArgument:&arg atIndex:0];
[sysInvo setArgument:&arg atIndex:1];
for (int i = 0; i < ops.count; ++i) {
sysInvo.target = ops[i];
[sysInvo invoke];
}
}
#pragma mark - 全局转发函数
__unused void LB_FORWARD_ENTRANCE(id self, SEL invoSel, NSInvocation* sysInvo){
SEL oSel = sysInvo.selector;
///befores block
LB_FORWARD_EXCUTE_OP(_LB_FORWARD_SET_OP_B,sysInvo);
///这里的问题是 只要有一个替代的操作, 原方法就执行不了, Aspeacts 也是同样的情况
if ([_LB_FORWARD_SET_OP_R count])
LB_FORWARD_EXCUTE_OP(_LB_FORWARD_SET_OP_R, sysInvo);
else{
_LB_FORWARD_EXCUTE_OI();
}
///after
LB_FORWARD_EXCUTE_OP(_LB_FORWARD_SET_OP_A,sysInvo);
}
bool _LB_FORWARD_OP_AVAILABLE_SIGNATURE(id self, SEL LB_FORWARD_PARAM_NAME_SEL, id LB_FORWARD_PARAM_NAME_OP){
LBBlockLayout* tmpBlockLayout = (__bridge LBBlockLayout *)(LB_FORWARD_PARAM_NAME_OP);
int flags = tmpBlockLayout -> flags;
if (flags & LBBlockFlagsHasSignature) {
////这2步都是指针的加减
void* signatureAddress = (void*)(tmpBlockLayout -> descriptor) + 2 * sizeof(unsigned long);
if (flags & LBBlockFlagsCopyDispose)
signatureAddress += 2 * sizeof(void*);
const char* signalStr = *((char**)signatureAddress);
if (signalStr) {
NSMethodSignature* blockS = [NSMethodSignature signatureWithObjCTypes:signalStr];
NSMethodSignature* selS = _LB_FORWARD_SEL_ORIGINAL_SIGNA(self, LB_FORWARD_PARAM_NAME_SEL);
if (!selS) return NO;
NSInteger tmpBArgCount = blockS.numberOfArguments;
if (tmpBArgCount > selS.numberOfArguments)
return NO;
if (tmpBArgCount > 1)
if ([blockS getArgumentTypeAtIndex:1][0] != '@') return NO;
for (NSUInteger i = 2; i < tmpBArgCount; ++i) {
const char *methodType = [selS getArgumentTypeAtIndex:i];
const char *blockType = [blockS getArgumentTypeAtIndex:i];
if (!methodType || !blockType ) return NO;
if (methodType[0] != blockType[0]) return NO;
}
return true;
}
}
return false;
}
流程
1. 创建car1对象
2. 调用lbf_hookOperationBlock: originalSel: executeTime: catch:
2.1 catch一定要传, 不然异常
2.2 检查 各个参数的是否空, 检查sel是否实现和过滤相关的方法,检查block的签名是否和sel的一致
2.3 注册相关信息
2.3.1 判断是否动态创建过子类, 没创建就创建子类
2.3.2 先从缓存里取相关的数据(block
), 没取到, 就创建, 创建的过程里把 当前的self
的isa
指向了新的类LBF_CLASS_originaclass
,然后动态添加了一个新的SEL
=== LBF_SEL_originalSel
, 并且指向了原方法的IMP
,然后将原方法oSel
指向了objc_msgForward
, 然后将转发函数forwardInvocation:
指向了全局处理的函数LB_FORWARD_ENTRANCE
, 当调用注册的方法的时候, 会来到全局处理的函数里, 在这里面根据记录进行筛选
2.4 可以调用resign
相关的函数注掉监听
PS:这个简单的demo,有很多问题, 只是简单的测试了下, 里面记录用的是全局的map, 所以要自己处理注销的问题,如果是用
runtime
里的关联,那么self 被dealloc的时候会自动释放掉之前关联的内存(Aspeacts), 里面没有实现类方法的监听, 但是写这个demo, 基本runtime的 知识点都用到了, 包括block的签名获取, 怎么通过方法调用block等等