一、fishhook
1、介紹
fishhook是facebook出品的一個(gè)開(kāi)源庫(kù)。利用mach-o文件加載原理,通過(guò)rebind_symbols函數(shù)修改__DATA Segment的符號(hào)指針指向,來(lái)動(dòng)態(tài)的Hook C函數(shù)。
2、主要信息
2.1、結(jié)構(gòu)體
struct rebinding {
const char *name; //函數(shù)名稱
void *replacement; //新的函數(shù)地址
void **replaced; //保存原始函數(shù)地址變量的指針(通常要存儲(chǔ)下來(lái),在替換后的方法里調(diào)用)
};
2.2、主要接口
/*
交換方法
arg1: 存放rebinding結(jié)構(gòu)體的數(shù)組
arg2: 數(shù)組的長(zhǎng)度
*/
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
3、實(shí)現(xiàn)
3.1、hook OC函數(shù)
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//這里必須要先加載一次要交換的函數(shù),否則符號(hào)表里面不會(huì)出現(xiàn)要交換的函數(shù)的地址
NSLog(@"我是純正的NSLog函數(shù)");
//定義rebinding結(jié)構(gòu)體
struct rebinding manager;
//要交換的函數(shù)的名稱
manager.name = "NSLog";
//新的函數(shù)地址
manager.replacement = new_NSLog;
//保存原始函數(shù)地址變量的指針(存儲(chǔ)下來(lái),在替換后的方法里調(diào)用)
manager.replaced = (void *)&old_NSLog;
//定義數(shù)組
struct rebinding rebs[] = {manager};
/*
交換方法
arg1: 存放rebinding結(jié)構(gòu)體的數(shù)組
arg2: 數(shù)組的長(zhǎng)度
*/
rebind_symbols(rebs, 1);
}
//函數(shù)指針,用來(lái)保存原始的函數(shù)地址
static void (* old_NSLog)(NSString *format, ...);
//新的NSLog函數(shù)
void new_NSLog(NSString *format, ...) {
//再調(diào)用原來(lái)的
old_NSLog(@"勾上了!");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"點(diǎn)擊屏幕");
}
@end
結(jié)果

3.2、hook C函數(shù)
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//這里必須要先加載一次要交換的函數(shù),否則符號(hào)表里面不會(huì)出現(xiàn)要交換的函數(shù)的地址
printf("我是純正的printf函數(shù)\n");
//定義rebinding結(jié)構(gòu)體
struct rebinding manager;
//要交換函數(shù)的名稱
manager.name = "printf";
//新的函數(shù)地址
manager.replacement = new_printf;
//保存原始函數(shù)地址變量的指針(存儲(chǔ)下來(lái),在替換后的方法里調(diào)用)
manager.replaced = (void *)&old_printf;
//定義數(shù)組
struct rebinding rebs[] = {manager};
/*
交換方法
arg1: 存放rebinding結(jié)構(gòu)體的數(shù)組
arg2: 數(shù)組的長(zhǎng)度
*/
rebind_symbols(rebs, 1);
}
//函數(shù)指針,用來(lái)保存原始的函數(shù)地址
static int (* old_printf)(const char *, ...);
//新的printf函數(shù)
void new_printf(const char * name, ...) {
//再調(diào)用原來(lái)的
old_printf("勾上了!\n");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
printf("點(diǎn)擊屏幕");
}
@end

二、Method Swizzle
1、介紹
Method Swizzle是利用OC的Runtime特性,動(dòng)態(tài)改變SEL(方法編號(hào))和IMP(方法實(shí)現(xiàn))的對(duì)應(yīng)關(guān)系,達(dá)到OC方法調(diào)用流程改變的目的。主要用于OC方法調(diào)換或往系統(tǒng)方法注入代碼。
2、Runtime 術(shù)語(yǔ)的數(shù)據(jù)結(jié)構(gòu)
SEL
它是selector在 Objc 中的表示(Swift 中是 Selector 類(lèi))。selector 是方法選擇器,其實(shí)作用就和名字一樣,日常生活中,我們通過(guò)人名辨別誰(shuí)是誰(shuí),注意 Objc 在相同的類(lèi)中不會(huì)有命名相同的兩個(gè)方法。selector 對(duì)方法名進(jìn)行包裝,以便找到對(duì)應(yīng)的方法實(shí)現(xiàn)。它的數(shù)據(jù)結(jié)構(gòu)是:
typedef struct objc_selector *SEL;
我們可以看出它是個(gè)映射到方法的 C 字符串,你可以通過(guò) Objc 編譯器器命令@selector() 或者 Runtime 系統(tǒng)的 sel_registerName 函數(shù)來(lái)獲取一個(gè) SEL 類(lèi)型的方法選擇器。
注意:
不同類(lèi)中相同名字的方法所對(duì)應(yīng)的selector是相同的,由于變量的類(lèi)型不同,所以不會(huì)導(dǎo)致它們調(diào)用方法實(shí)現(xiàn)混亂。
id
id 是一個(gè)參數(shù)類(lèi)型,它是指向某個(gè)類(lèi)的實(shí)例的指針。定義如下:
typedef struct objc_object *id;
struct objc_object { Class isa; };
以上定義,看到 objc_object 結(jié)構(gòu)體包含一個(gè) isa 指針,根據(jù) isa 指針就可以找到對(duì)象所屬的類(lèi)。
注意:
isa 指針在代碼運(yùn)行時(shí)并不總指向?qū)嵗龑?duì)象所屬的類(lèi)型,所以不能依靠它來(lái)確定類(lèi)型,要想確定類(lèi)型還是需要用對(duì)象的 -class 方法。(如:KVO 的實(shí)現(xiàn)機(jī)理就是將被觀察對(duì)象的 isa 指針指向一個(gè)中間類(lèi)而不是真實(shí)類(lèi)型)
Class
typedef struct objc_class *Class;
Class 其實(shí)是指向 objc_class 結(jié)構(gòu)體的指針。objc_class 的數(shù)據(jù)結(jié)構(gòu)如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY; //類(lèi)的isa指針,指向其所屬的元類(lèi)(meta)
#if !__OBJC2__
Class super_class //父類(lèi)指針 OBJC2_UNAVAILABLE;
const char *name //類(lèi)名 OBJC2_UNAVAILABLE;
long version //是類(lèi)的版本信息 OBJC2_UNAVAILABLE;
long info //是類(lèi)的詳情 OBJC2_UNAVAILABLE;
long instance_size //該類(lèi)的實(shí)例對(duì)象的大小 OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars //成員變量列表 OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists //方法列表 OBJC2_UNAVAILABLE;
struct objc_cache *cache //緩存 OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols //協(xié)議列表 OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
- ·Class 也有一個(gè) isa 指針,指向其所屬的元類(lèi)(meta)。
- ·super_class:指向其超類(lèi)。
- ·name:是類(lèi)名。
- ·version:是類(lèi)的版本信息。
- ·info:是類(lèi)的詳情。
- ·instance_size:是該類(lèi)的實(shí)例對(duì)象的大小。
- ·ivars:指向該類(lèi)的成員變量列表。
- ·methodLists:指向該類(lèi)的實(shí)例方法列表,它將方法選擇器和方法實(shí)現(xiàn)地址聯(lián)系起來(lái)。methodLists 是指向 ·objc_method_list 指針的指針,也就是說(shuō)可以動(dòng)態(tài)修改 *methodLists 的值來(lái)添加成員方法,這也是 Category 實(shí)現(xiàn)的原理,同樣解釋了 Category 不能添加屬性的原因。
- ·cache:Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到 cache 中(理論上講一個(gè)方法如果被調(diào)用,那么它有可能今后還會(huì)被調(diào)用),下次查找的時(shí)候效率更高。
- ·protocols:指向該類(lèi)的協(xié)議列表。
其中 objc_ivar_list 和 objc_method_list 分別是成員變量列表和方法列表:
//成員變量列表
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list {
struct objc_method_list *obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
由此可見(jiàn),我們可以動(dòng)態(tài)修改 *methodList 的值來(lái)添加成員方法,這也是 Category 實(shí)現(xiàn)的原理,同樣解釋了 Category 不能添加屬性的原因。
objc_ivar_list 結(jié)構(gòu)體用來(lái)存儲(chǔ)成員變量的列表,而 objc_ivar 則是存儲(chǔ)了單個(gè)成員變量的信息;同理,objc_method_list 結(jié)構(gòu)體存儲(chǔ)著方法數(shù)組的列表,而單個(gè)方法的信息則由 objc_method 結(jié)構(gòu)體存儲(chǔ)。
值得注意的時(shí),objc_class 中也有一個(gè) isa 指針,這說(shuō)明 Objc 類(lèi)本身也是一個(gè)對(duì)象。為了處理類(lèi)和對(duì)象的關(guān)系,Runtime 庫(kù)創(chuàng)建了一種叫做 Meta Class(元類(lèi)) 的東西,類(lèi)對(duì)象所屬的類(lèi)就叫做元類(lèi)。Meta Class 表述了類(lèi)對(duì)象本身所具備的元數(shù)據(jù)。

Method
Method 代表類(lèi)中某個(gè)方法的類(lèi)型
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
objc_method 存儲(chǔ)了方法名,方法類(lèi)型和方法實(shí)現(xiàn):
- 方法名,類(lèi)型為
SEL - 方法類(lèi)型
method_types是個(gè) char 指針,存儲(chǔ)方法的參數(shù)類(lèi)型和返回值類(lèi)型 -
method_imp指向了方法的實(shí)現(xiàn),本質(zhì)是一個(gè)函數(shù)指針
Ivar
Ivar 是表示成員變量的類(lèi)型。
typedef struct objc_ivar *Ivar;
struct objc_ivar {
char *ivar_name OBJC2_UNAVAILABLE;
char *ivar_type OBJC2_UNAVAILABLE;
int ivar_offset OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
}
其中 ivar_offset 是基地址偏移字節(jié)
IMP
IMP在objc.h中的定義是:
typedef id (*IMP)(id, SEL, ...);
它就是一個(gè)函數(shù)指針,這是由編譯器生成的。當(dāng)你發(fā)起一個(gè) ObjC 消息之后,最終它會(huì)執(zhí)行的那段代碼,就是由這個(gè)函數(shù)指針指定的。而 IMP 這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)。
你會(huì)發(fā)現(xiàn) IMP 指向的方法與 objc_msgSend 函數(shù)類(lèi)型相同,參數(shù)都包含 id 和 SEL 類(lèi)型。每個(gè)方法名都對(duì)應(yīng)一個(gè) SEL 類(lèi)型的方法選擇器,而每個(gè)實(shí)例對(duì)象中的 SEL 對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的,通過(guò)一組 id和 SEL參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址。
Cache
Cache 定義如下:
typedef struct objc_cache *Cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
Cache 為方法調(diào)用的性能進(jìn)行優(yōu)化,每當(dāng)實(shí)例對(duì)象接收到一個(gè)消息時(shí),它不會(huì)直接在 isa 指針指向的類(lèi)的方法列表中遍歷查找能夠響應(yīng)的方法,因?yàn)槊看味家檎倚侍土耍莾?yōu)先在 Cache 中查找。
Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到 Cache 中,如果一個(gè)方法被調(diào)用,那么它有可能今后還會(huì)被調(diào)用,下次查找的時(shí)候就會(huì)效率更高。就像計(jì)算機(jī)組成原理中 CPU 繞過(guò)主存先訪問(wèn) Cache 一樣。
3、方法交換(Method Swizzling)
下面代碼對(duì)UIViewController中的viewDidLoad方法進(jìn)行交換
1、UIViewController+swizzling.m文件
#import "UIViewController+swizzling.h"
#import <objc/runtime.h>
@implementation UIViewController (swizzling)
//load類(lèi)方法(當(dāng)某個(gè)類(lèi)的代碼被讀到內(nèi)存后調(diào)用)
+ (void)load
{
/*
SEL: 它就是個(gè)映射到方法的C字符串,可以用Objective-C編譯器命令@selector()或者Runtime系統(tǒng)的sel_registerName函數(shù)來(lái)獲得一個(gè)SEL類(lèi)型的方法選擇器。
數(shù)據(jù)結(jié)構(gòu)是:
typedef struct objc_selector *SEL;
*/
SEL origSel = @selector(viewDidLoad);
SEL swizSel = @selector(swiz_viewDidLoad);
[UIViewController swizzleMethods:[self class] originalSelector:origSel swizzledSelector:swizSel];
}
//交換兩個(gè)方法的實(shí)現(xiàn)
+ (void)swizzleMethods:(Class)class originalSelector:(SEL)origSel swizzledSelector:(SEL)swizSel
{
/*
Method 代表類(lèi)中某個(gè)方法的類(lèi)型
typedef struct objc_method *Method;
struct objc_method {
SEL method_name OBJC2_UNAVAILABLE;
char *method_types OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
}
objc_method 存儲(chǔ)了方法名,方法類(lèi)型和方法實(shí)現(xiàn):
方法名類(lèi)型為 SEL
方法類(lèi)型 method_types 是個(gè) char 指針,存儲(chǔ)方法的參數(shù)類(lèi)型和返回值類(lèi)型
method_imp 指向了方法的實(shí)現(xiàn),本質(zhì)是一個(gè)函數(shù)指針
class_getInstanceMethod(class, origSel);
返回class類(lèi)的origSel方法。
*/
Method origMethod = class_getInstanceMethod(class, origSel);
Method swizMethod = class_getInstanceMethod(class, swizSel);
/*
周全起見(jiàn),有兩種情況要考慮一下
第一種情況:要交換的方法并沒(méi)有在目標(biāo)類(lèi)中實(shí)現(xiàn),而是在其父類(lèi)中實(shí)現(xiàn)了, 這時(shí)使用class_getInstanceMethod函數(shù)獲取到的originalSelector指向的就是父類(lèi)的方法
第二種情況:要交換的方法已經(jīng)存在于目標(biāo)類(lèi)中
class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
往class這個(gè)類(lèi),添加方法origSel,新增方法的實(shí)現(xiàn)地址為method_getImplementation(swizMethod),參數(shù)及返回值類(lèi)型為method_getTypeEncoding(swizMethod)
添加成功返回YES,失敗返回NO(例如,該類(lèi)已經(jīng)包含一個(gè)同名的方法實(shí)現(xiàn))
method_getImplementation(swizMethod) 返回swizMethod方法實(shí)現(xiàn)
method_getTypeEncoding(swizMethod) 返回描述swizMethod方法參數(shù)和返回值類(lèi)型的字符串
*/
//判斷是情況一還是情況二
BOOL didAddMethod = class_addMethod(class, origSel, method_getImplementation(swizMethod), method_getTypeEncoding(swizMethod));
if (didAddMethod)
{
/*
第一種情況:要交換的方法已經(jīng)在父類(lèi)中實(shí)現(xiàn)了,則替換父類(lèi)方法
使用replaceMethod來(lái)替換給定類(lèi)的方法實(shí)現(xiàn)(將origMethod方法替換成swizSel方法)
*/
class_replaceMethod(class, swizSel, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
}
else
{
/*
第二種情況:要交換的方法已經(jīng)存在于目標(biāo)類(lèi)中
通過(guò)method_exchangeImplementations來(lái)交換方法
*/
method_exchangeImplementations(origMethod, swizMethod);
}
}
//自己實(shí)現(xiàn)的用于交換的方法
- (void)swiz_viewDidLoad
{
NSLog(@"調(diào)用了swiz_viewDidLoad方法");
/*
需要注入的代碼寫(xiě)在此處
*/
//執(zhí)行這句的時(shí)候跳轉(zhuǎn)到viewDidLoad方法中
[self swiz_viewDidLoad];
}
@end
2、ViewController.m文件
#import "ViewController.h"
#import "UIViewController+swizzling.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad
{
//執(zhí)行這句的時(shí)候跳到swiz_viewDidLoad方法中
[super viewDidLoad];
NSLog(@"調(diào)用了viewDidLoad方法");
}
@end
當(dāng)執(zhí)行[super viewDidLoad]時(shí),跳轉(zhuǎn)到swiz_viewDidLoad方法中。當(dāng)執(zhí)行到[self swiz_viewDidLoad]時(shí),再跳轉(zhuǎn)到viewDidLoad方法中。因此,先打印“調(diào)用了swiz_viewDidLoad方法”,再打印“調(diào)用了viewDidLoad方法”
