開(kāi)始本節(jié)內(nèi)容之前需要對(duì)了解WWDC2020中對(duì)runtime的改動(dòng)唱较,主要是對(duì)clean memory
和dirty memory
以及ro
、rw
有個(gè)初步的了解召川。
實(shí)際上內(nèi)存頁(yè)有分類南缓,一般來(lái)說(shuō)分為 clean memory
和 dirty memory
兩種,iOS
中也有 compressed memory
的概念荧呐。
Clean memory & dirty memory
對(duì)于一般的桌面操作系統(tǒng)汉形,clean memory
可以認(rèn)為是能夠進(jìn)行 Page Out
的部分。Page Out
指的是將優(yōu)先級(jí)低的內(nèi)存數(shù)據(jù)交換到磁盤上的操作倍阐,但iOS
并沒(méi)有內(nèi)存交換機(jī)制概疆,所以對(duì) iOS
這樣的定義是不嚴(yán)謹(jǐn)?shù)摹D敲磳?duì)于 iOS
來(lái)說(shuō)峰搪,clean memory
指的是能被重新創(chuàng)建的內(nèi)存岔冀,它主要包含下面幾類:
-
app
的二進(jìn)制可執(zhí)行文件 -
framework
中的_DATA_CONST
段 - 文件映射的內(nèi)存
- 未寫入數(shù)據(jù)的內(nèi)存
內(nèi)存映射的文件指的是當(dāng) app
訪問(wèn)一個(gè)文件時(shí),系統(tǒng)會(huì)將文件映射加載到內(nèi)存中罢艾,如果文件只讀
楣颠,那么這部分內(nèi)存就屬于 clean memory
尽纽。另外需要注意的是,鏈接的 framework
中 _DATA_CONST
并不絕對(duì)屬于 clean memory
童漩,當(dāng)app
使用到 framework
時(shí)弄贿,就會(huì)變成 dirty memory
。
未寫入數(shù)據(jù)的內(nèi)存也屬于 clean memory
矫膨,比如下面這段代碼差凹,只有寫入了的部分才屬于 dirty memory
。
int *array = malloc(20000 * sizeof(int));
array[0] = 32
array[19999] = 64
所有不屬于
clean memory
的內(nèi)存都是 dirty memory
侧馅。這部分內(nèi)存并不能被系統(tǒng)重新創(chuàng)建
危尿,所以 dirty memory
會(huì)始終占據(jù)物理內(nèi)存
,直到物理內(nèi)存
不夠用之后馁痴,系統(tǒng)便會(huì)開(kāi)始清理
谊娇。Compressed memory
當(dāng)物理內(nèi)存不夠用時(shí),iOS
會(huì)將部分物理內(nèi)存壓縮
罗晕,在需要讀寫時(shí)再解壓
济欢,以達(dá)到節(jié)約內(nèi)存
的目的。而壓縮之后的內(nèi)存小渊,就是所謂的 compressed memory
法褥。蘋果最開(kāi)始只是在 OS X
上使用這項(xiàng)技術(shù),后來(lái)也在 iOS 系統(tǒng)
上使用酬屉。
摘自:《iOS Memory 內(nèi)存詳解 》這篇文章對(duì)iOS的內(nèi)存進(jìn)行了詳細(xì)解讀半等,感興趣的可以去看下
WWDC2020中對(duì)runtime的改動(dòng)
接下來(lái)結(jié)合WWDC2020中對(duì)runtime的改動(dòng)探究類的數(shù)據(jù)結(jié)構(gòu),
在磁盤上app二進(jìn)制文件中類是這樣的
首先這有個(gè)類對(duì)象本身它包含了
最常被訪問(wèn)的信息
指向元類
呐萨、超類
和方法緩存指針
它還有一個(gè)指向更多數(shù)據(jù)的指針杀饵,存儲(chǔ)額外信息的地方叫做
class_ro_t
,'RO'代表只讀垛吗,它包含了類名
凹髓、方法
、協(xié)議
和實(shí)例變量
的信息怯屉,Swift
類和Objective-C
類共享
這一基礎(chǔ)結(jié)構(gòu)蔚舀,所以每個(gè)Swift類也有這些數(shù)據(jù)結(jié)構(gòu)。當(dāng)類第一次從磁盤加載到內(nèi)存中時(shí)锨络,它一開(kāi)始也是這樣的但是
一經(jīng)使用
它們就會(huì)發(fā)生變化
赌躺,這里就要結(jié)合clean memory
和 dirty memory
來(lái)看,clean memory
是指加載后不會(huì)發(fā)生變更的內(nèi)存class_ro_t
就屬于clean memory
因?yàn)樗侵蛔x的羡儿,dirty memory
是指在進(jìn)程運(yùn)行時(shí)會(huì)發(fā)生變化的內(nèi)存礼患,類結(jié)構(gòu)一經(jīng)使用就會(huì)變成 dirty memory
因?yàn)?code>Runtime會(huì)向它寫入新的數(shù)據(jù),例如 創(chuàng)建一個(gè)新的方法緩存
并從類中指向它,dirty memory
比 clean memory
要昂貴得多缅叠,只要進(jìn)程在運(yùn)行 它就必須一直存在 另一方面 clean memory
可以進(jìn)行移除從而節(jié)省更多的內(nèi)存空間悄泥,因?yàn)槿绻阈枰?code>clean memory系統(tǒng)可以從磁盤中重新加載
,macOS
可以選擇緩沖 dirty memory
但是iOS不能使用swap(內(nèi)存交換機(jī)制iOS不支持)
,所以dirty memory
在iOS中代價(jià)很大肤粱,dirty memory
是這個(gè)類被分成兩部分的原因弹囚,可以保持清潔的數(shù)據(jù)越多越好,通過(guò)分離出那些永遠(yuǎn)不會(huì)改變的數(shù)據(jù)可以把大部分?jǐn)?shù)據(jù)存儲(chǔ)為clean memory
领曼。雖然有了這些數(shù)據(jù)足以讓我們開(kāi)始鸥鹉,但是運(yùn)行時(shí)需要追蹤每個(gè)類的更多信息,所以當(dāng)一個(gè)類首次被使用運(yùn)行時(shí)會(huì)為它分配額外的容量庶骄,這個(gè)運(yùn)行時(shí)分配的容量是class_rw_t
用于讀取-編寫數(shù)據(jù)毁渗,在這個(gè)數(shù)據(jù)結(jié)構(gòu)中儲(chǔ)存了只有運(yùn)行時(shí)才會(huì)生成的新信息。例如 所有的類都會(huì)鏈接成一個(gè)樹(shù)狀結(jié)構(gòu)這是通過(guò)
First Subclass
和 Next Sibling Class
指針實(shí)現(xiàn)的单刁,這允許運(yùn)行時(shí)遍歷當(dāng)前使用的所有類這對(duì)于使方法緩存無(wú)效非常有用灸异。但是為什么方法和屬性也在只讀數(shù)據(jù)中時(shí),這里還要有方法和屬性呢幻碱?因?yàn)樗麄兛梢栽谶\(yùn)行時(shí)進(jìn)行改變绎狭,當(dāng)
category
被加載時(shí)它可以向類中添加新方法细溅,而且程序員可以使用運(yùn)行時(shí)的API
動(dòng)態(tài)的添加它們褥傍,因?yàn)?class_ro_t
是只讀的,所以需要在class_rw_t
中追蹤這些東西喇聊,現(xiàn)在結(jié)果是這樣做會(huì)占用相當(dāng)多的內(nèi)存恍风,在任何給定的設(shè)備中都有許多類在使用,Ben(蘋果工程師的名字)
在iPhone
上的整個(gè)系統(tǒng)中測(cè)量了大約30兆字節(jié)這些 class_rw_t
結(jié)構(gòu)誓篱,那么如何縮小這些結(jié)構(gòu)呢朋贬?記住我們需要這些東西存在read/write區(qū)
因?yàn)檫\(yùn)行時(shí)它們可能會(huì)被改變,但是通過(guò)檢查實(shí)際設(shè)備上的使用情況發(fā)現(xiàn)大約只有10%
的類真正地更改了它們的方法窜骄。而且
demangled name
這個(gè)字段只有Swift
類才可能使用到锦募,而且只有當(dāng)有東西訪問(wèn)它們的Objective-C名稱
時(shí)才會(huì)用到,所以可以拆掉那些平時(shí)不用的部分這將
class_rw_t
的大小減少了一半邻遏,對(duì)于那些確實(shí)需要額外信息的類糠亩,我們可以分配這些擴(kuò)展中的一個(gè)并把它滑到類中供其使用大約有90%
的類從來(lái)不需要這些擴(kuò)展數(shù)據(jù),這在系統(tǒng)范圍中可節(jié)省大約14MB的內(nèi)存准验。
實(shí)際上可以在Mac
上看到這一變化帶來(lái)的影響赎线,這只需要在終端上執(zhí)行一個(gè)命令,下面我們看下郵件
這個(gè)程序的情況
heap Mail | egrep 'class_rw|COUNT'
可以看到
郵件app
中使用了大約6000
個(gè)這樣的class_rw_t
類型,但是其中只有大約十分之一 600
多一點(diǎn)實(shí)際上需要使用這一擴(kuò)展信息糊饱,所以通過(guò)這個(gè)改變節(jié)省了很多內(nèi)存垂寥。現(xiàn)在很多從類中讀取數(shù)據(jù)的代碼都必須同時(shí)處理那些有擴(kuò)展數(shù)據(jù)和沒(méi)有擴(kuò)展數(shù)據(jù)的類當(dāng)然運(yùn)行時(shí)會(huì)為我們處理這一切,并且從外部看 一切都像往常一個(gè)工作,只是使用了更少的內(nèi)存滞项。
了解完這些之后我們就可以繼續(xù)類的探索了,這里有個(gè)小插曲狭归,不知道你有沒(méi)有注意到《iOS底層原理探究05-類的底層原理isa鏈&繼承鏈&類的內(nèi)存結(jié)構(gòu)》里
firstSubclass
為nil但是LGPerson
明明是有子類的LGTeacher
的
這是為啥呢?其實(shí)原因很簡(jiǎn)單文判,因?yàn)長(zhǎng)GTeacher類是懶加載的唉铜,當(dāng)前還沒(méi)加載
我們p一下
LGTeacher
之后,在輸出LGPerson
的數(shù)據(jù)firstSubclass
已經(jīng)被賦值了下面繼續(xù)探索類的數(shù)據(jù)結(jié)構(gòu)律杠,通過(guò)上面對(duì)WWDC2020的分析我們知道類的成員變量存在
ro
結(jié)構(gòu)中潭流,我們到ro
里看下能不能找到- 1.獲取class的data數(shù)據(jù)
- 2.獲取
ro
數(shù)據(jù) - 3.獲取
ro
中的ivars成員變量
數(shù)據(jù) - 4.看下成員變量輸出的信息
-
name = 0x0000000100003d29 "subject"
成員變量名稱 -
type = 0x0000000100003eaa "@\"NSString\""
成員變量類型 -
size = 8
成員變量大小
-
有些同學(xué)可能會(huì)疑惑subject
是哪兒冒出來(lái)的,它是LGPerson
的一個(gè)承運(yùn)變量
我們?cè)俣嗵砑訋讉€(gè)成員變量柜去,研究一下
@interface LGPerson : NSObject<NSCoding,NSObject>{
NSString *subject;
int age;
double height;
NSObject * face;
}
前面的流程是一致的灰嫉,后面的type里出現(xiàn)的
i
,d
,@
是啥意思,這些其實(shí)是類型編碼∩ど荩可以看下表對(duì)照也可以直接去官網(wǎng)看除了存儲(chǔ)的地方不一樣
屬性
和成員變量
還有沒(méi)有其他的區(qū)別呢讼撒,下面就來(lái)探索一下,通過(guò)xcrun編譯生成C/C++源碼看看在源碼層面(xcrun的介紹和使用)股耽,他們有什么區(qū)別下面我們把這個(gè)類編譯一下看下底層有什么區(qū)別
//編譯前
// 成員變量 vs 屬性 VS 實(shí)例變量
@interface LGPerson : NSObject
{
NSString *hobby; // 字符串
int a;
NSObject *objc; // 結(jié)構(gòu)體
}
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
@end
//編譯后
...省略無(wú)關(guān)代碼
#ifndef _REWRITER_typedef_LGPerson
#define _REWRITER_typedef_LGPerson
typedef struct objc_object LGPerson;
typedef struct {} _objc_exc_LGPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_nickName;
extern "C" unsigned long OBJC_IVAR_$_LGPerson$_name;
struct LGPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *hobby;
int a;
NSObject *objc;
NSString *_nickName;
NSString *_name;
};
// @property (nonatomic, copy) NSString *nickName;
// @property (nonatomic, strong) NSString *name;
/* @end */
// @implementation LGPerson
static NSString * _I_LGPerson_nickName(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_nickName)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }
static NSString * _I_LGPerson_name(LGPerson * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)); }
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
// @end
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[8];
} _OBJC_$_INSTANCE_METHODS_LGPerson __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
8,
{{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_},
{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
{(struct objc_selector *)"setNickName:", "v24@0:8@16", (void *)_I_LGPerson_setNickName_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGPerson_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGPerson_setName_}}
};
...省略無(wú)關(guān)代碼
- 底層
屬性
會(huì)被添加下劃線
如_name
變成一個(gè)以下劃線開(kāi)頭的成員變量 - 系統(tǒng)自動(dòng)給
屬性
生成了getter
根盒、setter
方法 -
屬性
=承運(yùn)變量
+getter
+setter
- 最下面方法列表中有的東西還需要看一下例如
{{(struct objc_selector *)"nickName", "@16@0:8", (void *)_I_LGPerson_nickName},
,前面的nickName
很明顯是方法名也就是nickName
的getter
的方法名物蝙,但是后面的@16@0:8
這個(gè)怎么理解呢炎滞?- "@16@0:8"解析
- 1:@ 表示返回值的類型編碼即返回值是
id
類型 - 2:16 表示整個(gè)這一串所占用的內(nèi)存
- 3:@ 表示第一個(gè)參數(shù)為id類型
- 4: 0 表示從0號(hào)位置開(kāi)始
- 5::表示第二個(gè)參數(shù)類型SEL
- 6:8表示從8號(hào)位置開(kāi)始
- 1:@ 表示返回值的類型編碼即返回值是
- "@16@0:8"解析
這樣ivar和property的區(qū)別就比較清晰了,但是有一個(gè)細(xì)節(jié)不知道你注意到了沒(méi)nickName
的setter
方法
static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }
name
的setter
方法
static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
-
name
的setter
方法使用內(nèi)存偏移賦值 -
nickName
的setter
方法則是直接調(diào)用objc_setProperty
這兩個(gè)屬性在OC
中我們聲明的時(shí)候有什么區(qū)別呢册赛?
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, strong) NSString *name;
nickName
使用的是copy
,而name
是用的strong
是否是因?yàn)檫@個(gè)導(dǎo)致底層setter
發(fā)生變化的呢森瘪。接下來(lái)我們?cè)賮?lái)研究下,因?yàn)?code>屬性的getter 和 setter
方法都是編譯器自動(dòng)生成的窗宇,所以我們到llvm
源碼中看看能不能找到答案。
關(guān)于llvm我們后面再補(bǔ)充(我網(wǎng)速有點(diǎn)慢蝇完,源碼還沒(méi)下下來(lái)??)
objc_setProperty
做了啥氢架,objc_setProperty
中最終都是調(diào)用reallySetProperty
來(lái)看下
reallySetProperty
的源碼reallySetProperty
實(shí)質(zhì)是深拷貝
的流程,會(huì)重新生成一個(gè)字符串對(duì)象并指向這個(gè)新的字符串孙援,直接地址偏移賦值直接改原來(lái)的對(duì)象并沒(méi)有這個(gè)深拷貝
的操作
解決上一篇遺留的問(wèn)題
在《iOS底層原理探究05》中我們留下了一個(gè)問(wèn)題拓售,類方法
存在哪里础淤?下面我來(lái)把這個(gè)坑填上
其實(shí)類方法
是存在元類
中的,我直接這么說(shuō)你肯定不信鸽凶,那么下面我們就來(lái)驗(yàn)證一下,上代碼
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject<NSCoding,NSObject>
- (void)sayNB;
+ (void)say666;
@end
@implementation LGPerson
- (void)sayNB{
}
+ (void)say666{
}
實(shí)例方法sayNB
是在LGPerson類
里保存的這個(gè)在《iOS底層原理探究05》里已經(jīng)驗(yàn)證過(guò)了,接下來(lái)就看看say666
方法是不是在LGPerson 元類
中
控制臺(tái)輸出是這樣的接下來(lái)我添加上備注信息你就明白了
(lldb) x/4gx LGPerson.class //打印LGPerson類的內(nèi)存結(jié)構(gòu)
0x100008778: 0x00000001000087a0 0x000000010036c140
0x100008788: 0x0000000100649dd0 0x0002a04000000003
(lldb) p/x 0x00000001000087a0 & 0x00007ffffffffff8ULL //LGPerson類的isa & ISA_MASK獲取到LGPerson元類
(unsigned long long) $1 = 0x00000001000087a0
(lldb) po 0x00000001000087a0
LGPerson //驗(yàn)證一下 獲取到的確實(shí)是LGPerson元類
(lldb) p/x 0x00000001000087a0+0x20 //內(nèi)存偏移找到元類bits數(shù)據(jù)結(jié)構(gòu) 這在上一篇中講到過(guò)
(long) $3 = 0x00000001000087c0
(lldb) p (class_data_bits_t *)0x00000001000087c0 // 告訴lldb bits是class_data_bits_t類型的
(class_data_bits_t *) $4 = 0x00000001000087c0
(lldb) p $4->data() //取出元類LGPerson的data
(class_rw_t *) $5 = 0x0000000100612780
(lldb) p *$5 //查看data的數(shù)據(jù)結(jié)構(gòu)
(class_rw_t) $6 = {
flags = 2684878849
witness = 1
ro_or_rw_ext = {
std::__1::atomic<unsigned long> = {
Value = 4301561681
}
}
firstSubclass = nil
nextSiblingClass = 0x00007fff800fcec0
}
(lldb) p $6->methods() //獲取到元類LGPerson的方法列表
(const method_array_t) $7 = {
list_array_tt<method_t, method_list_t, method_list_t_authed_ptr> = {
= {
list = {
ptr = 0x00000001000086b0
}
arrayAndFlag = 4295001776
}
}
}
Fix-it applied, fixed expression was:
$6.methods()
(lldb) p $7.list //取list
(const method_list_t_authed_ptr<method_list_t>) $8 = {
ptr = 0x00000001000086b0
}
(lldb) p $8.ptr //取prt
(method_list_t *const) $9 = 0x00000001000086b0
(lldb) p *$9 //查看method_list_t的數(shù)據(jù)結(jié)構(gòu)
(method_list_t) $10 = {
entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 1)
}
(lldb) p $10.get(0).big() //取方法列表中的第一個(gè)方法 獲取到了 say666方法
(method_t::big) $11 = {
name = "say666"
types = 0x0000000100003ea2 "v16@0:8"
imp = 0x0000000100003ba0 (KCObjcBuild`+[LGPerson say666])
}
(lldb)
添加完注釋流程還是蠻清晰的,驗(yàn)證了類方法
確實(shí)存在元類
中
結(jié)論
實(shí)例方法
保存在類
的methods
數(shù)據(jù)結(jié)構(gòu)里灰蛙,類方法
以實(shí)例方法
的形式保存在元類
中,本質(zhì)上不存在類方法
,類方法
是存在元類
中的實(shí)例方法仅父,底層都是函數(shù)
。
使用Runtime API 驗(yàn)證方法存儲(chǔ)位置
接下來(lái)我們使用runtime
的API
嘗試打印LGPerson
類的保存的所有方法
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
{
NSObject *objc;
NSString *nickName;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) NSObject *obj;
- (void)sayHello;
+ (void)sayHappy;
@end
NS_ASSUME_NONNULL_END
下面是打印方法的代碼
void lgObjc_copyMethodList(Class pClass){
unsigned int count = 0;
Method *methods = class_copyMethodList(pClass, &count);
for (unsigned int i=0; i < count; i++) {
Method const method = methods[I];
//獲取方法名
NSString *key = NSStringFromSelector(method_getName(method));
LGLog(@"Method, name: %@", key);
}
free(methods);
}
運(yùn)行結(jié)果
打印的結(jié)果中
sayHappy
并不在其中笙纤,因?yàn)槲覀冎浪?code>類方法保存在元類
中輸出
元類
的方法列表打印出了sayHappy
- (void)sayHello;
+ (void)sayHappy;
我們用這三個(gè)方法檢驗(yàn)一下
第一個(gè)方法驗(yàn)證sayHello
和sayHappy
是否是類
和元類
的實(shí)例方法
第一個(gè)方法驗(yàn)證sayHello
和sayHappy
是否是類
和元類
的類方法
第一個(gè)方法驗(yàn)證sayHello
和sayHappy
是否在類
和元類
中有IMP
實(shí)現(xiàn)
void lgInstanceMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getInstanceMethod(pClass, @selector(sayHello));
Method method2 = class_getInstanceMethod(metaClass, @selector(sayHello));
Method method3 = class_getInstanceMethod(pClass, @selector(sayHappy));
Method method4 = class_getInstanceMethod(metaClass, @selector(sayHappy));
LGLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgClassMethod_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
Method method1 = class_getClassMethod(pClass, @selector(sayHello));
Method method2 = class_getClassMethod(metaClass, @selector(sayHello));
// - (void)sayHello;
// + (void)sayHappy;
Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
Method method4 = class_getClassMethod(metaClass, @selector(sayHappy));
LGLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}
void lgIMP_classToMetaclass(Class pClass){
const char *className = class_getName(pClass);
Class metaClass = objc_getMetaClass(className);
IMP imp1 = class_getMethodImplementation(pClass, @selector(sayHello));
IMP imp2 = class_getMethodImplementation(metaClass, @selector(sayHello));// 0
// sel -> imp 方法的查找流程 imp_farw
IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy)); // 0
IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));
NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
NSLog(@"%s",__func__);
}
-
lgInstanceMethod_classToMetaclass
輸出為0x1000081c0-0x0-0x0-0x100008158
,-(void)sayHello
是類的實(shí)例方法
旭贬,+(void)sayHappy
不是元類的實(shí)例方法
死讹,+(void)sayHappy
不是類的實(shí)例方法
沮趣,+(void)sayHappy
是元類的實(shí)例方法
肚吏。 -
lgClassMethod_classToMetaclass
輸出為0x0-0x0-0x100008158-0x100008158
,-(void)sayHello
不是類的類方法
橡类,-(void)sayHello
不是元類的類方法
笼蛛,+(void)sayHappy
是類的類方法
拉馋,+(void)sayHappy
是元類的類方法
煌茴。 -
lgIMP_classToMetaclass
輸出為0x100003ac0-0x7fff202295c0-0x7fff202295c0-0x100003b00
,-(void)sayHello
在類中能找到IMP實(shí)現(xiàn),-(void)sayHello
在元類中能找到IMP實(shí)現(xiàn)龄句,+(void)sayHappy
在類中能找到IMP實(shí)現(xiàn)回论,+(void)sayHappy
在元類中能找到IMP實(shí)現(xiàn)。
分析:第一點(diǎn)毋庸置疑跟我們之前的結(jié)論是一致的分歇,但是第二點(diǎn)有點(diǎn)問(wèn)題class_getClassMethod(metaClass, @selector(sayHello))
有結(jié)果輸出傀蓉,+(void)sayHappy
怎么是元類
的類方法
呢,不是說(shuō)類方法
以實(shí)例方法
的形式存在元類
中接下來(lái)我們來(lái)看下class_getClassMethod
的源碼
/***********************************************************************
* class_getClassMethod. Return the class method for the specified
* class and selector.
**********************************************************************/
Method class_getClassMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
return class_getInstanceMethod(cls->getMeta(), sel);
}
可以看到源碼的實(shí)現(xiàn)
跟我們之前得到的結(jié)論
是一致的职抡,獲取類方法
的實(shí)現(xiàn)就是去元類里找同名的實(shí)例方法
葬燎,那我們現(xiàn)在傳進(jìn)來(lái)的就是元類,通過(guò)cls->getMeta()
獲取元類又會(huì)得到什么呢缚甩,我們來(lái)看下cls->getMeta()
的源碼
// NOT identical to this->ISA when this is a metaclass
Class getMeta() {
if (isMetaClassMaybeUnrealized()) return (Class)this;
else return this->ISA();
}
如果當(dāng)前傳入的類
是元類
會(huì)直接返回當(dāng)前類
谱净,如果不是元類
返回當(dāng)前類的isa
,所以class_getClassMethod(metaClass, @selector(sayHello)),其實(shí)底層是找metaClass
的實(shí)例方法
,當(dāng)然可以找到擅威。
第三點(diǎn)-(void)sayHello
在類中能找到IMP實(shí)現(xiàn)和+(void)sayHappy
在元類中能找到IMP實(shí)現(xiàn)這兩個(gè)沒(méi)啥問(wèn)題 但是 -(void)sayHello
在元類中能找到IMP實(shí)現(xiàn)和+(void)sayHappy
在類中能找到IMP實(shí)現(xiàn)這兩個(gè)就不正常了壕探,而且不知道你注意到?jīng)]他們兩個(gè)的IMP實(shí)現(xiàn)地址是一樣的
這是什么原因呢?
我們?cè)诳聪?code>class_getMethodImplementation的
源碼
__attribute__((flatten))
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
lockdebug_assert_no_locks_locked_except({ &loadMethodLock });
imp = lookUpImpOrNilTryCache(nil, sel, cls, LOOKUP_INITIALIZE | LOOKUP_RESOLVER);
// Translate forwarding function to C-callable external version
if (!imp) {
return _objc_msgForward;
}
return imp;
}
查找IMP
的過(guò)程就是通過(guò)SEL
查找IMP
的過(guò)程郊丛,也就是我們常說(shuō)的方法查找流程
李请,不知道你有沒(méi)有注意到這一句代碼
if (!imp) {
return _objc_msgForward;
}
如果找不到SEL
對(duì)應(yīng)的IMP
就返回_objc_msgForward
,這是個(gè)啥玩意兒呢瞧筛?歡迎來(lái)到下一部分方法查找流程
。