前言
這篇文章緊接上一篇, runtime理論我想大家都知道的, 上一篇是站在個人理解的角度, 探討了相關(guān)的一些動態(tài)原理,和基本實(shí)現(xiàn)思路,最后拓展到runtime框架, 而且也簡單寫了動態(tài)添加屬性和KVO簡單實(shí)現(xiàn)的demo,如果有興趣可以去看深入理解OC的運(yùn)行時(Runtime)
編碼相關(guān)
具體可通過編譯指令 @encode(type) typeof()
@encode(type)必須傳入類型 不能是具體的變量
typeof()傳入的可以是變量, 可以是類型, 通過typeof獲取到類型然后傳給@encode,最后會返回對應(yīng)的const char*, 這個就是類型的編碼, 同樣可以獲取到block和函數(shù)的(相當(dāng)于沒有獲取到), 這里舉出一些例子:
** 先定義一個Log輸出的宏(來自RAC宏的運(yùn)用改編,有時間會專門寫一篇宏相關(guān)的東西) ,這里設(shè)置的最多10個參數(shù), 如果不夠,可以自己看著往后加 **
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
使用
////打印整型浮點(diǎn),基本指針
int a = 100;
float b = 1.23432e3;
char ch = 'A';
Log(a,b, 'a', ch, 919123L);
//// 打印結(jié)果
100
1234.319946
97
A
919123
////打印指針
float* fp = &b;
Log(fp, *fp, &b);
///打印結(jié)果
0x7ffee56ce8e8
1234.319946
0x7ffee56ce8e8
///打印c字符串
char *s = "ssssss";
char array[] = "array";
Log("aaa","bbb","ccc", s, array, 123, 890);
///打印結(jié)果:
aaa
bbb
ccc
ssssss
array
123
890
///打印oc對象 如果直接在宏的參數(shù)里填寫字典,數(shù)組, 加上括號,整體括起來
NSArray* tmp = @[@"xxxx",@"bbbb",@{@"array":@123}];
Log(@"123",
(@{@"1233":@123}),
tmp,
"這個是c的字符串",
324.12331f
);
///打印結(jié)果:
{
1233 = 123;
}
(
xxxx,
bbbb,
{
array = 123;
}
)
這個是c的字符串
324.123322
各種數(shù)字(整型,浮點(diǎn))編碼
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
各種指針的編碼 就是在對應(yīng)的類型前面加上^
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語言里數(shù)組的編碼
short[]: ^s
int[10]: [10i]
long[5]: [5q]
long long[8]: [9q]
NSInteger[7]: [7q]
unsigned short[]: ^S
....///規(guī)律自己尋找
const相關(guān)的編碼
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]
函數(shù)指針和block相關(guān) ?代表著匿名
////局部的函數(shù)指針和block
void (*funtest)(void) = NULL; ^?
void (^blocktest)(void) = NULL; @?
int (*funtest1)(int) = NULL; ^?
int (^blocktest1)(int) = NULL;@?
結(jié)構(gòu)體,聯(lián)合體相關(guān)
///系統(tǒng)的
@encode(typeof(CGRect)) {CGRect={CGPoint=dd}{CGSize=dd}}
///自己定義的
struct tmp{
int a;
struct{
int b;
NSString* c;
};
};
///編碼格式結(jié)果
{tmp=i{?=i@}}
///聯(lián)合體
union{
int a;
int b;
}d;
////d的編碼是 ?代表匿名 d這里是一個變量
(?=ii)
union tmpU{
int a;
int b;
}d;
////此時d的編碼是
(tmpU=ii)
oc相關(guān)的編碼
@encode(typeof(instance)) @
@encode(typeof(NSObject)) {NSObject=#}
@encode(typeof(UIViewController)) {UIViewController=#}
@interface Person :NSObject{
int* age;
NSString* name;
}
@end
@encode(typeof(Person)) {UIViewController=#}
////類里方法的編碼通過runtime函數(shù)獲取到method, 然后再獲取編碼
方法簽名
?1. 在NSObject里有一個簽名的類NSMethodSignature
?2. 獲取NSMethodSignature的途徑有
??2.1 通過NSObject的接口
例子如下:
@implementation VC :UIViewController
+ (void)classMT{}
- (void)objcMT{}
@end
/**
-[NSObject methodSignatureForSelector:SEL]這個方法在頭文件里聲明的是對象方法, 但是以類方法的調(diào)用形式去調(diào)用也可以, 不知道為什么
*/
///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
總結(jié): 1.如果是獲取對象方法的簽名, 就用對象去調(diào)用methodSignatureForSelector或者用類去調(diào)用instanceMethodSignatureForSelector
???2.如果是獲取類方法的簽名, 就用類去調(diào)用methodSignatureForSelector
?
獲取簽名對象后, 函數(shù)簽名代碼如下:
- (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
////對應(yīng) IMP的調(diào)用參數(shù)表
//// 第0個 id @
//// 第1個SEL 對應(yīng) :
//// 第2個開始是參數(shù) 上面的是 int 所以是 i
//// 第3個 對應(yīng)上面的 NSString* @
//// 返回值要單獨(dú)獲取, 這里是void 所以是V
??2.2 通過runtime的接口
還是上面的那個類定義
Method m = class_getInstanceMethod(ViewController.class, @selector(signatest3:arg2:));
NSLog(@"%s",method_getTypeEncoding(m));
/// i28@0:8i16@20 這些數(shù)字代表什么含義,沒懂, 但是去掉數(shù)字就和上面對應(yīng)了,而且最開始的是從返回算的
?3. 自己簽名
創(chuàng)建簽名對象
NSMethodSignature的時候,給的簽名類型,注意創(chuàng)建簽名的時候, 要把返回值也帶上
NSMethodSignature* method = [NSMethodSignature signatureWithObjCTypes:"i@:i@"];
for (NSInteger i = 0; i < method.numberOfArguments; ++i) {
NSLog(@"%s",[method getArgumentTypeAtIndex:i]);
}
NSLog(@"%s",method.methodReturnType);
/// @:i@ 對應(yīng)的方法 - (int)name:(int)a arg:(id)obj;
/// 第一是返回值 所以創(chuàng)建的時候要指定返回值i
消息轉(zhuǎn)發(fā)(利用系統(tǒng)提供的接口轉(zhuǎn)發(fā))
?在oc里如果對對象發(fā)送消息時, 最后沒有找到方法的實(shí)現(xiàn), 系統(tǒng)會自動進(jìn)入消息轉(zhuǎn)發(fā)流程, 而且在oc的層面提供了接口,在這些接口里我們可以轉(zhuǎn)發(fā)消息, 這里拿對象方法來說
? 1. 進(jìn)入+(bool)resolveInstanceMethod:(SEL)
這里系統(tǒng)會將之前傳入的SEL傳遞過來,我們知道, SEL是oc接收消息后去尋找方法匹配的標(biāo)識,和IMP是分開的, 這里把SEL方法的標(biāo)識傳過來, 表示當(dāng)前類沒有與SEL對應(yīng)的實(shí)現(xiàn)IMP, 所以這里就為這個SEL指定一個實(shí)現(xiàn)地址, 然后返回YES告訴系統(tǒng)處理了, 系統(tǒng)就不會往下走了
@implementation VC : UIViewController
- (void)forwardTestFun1{}
- (void)viewDidLoad{
[super viewDidLoad];
////這里瞎調(diào)用了一個方法, 寫成這樣是為了消除警告
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("已經(jīng)被改變指向了,來到了這里執(zhí)行");
}), "v@:");
return 1;
}
return [super resolveInstanceMethod:sel];
}
@end
這一步主要的用途是在本類中動態(tài)為sel添加一個Method, 如果這里返回false, 或什么都不管, 就會去下一步. 這里要說明一點(diǎn), 就算將當(dāng)前沒有實(shí)現(xiàn)的
noMethodTest的sel 改為forwardTestFun1的sel傳給父類,還是會崩潰
? 2. 消息轉(zhuǎn)發(fā)的第2步:- (id)forwardingTargetForSelector:(SEL)aSelector
這一步要求給系統(tǒng)傳一個對象, 要求這個對象有sel的實(shí)現(xiàn), 比如傳一個Person對象, 他實(shí)現(xiàn)了這sel
?3. 如果2不處理, 即返回了nil,或者沒有實(shí)現(xiàn)會進(jìn)入到這里- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector, 這個方法要求對sel提供一個簽名對象 eg:
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{
return [NSMethodSignature signatureWithObjCTypes:"V@:"];
}
這里的簽名一定要和傳入的sel對應(yīng), 這里表示
- (void)funName{ }, v==void, @ == id : == sel 因?yàn)?objc_msgsend()的參數(shù)是 id, sel, 所以這里要提供@和:
?4. 如果3不實(shí)現(xiàn)或者,返回nil,崩潰, 如果返回了簽名則會執(zhí)行: - (void)forwardInvocation:(NSInvocation *)anInvocation, 這個方法會傳遞一個NSInvocation對象, 簽名已經(jīng)設(shè)置好了, 就是上面的一步我們設(shè)置的簽名, 這個方法要求提供一個對象供anInvocation調(diào)用, 而且要求對象必須實(shí)現(xiàn)了 同名的sel的實(shí)現(xiàn), 因?yàn)檫@里的anInvocation的selector系統(tǒng)賦值的是傳過來的sel,所以一定會調(diào)用新對象同名的方法,如果想指定新對象別的方法,這里需要更改anInvocation的selector屬性值,但是對應(yīng)實(shí)現(xiàn)的方法簽名必須和當(dāng)前參數(shù)sel的一致。代碼如下
@implementation Son :NSObject
- (void)test{
Log("消息會不會被轉(zhuǎn)發(fā)來呢?");
}
- (void)noMethodTest{
Log("消息一定會轉(zhuǎn)發(fā)到這里");
}
@end
VC中:
- (void)forwardInvocation:(NSInvocation *)anInvocation{
///如果上一步提供的簽名不對, 也是崩潰的
[anInvocation invokeWithTarget:Son.new];
///這里可以獲取到參數(shù)信息
NSMethodSignature* si = anInvocation.methodSignature;
for (NSInteger i = 2; i < si.numberOfArguments; ++i) {
@autoreleasepool {
__autoreleasing NSObject* s;
[anInvocation getArgument:&s atIndex:i];
NSLog(@"%@",s);
}
}
}
///如果不實(shí)現(xiàn)noMethodTest, 崩潰, 方法找不到,如果實(shí)現(xiàn)了會調(diào)用noMethodTest
手動觸發(fā)消息轉(zhuǎn)發(fā)(方法監(jiān)聽AOP編程)
? 1. 手動觸發(fā)消息轉(zhuǎn)發(fā)還是和原理一樣的, 要實(shí)現(xiàn)即使 當(dāng)前調(diào)用的sel有對應(yīng)的IMP, 也要觸發(fā)轉(zhuǎn)發(fā)流程. 這里要說一點(diǎn)就是, 處理的操作肯定是在 轉(zhuǎn)發(fā)流程的其中一步, 因?yàn)榧热皇寝D(zhuǎn)發(fā), 一定會走到轉(zhuǎn)發(fā)流程的函數(shù),所以我們處理的地方就是在那幾步當(dāng)中(針對當(dāng)前類來說,后面拓展到通用的時候?qū)崿F(xiàn)方案又不一樣的)
場景1 假設(shè)現(xiàn)在的需求是 項目里所有Person對象用到的地方, 只要調(diào)用了- [Person runFrom: to:] 都必須在調(diào)用前打印 hello world
解決方案選擇
方案1
- 找到工程里所有的
Person實(shí)例化的對象, 然后查找哪里調(diào)用了- [Person runFrom: to:], 在每一個調(diào)用前都加上輸出語句
方案2
-
修改Person中對應(yīng)方法的實(shí)現(xiàn), 在- [Person runFrom: to:]的實(shí)現(xiàn)里, 最開始加上輸出語句
方案3
-
修改Person類的實(shí)現(xiàn), 在Person.m文件里加入私有的類比如_PersonHelloWord繼承自Person,修改Person的初始化操作返回_PersonHelloWord, 然后_PersonHelloWord的實(shí)現(xiàn)里重寫- [Person runFrom: to:], 加入輸出語句, 再調(diào)用父類的方法
方案4
-
不修改Person類, 也不創(chuàng)建子類, 利用runtime的方案, 這里可以為Person添加一個分類, 在分類里寫一個新的方法, 然后利用方法交換的技術(shù)實(shí)現(xiàn)需求, 當(dāng)然這不算是消息轉(zhuǎn)發(fā),注意即使他媽的輸出語句是不需要參數(shù)的,但是交互的方法也必須提供
方案5
-
修改Person, 用消息轉(zhuǎn)發(fā)的技術(shù)去處理, 具體的介紹在方案4的代碼之后
方案6
-
KVO, 自己實(shí)現(xiàn)KVO的過程, 上一篇深入理解OC的運(yùn)行時(Runtime)里詳細(xì)的代碼.
?
1,2種我就不說了, 至于第3種,我想沒有人會這樣搞, 這種方案可能會用在別的情況下, 總之既然是方案, 那就沒有好壞之分, 只有適用和不適用之說, 第4種我下面簡單給出偽代碼, 第5種要重點(diǎn)探討的, 第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("產(chǎn)品有病啊, 輸出一句hello world, 日!");
[self helloworld:cao to:nima];
}
@end
方案5 的實(shí)現(xiàn)方案介紹
0
1 要實(shí)現(xiàn)消息轉(zhuǎn)發(fā), 在正常sel存在IMP的情況下, 如果不做處理, 是不會進(jìn)入轉(zhuǎn)發(fā)流程的, 所以要做的事情是手動代碼控制, 進(jìn)入轉(zhuǎn)發(fā)流程
手動進(jìn)入轉(zhuǎn)發(fā)流程有多種方案, 但原理都是修改sel的IMP(這個runtime提供了接口)
1.1 讓當(dāng)前的sel指向一個 和sel原始IMP簽名不一樣的函數(shù)或block, 因?yàn)楹灻粚? 找不到實(shí)現(xiàn)進(jìn)入轉(zhuǎn)發(fā)
1.2 直接讓sel指向 系統(tǒng)轉(zhuǎn)發(fā)函數(shù) _objc_msgForward, 強(qiáng)制進(jìn)入轉(zhuǎn)發(fā)
2 進(jìn)入轉(zhuǎn)發(fā)流程后, 在第三步進(jìn)行操作-[NSObject forwardInvocation:invo]
3 -[NSObject forwardInvocation:invo]會將函數(shù)簽名和參數(shù)類型,返回類型, 消息接收者等包裝到invo中
4 我們要將 當(dāng)前的sel, 即runFrom:to: 通過 invo 指定一個新的 newTarget對象, 讓這個newTarget 去執(zhí)行剩下的操作
5 newTarget 執(zhí)行 runFrom:to:, 首先 輸出 hello wrold, 然后再想辦法回調(diào)原來的實(shí)現(xiàn)
6 所以 第4步中, 在指定給newTarget的時候, 要將當(dāng)前對象eg:originalTarget傳遞給newTarget, 因?yàn)榧僭O(shè) newTarget能調(diào)用到原來的 runFrom:to:, 但是原始的runFrom:to:里可能通過 originalTarget來獲取相關(guān)的成員變量等信息, 這就要求了 newTarget在回調(diào)原來的方法時要原封不動的通過 [originalTarget runFrom:arg0 to:arg1]的方式去調(diào)用,這不需要傳遞參數(shù), 因?yàn)檗D(zhuǎn)發(fā)給newTarge的 runFrom:to:時,參數(shù)也傳遞了過來
7 第6步的分析又出現(xiàn)新的問題, 就是 [originalTarget runFrom:arg0 to:arg1] 會直接進(jìn)入消息轉(zhuǎn)發(fā), 因?yàn)樯厦嬉呀?jīng)說了, originalTarget的 runFrom:to:已經(jīng)被替換成了_objc_msgForward, 那怎么解決呢
8 第7步解決的方案, 那只有在將 runFrom:to的IMP替換為 _objc_msgForward之前, 就先記錄下來, 然后在 4步里創(chuàng)建newTarget的時候, 將IMP賦值給newTarget, 這樣newTarget可以直接調(diào)用
IMP(newTarget.originalTarget, NSSelectorFromeString(@"runFrom:to:"), arg0,arg1)
9 反思, 上述流程里是靜態(tài)創(chuàng)建了一個轉(zhuǎn)發(fā)對象newTarget, 那么newTarget的類型,即類的設(shè)計可以考慮靜態(tài)xcode的方式去創(chuàng)建, 也可以考慮動態(tài)創(chuàng)建, 那既然這里講的是runtime, 那就用runtime來創(chuàng)建類, 豈不是逼格更高?!!
10 選擇方案, 動態(tài)創(chuàng)建當(dāng)前類Person的子類, 然后添加 子類的 runFrom:to:的IMP, 而且動態(tài)創(chuàng)建的子類要有擴(kuò)充的內(nèi)存, 用來添加一個元素的對象originalTarget, 這里就用到了 runtime的 add_Var的技巧了, 具體流程圖如下:(有點(diǎn)丑,但不要糾結(jié))

核心代碼
@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 - 每個對象都調(diào)換
+ (void)helloRegisterMethod{
////找到目標(biāo)方法
SEL tarSEL = sel_getUid("runFrom:to:");
Method tarM = class_getInstanceMethod(self, tarSEL);
////如果找到
if (tarM) {
///子類的類名 當(dāng)前類的名字 + _ + subClass
NSString* subClass = [NSStringFromClass(self) stringByAppendingFormat:@"_subClass"];
///創(chuàng)建子類 順便給一個var的空間, 用來保存轉(zhuǎn)發(fā)前的父類的實(shí)例對象, 目的是調(diào)用父類的實(shí)現(xiàn)的時候, 將這個對象傳給IMP, 保證原來的方法中做的事情不會被更改
Class sub = objc_allocateClassPair(self, subClass.UTF8String, sizeof(void*));
if (sub) {
/////這里先獲取到IMP的地址, 如果在block內(nèi)部獲取, 不知道為什么獲取的是 _objc_msgForward的地址
IMP org = method_getImplementation(tarM);
////添加一個var
NSString* varName = [NSStringFromClass(self) stringByAppendingFormat:@"_sub_%@",NSStringFromSelector(tarSEL)];
///這里解釋下第3g 參數(shù), 內(nèi)存對齊的問題, 他的值與 要添加var的類型有關(guān), 也和cpu的架構(gòu)有關(guān),如果是指針, 則傳log2(sizeof(type*)), 官方文檔說的很清楚
class_addIvar(sub,
varName.UTF8String,
sizeof(void*),
log2(sizeof(void*)),
@encode(NSString*));
////為子類添加 一個同樣的sel的實(shí)現(xiàn), 相當(dāng)于是覆蓋了父類
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) {
///再調(diào)用原始的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 的實(shí)現(xiàn)
- 在
Person里加一個類方法, 最好在Person初始化或者第一次用的的時候調(diào)用 - 在這個類方法中像上一篇實(shí)現(xiàn)KVO的原理一樣,
只不過這里不是替換setter,這里是替換 runFrom:to: - 新的實(shí)現(xiàn)里 先輸出
hello wrold, 然后調(diào)用父類的實(shí)現(xiàn) - 這里要注意的一點(diǎn)是,怎么調(diào)用父類的代碼, 這里先插入一段關(guān)于super調(diào)用的問題
super代表的僅僅是編譯器指令, 并不是一個實(shí)體的對象, 比如
[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
};
//參數(shù)1 代表消息接收者, 參數(shù)2代表superClass的類對象
//編譯器創(chuàng)建臨時的tmpSuper偽代碼如下
struct objc_super* tmpSuper = malloc(sizeof(struct objc_super));
tmpSuper->receiver = self;
tmpSuper->super_class = class_getSuperclass( object_getClass(self) );
///然后將創(chuàng)建好的 tmpSuper傳給 objc_msgSendSuper
objc_msgSendSuper(tmpSuper, callFun, arg0);
///objc_msgSendSuper的內(nèi)部會根據(jù) tmpSuper的 super_class, 找到父類類對象的地址
/// 然后去方法列表里尋找方法, 找到方法之后eg:findIMP, 就會直接通過IMP調(diào)用
findIMP(tmpSuper->receiver, callFun, arg0);
///所以很多面試?yán)飭柕?[super class], 返回的class是什么類型, 其實(shí)就像上面說的過程一樣,最后接收消息的對象始終沒有變, 所以還是當(dāng)前的類
///有的文檔說objc_msgSendSuper的內(nèi)部在找到方法的時候, 會再次調(diào)用 objc_msgSend(tmpSuper->receiver, callFun, arg0)
///我想應(yīng)該不是這樣, 這樣和 [self callFun:arg0] 沒有區(qū)別了, 因?yàn)閟uper表示到父類里找實(shí)現(xiàn)
///調(diào)用父類里處理的邏輯, 主要是和self覆蓋的callFun: 分開來, 所以我認(rèn)為是找到findIMP后, 直接執(zhí)行了
free(tmpSuper);
需求實(shí)現(xiàn)核心代碼
Class reallyClass = object_getClass(self);
NSString* tmpClassName = [_ClassPrefix stringByAppendingString:NSStringFromClass(reallyClass)];
///創(chuàng)建新的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");
////自己構(gòu)建objc_msgSendSuper的第1個結(jié)構(gòu)體
struct objc_super tmpSuper = {
obj,
class_getSuperclass( object_getClass(obj) );
};
objc_msgSendSuper(&tmpSuper, NSSelectorFromString(@"runFrom:to:"), value1, value2)
}), "v@:@");
objc_registerClassPair(newClass);
上述是為整個類替換掉! 能不能只指定某個對象, 其他對象不影響呢!!, 其實(shí)上一篇的KVO實(shí)現(xiàn)就是針對某個對象的, 其他對象不受影響
場景2 和上述需求一樣, 監(jiān)聽某個方法, 在方法執(zhí)行之后或之前做事情, 但是不同的是, 場景2要求只監(jiān)聽某些對象, 沒有被監(jiān)聽的對象不影響
比如
Car這個類, 監(jiān)聽方法runFrom:to:, 希望在這個方法執(zhí)行后, 再輸出一句洗車, 但是只是Car的實(shí)例 car1會改變動作, 其他的car2, car3. car4等不受影響
對比場景1
? 和場景1不一樣的地方是, 這里只針對某些對象, 有點(diǎn)像上一篇的KVO(深入理解OC的運(yùn)行時(Runtime)), 這里也有 幾種對應(yīng)關(guān)系:
? 1VS1 比如 vc1 想監(jiān)聽car1的這個動作, 然后在car1執(zhí)行完方法后, 做自己的事情
? 1VS多, 比如view1也想像VC一樣監(jiān)聽 car1, 做自己的操作
? 多VS1 VC想同時監(jiān)聽 car1, car2
.... 總之和kvo的性質(zhì)一樣, 但是這里的實(shí)現(xiàn)和kvo有所不同
解決方案選擇
利用runtime的進(jìn)行轉(zhuǎn)發(fā), 用實(shí)現(xiàn)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相關(guān)的信息字典
@"sel1字符串":@{ 對象1的 sel1 相關(guān)信息的字
@"before": @[block1, block2, ...].multable
@"replace": @[block1, block2, ...].multable
@"after":@[block1,block2, ... ].multable
}.mutable,
@"sel2字符串":@{ 對象1的 sel2 相關(guān)信息的字典
///和sel1同樣的結(jié)構(gòu)
}.mutable,
...
}.multable,
@"對象2的hash字符串":@{
和對象1同樣的結(jié)構(gòu)
}.multable
}.multable
轉(zhuǎn)發(fā)分類 相關(guān)宏的頭文件
#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,
/** 回調(diào)不能空 */
LBFEC(EmptyOperation) = TimeBegin_,
/** sel不能空 */
LBFEC(EmptySEL),
/** delloc的監(jiān)聽時間點(diǎn)必須是調(diào)用dealloc之前 */
LBFEC(DellocTime),
/** 禁止監(jiān)聽alloc */
LBFEC(AllocForbid),
/** 禁止監(jiān)聽initXXXX */
LBFEC(InitForbid),
/** 禁止監(jiān)聽ARC相關(guān)的 內(nèi)存管理 */
LBFEC(ARCForbid),
/** 禁止監(jiān)聽轉(zhuǎn)函數(shù) 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 @"回調(diào)不能空";
case LBFECEmptySEL: return @"原SEL不能空";
case LBFECDellocTime:return @"回調(diào)必須在調(diào)用dealloc之前";
case LBFECAllocForbid:return @"alloc 不能監(jiān)聽";
case LBFECInitForbid: return @"initXXX 不能監(jiān)聽";
case LBFECARCForbid: return @"ARC(retain release, autoRelease) 不能監(jiān)聽";
case LBFECSYSForward:return @"禁止監(jiān)聽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
這個類主要的作用是注冊監(jiān)聽方法的時候, 出錯的描述
轉(zhuǎn)發(fā)的分類頭文件
#import "LBForwardType.h"
#define TimeBegin_ 3000
/** LBForwardOperationTime */
typedef enum: NSInteger{
/** 在調(diào)用方法之前 回調(diào) */
LBFOT(Before) = TimeBegin_,
/** 替換之前的方法 */
LBFOT(Replace),
/** 調(diào)用完之前的方法后 再回調(diào) */
LBFOT(After)
}LBFOT;
#undef TimeBegin_
@class LBForwardError;
typedef void(^LBFEBlock)(LBForwardError* _Nonnull error);
NS_ASSUME_NONNULL_BEGIN
@interface NSObject (LBForward)
/**
對self的方法進(jìn)行監(jiān)聽, 在指定的time做自己的操作
@param opBlock 你的操作 block, 簽名格式要對應(yīng)sel block的第一個參數(shù) == self, 后面開始是 sel的參數(shù)列表, block里沒有傳sel出來
@param oSel 原方法的sel
@param time 操作的時間點(diǎn)
@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
轉(zhuǎn)發(fā)的分類的實(shí)現(xiàn)
#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相關(guān)的報錯"
#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的結(jié)構(gòu)
typedef struct LBBlockLayout LBBlockLayout;
struct LBBlockLayout{
void *isa; ///block指向的類, 這里oc是為了統(tǒng)一, 因?yàn)楫?dāng)一個參數(shù)是id的時候, 可以傳任意的block,而且還可以通過 NSInvocation 指定target, 設(shè)置好簽名參數(shù)調(diào)用block的實(shí)現(xiàn)
int flags; ///標(biāo)記
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相關(guān)信息的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 相關(guān)信息的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 - 快速根據(jù)code創(chuàng)建錯誤信息
static inline LBForwardError* _LB_FORWARD_ERROR(LBFEC code){
return [LBForwardError lbEWithCode:code];
}
#pragma mark - 快速根據(jù)des創(chuàng)建 其他錯誤信息的error
static inline LBForwardError* _LB_FORWARD_OTHER_E(NSString* des){
return [LBForwardError lbOEWithDes:des];
}
#pragma mark - 檢查當(dāng)前的對象是否是動態(tài)創(chuàng)建的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 - 快速獲取當(dāng)前對象 要動態(tài)被創(chuàng)建的類名
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 - 快速獲取當(dāng)前動態(tài)創(chuàng)建的子類 被添加的新的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 - 根據(jù)操作的枚舉值返回對應(yīng)的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已經(jīng)指向動態(tài)的子類了
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 - 輔助函數(shù)
#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 - 類實(shí)現(xiàn)
@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];
////注冊相關(guān)信息
[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];
////注冊相關(guān)信息
[self registerInfoWithOperationBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
}
#pragma mark - 參數(shù)相關(guān)檢查
- (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));
}
///沒有具體的實(shí)現(xiàn)
if(!_LB_FORWARD_SEL_ORIGINAL_SIGNA(self, LB_FORWARD_PARAM_NAME_SEL)){
NSString* errorStr = [NSString stringWithFormat:@"對象:%p(%@) 的%@ 沒有對應(yīng)的實(shí)現(xiàn)",
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));
}
///不能監(jiān)聽的方法
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 - 注冊相關(guān)信息
- (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];
///創(chuàng)建子類
if (!reallyClass)
[self createSubClassWithSEL:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
///緩存相關(guān)的信息
[self cacheOPWithBlock:LB_FORWARD_PARAM_NAME_OP
originalSel:LB_FORWARD_PARAM_NAME_SEL
executeTime:LB_FORWARD_PARAM_NAME_TIME];
}
#pragma mark - 當(dāng)前類是否創(chuàng)建過子類
- (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(內(nèi)部會設(shè)置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 - 緩存相關(guān)信息
- (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 - 動態(tài)創(chuàng)建類
- (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 - 方法注冊相關(guān)
- (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會添加失敗, 因?yàn)榈?次的時候已經(jīng)正確填充信息了, 所以這里不要糾結(jié)
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)) {
///將原來的方法 強(qiáng)制指向系統(tǒng)調(diào)用函數(shù), 這樣外界調(diào)用的時候就會進(jìn)入轉(zhuǎn)發(fā)
class_replaceMethod(reallyClass,
LB_FORWARD_PARAM_NAME_SEL,
_objc_msgForward,
selOEncode
);
///將當(dāng)前class的 轉(zhuǎn)發(fā)函數(shù)替換到 自定義的, 方便處理
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 - 全局轉(zhuǎn)發(fā)函數(shù)
__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);
///這里的問題是 只要有一個替代的操作, 原方法就執(zhí)行不了, 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. 創(chuàng)建car1對象
? 2. 調(diào)用lbf_hookOperationBlock: originalSel: executeTime: catch:
? ? 2.1 catch一定要傳, 不然異常
? ? 2.2 檢查 各個參數(shù)的是否空, 檢查sel是否實(shí)現(xiàn)和過濾相關(guān)的方法,檢查block的簽名是否和sel的一致
? ? 2.3 注冊相關(guān)信息
? ? ? ? 2.3.1 判斷是否動態(tài)創(chuàng)建過子類, 沒創(chuàng)建就創(chuàng)建子類
? ? ? ? 2.3.2 先從緩存里取相關(guān)的數(shù)據(jù)(block), 沒取到, 就創(chuàng)建, 創(chuàng)建的過程里把 當(dāng)前的self的isa指向了新的類LBF_CLASS_originaclass,然后動態(tài)添加了一個新的SEL === LBF_SEL_originalSel, 并且指向了原方法的IMP,然后將原方法oSel指向了objc_msgForward, 然后將轉(zhuǎn)發(fā)函數(shù)forwardInvocation:指向了全局處理的函數(shù)LB_FORWARD_ENTRANCE, 當(dāng)調(diào)用注冊的方法的時候, 會來到全局處理的函數(shù)里, 在這里面根據(jù)記錄進(jìn)行篩選
? ? 2.4 可以調(diào)用resign相關(guān)的函數(shù)注掉監(jiān)聽
PS:這個簡單的demo,有很多問題, 只是簡單的測試了下, 里面記錄用的是全局的map, 所以要自己處理注銷的問題,如果是用
runtime里的關(guān)聯(lián),那么self 被dealloc的時候會自動釋放掉之前關(guān)聯(lián)的內(nèi)存(Aspeacts), 里面沒有實(shí)現(xiàn)類方法的監(jiān)聽, 但是寫這個demo, 基本runtime的 知識點(diǎn)都用到了, 包括block的簽名獲取, 怎么通過方法調(diào)用block等等