面試系列:
- iOS面試全解1:基礎(chǔ)/內(nèi)存管理/Block/GCD
- iOS面試全解2:Runloop
- iOS面試全解3:Runtime
- iOS面試全解4:KVC谅河、KVO、通知/推送/信號(hào)量球碉、Delegate/Protocol、Singleton
一、概念:什么是Runtime达布?平時(shí)項(xiàng)目中有用過么?
- 簡(jiǎn)單來說:是一套底層的C語言API斩熊。
- 具體應(yīng)用:能動(dòng)態(tài) “ 增/刪/改/查 ” 一個(gè)類的 成員變量 和 方法.
1往枣、能動(dòng)態(tài) 創(chuàng)建 一個(gè)類,一個(gè)成員變量(屬性)粉渠,一個(gè)方法
2分冈、能動(dòng)態(tài) 修改 一個(gè)類,一個(gè)成員變量(屬性)霸株,一個(gè)方法
3雕沉、能動(dòng)態(tài) 刪除 一個(gè)類,一個(gè)成員變量(屬性)去件,一個(gè)方法
OC是一門動(dòng)態(tài)性比較強(qiáng)的編程語言坡椒,允許很多操作推遲到程序運(yùn)行時(shí)再進(jìn)行。通過此篇文章了解多態(tài):iOS面試全解1:基礎(chǔ)/內(nèi)存管理/Block/GCD
OC的動(dòng)態(tài)性就是由Runtime來支撐和實(shí)現(xiàn)的尤溜,Runtime是一套C語言的API倔叼,封裝了很多動(dòng)態(tài)性相關(guān)的函數(shù),平時(shí)編寫的OC代碼宫莱,底層都是轉(zhuǎn)換成了Runtime API進(jìn)行調(diào)用:
比如類轉(zhuǎn)成了runtime庫里面的結(jié)構(gòu)體等數(shù)據(jù)類型丈攒,方法轉(zhuǎn)成了runtime庫里面的C語言函數(shù),平時(shí)調(diào)方法都是轉(zhuǎn)成了objc_msgSend
函數(shù) (就是OC的消息發(fā)送機(jī)制
)授霸。在OC中巡验,使用對(duì)象進(jìn)行方法調(diào)用是一個(gè)消息發(fā)送的過程(Objective-C采用“動(dòng)態(tài)綁定機(jī)制”
,所以所要調(diào)用的方法直到運(yùn)行期才能確定)碘耳。
項(xiàng)目中的使用:
1显设、
關(guān)聯(lián)對(duì)象
: 給分類添加屬性,AssociatedObject(提高性能辛辨,避免不使用的 繼承類屬性 初始化)
2捕捂、遍歷類的所有成員變量
:修改textfield的占位文字顏色瑟枫、字典轉(zhuǎn)模型Model、自動(dòng)歸檔解檔
3指攒、交換方法實(shí)現(xiàn)
:(交換系統(tǒng)的方法力奋,埋點(diǎn)、防逆向hook)
4幽七、利用消息轉(zhuǎn)發(fā)機(jī)制
:解決 「方法找不到」的異常問題
......
什么是 isa 景殷?
isa 指針(NONPOINTER_ISA)
isa:
是一個(gè)指向?qū)ο笏鶎貱lass類型的指針。(nonPointer_isa)
對(duì)象的isa指針澡屡,用來表明對(duì)象所屬的類類型和一些附加信息猿挚。
如果isa指針僅表示類型的話,對(duì)內(nèi)存顯然也是一個(gè)極大的浪費(fèi)驶鹉。于是绩蜻,就像tagged pointer一樣,對(duì)于isa指針室埋,蘋果同樣進(jìn)行了優(yōu)化
办绝。isa指針表示的內(nèi)容變得更為豐富,除了表明對(duì)象屬于哪個(gè)類之外姚淆,還附加了 「引用計(jì)數(shù)」extra_rc
孕蝉,是否有被weak引用標(biāo)志位weakly_referenced
,是否有附加對(duì)象標(biāo)志位has_assoc
等信息腌逢。(rc:reference counter 引用計(jì)數(shù))
1降淮、實(shí)例isa 指向類對(duì)象,類對(duì)象isa 指向元類對(duì)象(指向上)
2搏讶、在 arm64之前佳鳖,isa指針就直接存儲(chǔ)著 類對(duì)象/元類對(duì)象 的地址值;
在 arm64時(shí)媒惕,isa進(jìn)行了優(yōu)化系吩,采取 共用體(union)的結(jié)構(gòu),共有64位妒蔚,分開來存儲(chǔ)了很多東西穿挨,其中有33位是存儲(chǔ) 地址值,使用 &Mask 取出 地址值面睛。
objc源碼
//------- NSObject(實(shí)質(zhì)) -------
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
//------- objc_class(繼承)-------
struct objc_class : objc_object {
// Class ISA;
Class superclass;
cache_t cache; // formerly cache pointer and vtable 以前的緩存指針和虛函數(shù)表
class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags class_rw_t *加上自定義rr/alloc標(biāo)志
//絮蒿。尊搬。叁鉴。
}
//------- isa指針(指向)-------
struct objc_object {
private:
isa_t isa;
public:
Class ISA(); // ISA() assumes this is NOT a tagged pointer object 假設(shè)這不是一個(gè)標(biāo)記的指針對(duì)象
Class getIsa(); // getIsa() allows this to be a tagged pointer object 允許這是一個(gè)標(biāo)記的指針對(duì)象
//。佛寿。幌墓。
}
//------- 聯(lián)合體(定義)-------
union isa_t
{
isa_t() { } //構(gòu)造函數(shù)1
isa_t(uintptr_t value) : bits(value) { } //構(gòu)造函數(shù)2
Class cls; //成員1(占據(jù)64位內(nèi)存空間)
uintptr_t bits; //成員2(占據(jù)64位內(nèi)存空間)
#if SUPPORT_PACKED_ISA
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
struct { //成員3(占據(jù)64位內(nèi)存空間:從低位到高位依次是nonpointer到extra_rc但壮。成員后面的:表明了該成員占用幾個(gè)bit。)
uintptr_t nonpointer : 1; //(低位)注意:標(biāo)志位常侣,表明isa_t *是否是一個(gè)真正的指針@!胳施!
uintptr_t has_assoc : 1; // 關(guān)聯(lián)對(duì)象
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1; //弱引用
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1; //引用計(jì)數(shù) 相關(guān)成員1
uintptr_t extra_rc : 19; //引用計(jì)數(shù) 相關(guān)成員2 (用19位來 記錄對(duì)象的引用次數(shù))
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
};
}
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
//向該類所繼承的父類對(duì)象
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
//成員變量列表
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
//方法列表
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;//方法列表
//用于緩存調(diào)用過的方法
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
//協(xié)議鏈表用來存儲(chǔ)聲明遵守的正式協(xié)議
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
}
Runtime 實(shí)質(zhì)是:方法和消息動(dòng)態(tài)綁定的過程溯祸。
當(dāng)對(duì)象收到消息時(shí),消息函數(shù)首先根據(jù)該對(duì)象的isa指針找到該對(duì)象所對(duì)應(yīng)的類的方法表舞肆,并從表中尋找 該消息對(duì)應(yīng)的方法選標(biāo)焦辅。如果找不到,objc_msgSend
將繼續(xù)從父類中尋找椿胯,直到 NSObject 類筷登。一 旦找到了方法選標(biāo), objc_msgSend 則以消息接收者對(duì)象為參數(shù)調(diào)用哩盲,調(diào)用該選標(biāo)對(duì)應(yīng)的方法實(shí)現(xiàn)前方。
這就是在運(yùn)行時(shí)
系統(tǒng)中選擇方法實(shí)現(xiàn)的方式。在面向?qū)ο缶幊讨辛停话惴Q作方法和消息動(dòng)態(tài)綁定的過程
惠险。
為了加快消息的處理過程,運(yùn)行時(shí)系統(tǒng)通常會(huì)將使用過的方法選標(biāo)和方法實(shí)現(xiàn)的地址放入緩存中
抒线。每個(gè)類都有一個(gè)獨(dú)立的緩存莺匠,同時(shí)包括繼承的方法和在該類中定義的方法。消息函數(shù)會(huì)首先檢查消息接收者對(duì)象對(duì)應(yīng)的類的緩存(理論上十兢,如果一個(gè)方法被使用過一次趣竣,那么它很可能被再次使用)。如果在緩存中已經(jīng)有了需要的方法選標(biāo)旱物,則消息僅僅比函數(shù)調(diào)用慢一點(diǎn)點(diǎn)遥缕。如果程序運(yùn)行了足夠長(zhǎng)的時(shí)間,幾乎每個(gè)消息都能在緩存中找到方法實(shí)現(xiàn)宵呛。程序運(yùn)行時(shí)单匣,緩存也將隨著新的消息的增加而增加。
既然類也是一個(gè)對(duì)象宝穗,那么它一定是其他類的實(shí)例户秤,那個(gè)類就是元類 (metaclass)
。元類是類(對(duì)象)的描述逮矛,就像類是普通實(shí)例的描述一樣鸡号。特別的是,元類的方法列表是類方法(類響應(yīng)的選擇器)须鼎。當(dāng)你發(fā)送消息給一個(gè)類(元類的實(shí)例)鲸伴,objc_msgSend()
會(huì)查找元類(已及它的父類)的方法列表并確定調(diào)用的方法府蔗。類方法是由類代表的元類所描述,如同實(shí)例方法是由實(shí)例對(duì)象對(duì)應(yīng)的類(對(duì)象)來描述的汞窗。
注意:當(dāng)一個(gè)消息發(fā)送給任何一個(gè)對(duì)象時(shí)姓赤,將會(huì)由對(duì)象的 isa 指針開始查找方法,接著沿著父類鏈向上去查找仲吏。
- 實(shí)例方法:被類定義不铆,
- 類方法:被元類定義。
以下案例說明類的關(guān)系:
MyClass *myClass = [[MyClass alloc] init];
整理下相互間的關(guān)系:
? myClass 是實(shí)例對(duì)象
? MyClass 是類對(duì)象
? MyClass 的元類就是 NSObject
? NSObject 就是 Root class (class)
? NSObject 的 superclass 為 nil
? NSObject 的元類就是它自己
? NSObject 的元類的 superclass 就是 NSObject
你覺得元類是什么樣的裹唆?到元類還會(huì)繼續(xù)向下嗎狂男?不,元類其實(shí)是根類 (root class) 的元類的實(shí)例品腹;這個(gè)根類的元類岖食,實(shí)際上就是它自己。isa 鏈在這里結(jié)束舞吭,形成一個(gè)環(huán)形(實(shí)例->類->元類->父元類->根元類->根元類自己
)泡垃。
objc_method:Method
objc_selector:SEL
objc_category:Category
objc_cache: 類緩存
objc_class: 類對(duì)象
objc_object:實(shí)例
Root class:根類 object(人的認(rèn)知)
meteClass:元類-人類
Superclass:父類 - 小明父親、小華父親......
Subclass: ??子類- 小明羡鸥、小華
找父類
子類的實(shí)例 -> 子類 -> 父類 -> 根類 -> nil
父類的實(shí)例 -> 父類 -> 根類 -> nil
根類的實(shí)例 -> 根類 -> nil
子類的元類 -> 父類的元類 -> 根類的元類 -> 根類的元類
找元類
子類 -> 子類的元類
父類 -> 父類的元類
根類 -> 根類的元類
終點(diǎn)是:根元類(NSObject
)
isa的關(guān)系圖
二蔑穴、基本源碼解析
1、基本符號(hào)與標(biāo)識(shí):
SEL
:方法編號(hào)惧浴,類成員方法的指針存和,但不同于C語言中的函數(shù)指針,函數(shù)指針直接保存了方法的地址衷旅,但SEL只是方法編號(hào)捐腿。定義成 char *
IMP
:函數(shù)指針,保存了方法的地址
2柿顶、常用的頭文件
#import <objc/ >
//包含對(duì):類茄袖、成員變量、屬性嘁锯、方法的操作
#import <objc/message.h>
//包含消息機(jī)制
3.常用方法
class_copyIvarList() 返回一個(gè)指向類的成員變量數(shù)組的指針
class_copyPropertyList()返回一個(gè)指向類的屬性數(shù)組的指針
注意:根據(jù)Apple官方runtime.h文檔所示宪祥,上面兩個(gè)方法返回的指針,在使用完畢之后必須free()家乘。
-------------------------------------
ivar_getName() 獲取成員變量名--> C類型的字符串
property_getName()獲取屬性名 --> C類型的字符串
-------------------------------------
typedef struct objc_method *Method;
class_getInstanceMethod()返回一個(gè)實(shí)例方法
class_getClassMethod() 返回一個(gè)類方法
method_exchangeImplementations()交換兩個(gè)方法的實(shí)現(xiàn)
-------------------------------------
.......很多方法蝗羊,在后面敘述!仁锯!
一種常見的辦法是通過runtime.h中
objc_getAssociatedObject /
objc_setAssociatedObject 來訪問和生成關(guān)聯(lián)對(duì)象耀找。
這兩個(gè)方法可以讓一個(gè)對(duì)象和另一個(gè)對(duì)象關(guān)聯(lián),就是說一個(gè)對(duì)象可以保持對(duì)另一個(gè)對(duì)象的引用,并獲取那個(gè)對(duì)象。
4.Runtime 基礎(chǔ)函數(shù)
method相關(guān)的函數(shù)也不是太多,下邊簡(jiǎn)單羅列說明一下
Method class_getInstanceMethod(Class cls, SEL name) //(-)獲取類中的 實(shí)例方法(減號(hào)方法):-(void)test;
Method class_getClassMethod(Class cls, SEL name) //(+)獲取類中的 類方法 (加號(hào)方法):+(void)test;
IMP class_getMethodImplementation(Class cls, SEL name) //獲取類中的方法實(shí)現(xiàn)
IMP class_getMethodImplementation_stret(Class cls, SEL name) //獲取類中的方法的實(shí)現(xiàn)插龄,該方法的返回值類型為struct
SEL method_getName(Method m) //獲取Method中的SEL(方法的名稱)
IMP method_getImplementation(Method m) //獲取Method中的IMP(方法的指針)
IMP method_setImplementation(Method m, IMP imp) //設(shè)置Method的IMP (方法的指針)
// 消息發(fā)送:
objc_msgSend(id _Nullable self, SEL _Nonnull op, ...)
-
方法執(zhí)行的邏輯是:先獲取對(duì)象對(duì)應(yīng)類的信息刽沾,再獲取方法的緩存,根據(jù)方法的 selector 查找函數(shù)指針灼芭, 經(jīng)過異常錯(cuò)誤處理后,最后跳到對(duì)應(yīng)函數(shù)的實(shí)現(xiàn)。
對(duì)象對(duì)應(yīng)類效诅、方法的緩存、函數(shù)指針趟济、異常處理乱投、函數(shù)的實(shí)現(xiàn)。
object -> class -> IMP -> exception -> method
//例5:方法的調(diào)用本質(zhì)是:消息機(jī)制顷编,objc_msgSend(消息接收者, 消息名稱);
[TZPerson addRun];
//同上
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TZPerson"), sel_registerName("addRun"));
//同上
objc_msgSend(objc_getClass("TZPerson"), sel_registerName("addRun"));
//同上
objc_msgSend(objc_getClass("TZPerson"), @selector(addRun));
三戚炫、方法的調(diào)用本質(zhì):消息轉(zhuǎn)發(fā)機(jī)制
以下是方法調(diào)用的過程:
0、通過isa找到類:先去緩存表中找媳纬,如果沒有双肤,方法表中尋找
1、如果沒有钮惠,就繼續(xù)找父類方法緩存表茅糜,沒有繼續(xù)找父父類方法表,直到根類素挽。
3蔑赘、 是否有方法:有就去調(diào)用、沒有去新增
4预明、是否有新增方法:有新增然后去調(diào)用缩赛、沒有新增去轉(zhuǎn)發(fā)
5、是否有消息轉(zhuǎn)發(fā):有取調(diào)用撰糠、沒有就崩潰
// objc_msgSend:(類/實(shí)例, 方法名稱) ;
// 本質(zhì)是消息機(jī)制:(消息接收者, 消息名稱)
在OC中方法調(diào)用的是通過Runtime實(shí)現(xiàn)的峦筒,Runtime進(jìn)行方法調(diào)用本質(zhì)上是發(fā)送消息,通過objc_msgSend()函數(shù)進(jìn)行消息發(fā)送窗慎。
給實(shí)例發(fā)送一個(gè)消息:
- 判斷receiver == nil
- 通過isa找到類
- 從類的緩存里查找IMP物喷,去類的方法表中尋找
- 通過superclass去找父類
- 通過父類去找IMP
- 循環(huán)4,5
消息轉(zhuǎn)發(fā)流程圖
1.0遮斥、方法的實(shí)現(xiàn)
* 獲取實(shí)例方法:class_getInstanceMethod(Class cls, SEL name)
* 設(shè)置類方法: class_getClassMethod(Class cls, SEL name)
* 獲取方法指針:method_getImplementation(Method m)
* 設(shè)置方法指針:method_setImplementation(Method m, IMP imp) (方法可以重新指向)
* 設(shè)置對(duì)象的類:object_setClass(id obj, Class cls)
1峦失、消息發(fā)送(方法調(diào)用的本質(zhì))
* 注冊(cè)方法:sel_registerName(const char *str),sel_registerName("new")
* 消息發(fā)送:objc_msgSend(id self, SEL op, ...)
* 創(chuàng)建一類:objc_allocateClassPair(Class superclass, const char *name, size_t extraBytes)
objc_msgSend(消息接受者术吗,消息名稱)
objc_msgSend(person, sel_registerName("walk"));
objc_msgSend(objc_getClass("TZPerson"), sel_registerName("addRun"));
Class TZCat = objc_allocateClassPair([NSObject class], "TZCat", 0);
2尉辑、動(dòng)態(tài)方法解析
- 對(duì)象在收到無法解讀的消息后:調(diào)用
+ (BOOL)resolveInstanceMethod:(SEL)sel
來動(dòng)態(tài)為其 添加實(shí)例方法
,來處理該選擇较屿。 - 如果尚未實(shí)現(xiàn)的方法是類方法:調(diào)用
+ (BOOL)resolveClassMethod:(SEL)sel
來動(dòng)態(tài)為其 添加類方法
//#pragma mark --- 實(shí)例方法 的轉(zhuǎn)換:+ (BOOL)resolveInstanceMethod:(SEL)sel
//#pragma mark --- 實(shí)例方法
+ (BOOL)resolveInstanceMethod:(SEL)sel {
NSLog(@"---resolveInstanceMethod:%s", __func__);
// 元類
// 實(shí)例對(duì)象隧魄、類對(duì)象卓练、元類對(duì)象
//例6.2.1:無方法,構(gòu)建一個(gè)方法购啄,并去調(diào)用實(shí)現(xiàn)
if (sel == @selector(walk)) {
//創(chuàng)建實(shí)例方法(向?qū)嵗l(fā)送消息襟企,在類上添加方法)
Method runMethod = class_getInstanceMethod(self, @selector(run));
//方法指針
IMP runIMP = method_getImplementation(runMethod);
//描述方法參數(shù)的類型
const char* types = method_getTypeEncoding(runMethod);
NSLog(@"---(實(shí)例方法)types:%s", types); //v16@0:8 參數(shù)字節(jié)數(shù)16 @從第幾個(gè)字節(jié)開始 :表示第8個(gè)
return class_addMethod(self, sel, runIMP, types);
}
//有方法,去調(diào)用實(shí)現(xiàn)狮含,
return [super resolveInstanceMethod:sel];
}
//#pragma mark --- 類方法 的轉(zhuǎn)換:+ (BOOL)resolveClassMethod:(SEL)sel
//#pragma mark --- 類方法
+ (BOOL)resolveClassMethod:(SEL)sel {
NSLog(@"---resolveClassMethod:%s", __func__);
//例6.2.2:(類方法)無方法顽悼,構(gòu)建一個(gè)方法,并去調(diào)用實(shí)現(xiàn)
if (sel == @selector(walk)) {
//創(chuàng)建類方法(向類發(fā)送消息几迄,在元類上添加方法)
Method runMethod = class_getInstanceMethod(object_getClass(self), @selector(run));
//方法指針
IMP runIMP = method_getImplementation(runMethod);
//描述方法參數(shù)的類型
const char* types = method_getTypeEncoding(runMethod);
NSLog(@"---(類方法)types:%s", types); //v16@0:8 參數(shù)字節(jié)數(shù)16 @從第幾個(gè)字節(jié)開始 :表示第8個(gè)
return class_addMethod(object_getClass(self), sel, runIMP, types);
}
//有方法蔚龙,去調(diào)用實(shí)現(xiàn),
return [super resolveClassMethod:sel];
}
3映胁、消息轉(zhuǎn)發(fā)
掛載到其他類的方法上木羹。
是否有消息轉(zhuǎn)發(fā):有就去調(diào)用、沒有就崩潰解孙。
既然已經(jīng)問過了汇跨,沒有新增方法,那就問問有沒有別人能夠幫忙處理一下:
\\實(shí)例方法 處理
- (id) forwardingTargetForSelector:(SEL)aSelector
- (NSMethodSignature* )methodSignatureForSelector:(SEL)aSelector
- (void)forwardInvocation:(NSInvocation *)anInvocation
\\類方法 處理
+ (id) forwardingTargetForSelector:(SEL)aSelector
+ (NSMethodSignature* )methodSignatureForSelector:(SEL)aSelector
+ (void)forwardInvocation:(NSInvocation *)anInvocation
源碼如下:
# ------------------------- (實(shí)例:消息轉(zhuǎn)發(fā)) ---------------------------
# 7.1: 實(shí)例方法(方法掛載妆距,掛載到其他類上)
# 方式一
- (id) forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(walk7)) {
return [TZDog new];
}
return [super forwardingTargetForSelector:aSelector];
}
# 方式二
# 方法名注冊(cè)
- (NSMethodSignature* )methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(walk7)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
# 消息轉(zhuǎn)發(fā):實(shí)例方法
- (void)forwardInvocation:(NSInvocation *)anInvocation {
//NSLog(@"%s", __func__);
//[anInvocation invokeWithTarget:[TZDog new]]; // 轉(zhuǎn)發(fā)給 其他
// 轉(zhuǎn)發(fā)給 自己
anInvocation.selector = @selector(run);
anInvocation.target = self;
[anInvocation invoke];
}
## ------------------------- (類:消息轉(zhuǎn)發(fā)) ---------------------------
## 7.2:類方法 消息轉(zhuǎn)發(fā)
## 方式一
+ (id) forwardingTargetForSelector:(SEL)aSelector {
if (aSelector == @selector(walk7)) {
//return [TZDog new]; //可以轉(zhuǎn)發(fā)到:類的實(shí)例方法
return [TZDog class];//可以轉(zhuǎn)發(fā)到:類方法
}
return [super forwardingTargetForSelector:aSelector];
}
## 方式二
# 方法名注冊(cè)
+ (NSMethodSignature* )methodSignatureForSelector:(SEL)aSelector {
if (aSelector == @selector(walk7)) {
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
return [super methodSignatureForSelector:aSelector];
}
# 消息轉(zhuǎn)發(fā):類方法
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
NSLog(@" 轉(zhuǎn)發(fā)給 其他= %s", __func__);
//[anInvocation invokeWithTarget:[TZDog new]]; //可以轉(zhuǎn)發(fā)到:類的實(shí)例方法
//[anInvocation invokeWithTarget:[TZDog class]]; //可以轉(zhuǎn)發(fā)到:類方法
// 轉(zhuǎn)發(fā)給 自己
anInvocation.selector = @selector(run);
anInvocation.target = self;
[anInvocation invoke];
}
# ------------------------- 結(jié)束(消息轉(zhuǎn)發(fā)) ---------------------------
三穷遂、Runtime 實(shí)現(xiàn)KVO
KVO的實(shí)現(xiàn)依賴于 Objective-C 強(qiáng)大的 Runtime,當(dāng)觀察某對(duì)象 People
時(shí)娱据,KVO 機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)對(duì)象people當(dāng)前類的子類蚪黑,并為這個(gè)新的子類重寫了被觀察屬性 keyPath
的 setter
方法。setter 方法隨后負(fù)責(zé)通知觀察對(duì)象屬性的改變狀況中剩。
Apple 使用了isa-swizzling
來實(shí)現(xiàn) KVO 忌穿。當(dāng)觀察對(duì)象people時(shí),KVO機(jī)制動(dòng)態(tài)創(chuàng)建一個(gè)新的名為:NSKVONotifying_People
的新類结啼,該類繼承自對(duì)象People
的本類掠剑,且 KVO 為 NSKVONotifying_People 重寫觀察屬性的 setter 方法,setter 方法會(huì)負(fù)責(zé)在調(diào)用原 setter 方法之前和之后郊愧,通知所有觀察對(duì)象屬性值的更改情況朴译。
NSKVONotifying_People 類剖析
NSLog(@"self->isa: %@",self->isa);
NSLog(@"self class: %@",[self class]);
# 在建立KVO監(jiān)聽前,打印結(jié)果為:
self->isa: People
self class: People
# 在建立KVO監(jiān)聽之后属铁,打印結(jié)果為:
self->isa: NSKVONotifying_People
self class: People
子類setter方法剖析
KVO 的鍵值觀察通知依賴于 NSObject 的兩個(gè)方法:
-
willChangeValueForKey:
被觀察屬性發(fā)生改變之前被調(diào)用眠寿,通知即將改變、 -
didChangeValueForKey:
被觀察屬性發(fā)生改變之后被調(diào)用焦蘑,通知已經(jīng)變更盯拱。
在存取數(shù)值的前后分別調(diào)用 這2 個(gè)方法,且重寫觀察屬性的setter 方法這種繼承方式的注入是在運(yùn)行時(shí)
而不是編譯時(shí)實(shí)現(xiàn)的。
KVO 為子類的觀察者屬性重寫調(diào)用存取方法的工作原理在代碼中相當(dāng)于:
- (void)setName:(NSString *)newName {
[self willChangeValueForKey:@"name"]; # KVO 在調(diào)用存取方法之前總調(diào)用
[super setValue:newName forKey:@"name"]; # 調(diào)用父類的存取方法
[self didChangeValueForKey:@"name"]; # KVO 在調(diào)用存取方法之后總調(diào)用
}
四狡逢、Runtime的使用
1宁舰、
關(guān)聯(lián)對(duì)象
: 給分類添加屬性,AssociatedObject(提高性能奢浑,避免不使用的 繼承類屬性 初始化)
2蛮艰、遍歷類的所有成員變量
:修改textfield的占位文字顏色、字典轉(zhuǎn)模型Model殷费、自動(dòng)歸檔解檔
3印荔、交換方法實(shí)現(xiàn)
:(交換系統(tǒng)的方法低葫,埋點(diǎn)详羡、防逆向hook)
4、利用消息轉(zhuǎn)發(fā)機(jī)制
:解決 「方法找不到」的異常問題
......
1嘿悬、實(shí)現(xiàn)NSCoding的自動(dòng)歸檔和自動(dòng)解檔
原理描述:用 Runtime
提供的函數(shù)遍歷Model
自身所有屬性实柠,并對(duì)屬性進(jìn)行encode
和decode
操作。
核心方法:在Model
的基類中重寫方法:
引用:
1善涨、NSCoding協(xié)議
2窒盐、 知識(shí)小結(jié)三:NSCoding理解
NSArchiver、NSUnarchiver钢拧、NSKeyedArchiver蟹漓、NSKeyUnarchiver和NSPortCoder。NSCoder具體的子類統(tǒng)一稱作:編碼器類源内。
協(xié)議中只有兩個(gè)方法葡粒,都是@require必須實(shí)現(xiàn)的方法
-(void)encodeWithCoder:(NSCoder *)aCoder
-(id)initWithCoder:(NSCoder *)aDecoder
# 解檔(獲取內(nèi)容數(shù)據(jù))
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[self setValue:[aDecoder decodeObjectForKey:key] forKey:key];
}
}
return self;
}
# 歸檔
- (void)encodeWithCoder:(NSCoder *)aCoder {
unsigned int outCount;
Ivar * ivars = class_copyIvarList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
Ivar ivar = ivars[i];
NSString * key = [NSString stringWithUTF8String:ivar_getName(ivar)];
[aCoder encodeObject:[self valueForKey:key] forKey:key];
}
}
2、實(shí)現(xiàn)字典和模型的自動(dòng)轉(zhuǎn)換( MJExtension )
原理描述:用Runtime
提供的函數(shù)遍歷Model
自身所有屬性膜钓,如果屬性在json
中有對(duì)應(yīng)的值嗽交,則將其賦值。
核心方法:在NSObject 的分類
中添加方法
- (instancetype)initWithDict:(NSDictionary *)dict {
if (self = [self init]) {
#//(1)獲取類的屬性及屬性對(duì)應(yīng)的類型
NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * attributes = [NSMutableArray array];
/** 例子
* name = value3 attribute = T@"NSString",C,N,V_value3
* name = value4 attribute = T^i,N,V_value4
*/
unsigned int outCount;
objc_property_t * properties = class_copyPropertyList([self class], &outCount);
for (int i = 0; i < outCount; i ++) {
objc_property_t property = properties[i];
#// 通過property_getName函數(shù)獲得屬性的名字
NSString * propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
[keys addObject:propertyName];
#// 通過property_getAttributes函數(shù)可以獲得屬性的名字和@encode編碼
NSString * propertyAttribute = [NSString stringWithCString:property_getAttributes(property) encoding:NSUTF8StringEncoding];
[attributes addObject:propertyAttribute];
}
#// 立即釋放properties指向的內(nèi)存
free(properties);
#//(2)根據(jù)類型給屬性賦值
for (NSString * key in keys) {
if ([dict valueForKey:key] == nil) continue;
[self setValue:[dict valueForKey:key] forKey:key];
}
}
return self;
}