iOS底層原理探究06-類的底層原理下

開(kāi)始本節(jié)內(nèi)容之前需要對(duì)了解WWDC2020中對(duì)runtime的改動(dòng)唱较,主要是對(duì)clean memorydirty memory以及rorw有個(gè)初步的了解召川。
實(shí)際上內(nèi)存頁(yè)有分類南缓,一般來(lái)說(shuō)分為 clean memorydirty 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

image.png

所有不屬于 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)制文件中類是這樣的

image.png

首先這有個(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)。
image.png

當(dāng)類第一次從磁盤加載到內(nèi)存中時(shí)锨络,它一開(kāi)始也是這樣的但是一經(jīng)使用 它們就會(huì)發(fā)生變化赌躺,這里就要結(jié)合clean memorydirty 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 memoryclean 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ì)生成的新信息。
image.png

例如 所有的類都會(huì)鏈接成一個(gè)樹(shù)狀結(jié)構(gòu)這是通過(guò)First SubclassNext Sibling Class 指針實(shí)現(xiàn)的单刁,這允許運(yùn)行時(shí)遍歷當(dāng)前使用的所有類這對(duì)于使方法緩存無(wú)效非常有用灸异。
image.png

但是為什么方法和屬性也在只讀數(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%的類真正地更改了它們的方法窜骄。
image.png

而且demangled name 這個(gè)字段只有Swift類才可能使用到锦募,而且只有當(dāng)有東西訪問(wèn)它們的Objective-C名稱時(shí)才會(huì)用到,所以可以拆掉那些平時(shí)不用的部分
image.png

這將class_rw_t的大小減少了一半邻遏,對(duì)于那些確實(shí)需要額外信息的類糠亩,我們可以分配這些擴(kuò)展中的一個(gè)并把它滑到類中供其使用
image.png

大約有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'

image.png

可以看到郵件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)存滞项。

摘自《WWDC 2020 Advancements in the Objective-C runtime》

了解完這些之后我們就可以繼續(xù)類的探索了,這里有個(gè)小插曲狭归,不知道你有沒(méi)有注意到《iOS底層原理探究05-類的底層原理isa鏈&繼承鏈&類的內(nèi)存結(jié)構(gòu)》

image.png

firstSubclass為nil但是LGPerson明明是有子類的LGTeacher

image.png

這是為啥呢?其實(shí)原因很簡(jiǎn)單文判,因?yàn)長(zhǎng)GTeacher類是懶加載的唉铜,當(dāng)前還沒(méi)加載
image.png

我們p一下LGTeacher之后,在輸出LGPerson的數(shù)據(jù)firstSubclass已經(jīng)被賦值了
下面繼續(xù)探索類的數(shù)據(jù)結(jié)構(gòu)律杠,通過(guò)上面對(duì)WWDC2020的分析我們知道類的成員變量存在ro結(jié)構(gòu)中潭流,我們到ro里看下能不能找到
image.png

  • 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)變量

image.png

我們?cè)俣嗵砑訋讉€(gè)成員變量柜去,研究一下

@interface LGPerson : NSObject<NSCoding,NSObject>{
    NSString *subject;
    int age;
    double height;
    NSObject * face;
}

image.png

前面的流程是一致的灰嫉,后面的type里出現(xiàn)的i,d,@是啥意思,這些其實(shí)是類型編碼∩ど荩可以看下表對(duì)照也可以直接去官網(wǎng)
Type Encodings

除了存儲(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很明顯是方法名也就是nickNamegetter的方法名物蝙,但是后面的@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)始

這樣ivar和property的區(qū)別就比較清晰了,但是有一個(gè)細(xì)節(jié)不知道你注意到了沒(méi)nickNamesetter方法

static void _I_LGPerson_setNickName_(LGPerson * self, SEL _cmd, NSString *nickName) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct LGPerson, _nickName), (id)nickName, 0, 1); }

namesetter方法

static void _I_LGPerson_setName_(LGPerson * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_LGPerson$_name)) = name; }
  • namesetter方法使用內(nèi)存偏移賦值
  • nickNamesetter方法則是直接調(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

image.png

來(lái)看下reallySetProperty的源碼
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 元類

image.png

控制臺(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)我們使用runtimeAPI嘗試打印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é)果

image.png

打印的結(jié)果中sayHappy并不在其中笙纤,因?yàn)槲覀冎浪?code>類方法保存在元類
image.png

輸出元類的方法列表打印出了sayHappy

- (void)sayHello;
+ (void)sayHappy;

我們用這三個(gè)方法檢驗(yàn)一下
第一個(gè)方法驗(yàn)證sayHellosayHappy是否是元類實(shí)例方法
第一個(gè)方法驗(yàn)證sayHellosayHappy是否是元類類方法
第一個(gè)方法驗(yàn)證sayHellosayHappy是否在元類中有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__);

}
image.png
  • 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)地址是一樣的

image.png

這是什么原因呢?
我們?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)到下一部分方法查找流程

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捻艳,一起剝皮案震驚了整個(gè)濱河市驾窟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌认轨,老刑警劉巖绅络,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異嘁字,居然都是意外死亡恩急,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門纪蜒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)衷恭,“玉大人,你說(shuō)我怎么就攤上這事纯续∷嬷椋” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵猬错,是天一觀的道長(zhǎng)窗看。 經(jīng)常有香客問(wèn)我,道長(zhǎng)倦炒,這世上最難降的妖魔是什么显沈? 我笑而不...
    開(kāi)封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮逢唤,結(jié)果婚禮上拉讯,老公的妹妹穿的比我還像新娘。我一直安慰自己鳖藕,他們只是感情好魔慷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著吊奢,像睡著了一般盖彭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上页滚,一...
    開(kāi)封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音铺呵,去河邊找鬼裹驰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛片挂,可吹牛的內(nèi)容都是我干的幻林。 我是一名探鬼主播贞盯,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼沪饺!你這毒婦竟也來(lái)了躏敢?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤整葡,失蹤者是張志新(化名)和其女友劉穎件余,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體遭居,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡啼器,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了俱萍。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片端壳。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖枪蘑,靈堂內(nèi)的尸體忽然破棺而出损谦,到底是詐尸還是另有隱情,我是刑警寧澤岳颇,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布照捡,位于F島的核電站,受9級(jí)特大地震影響赦役,放射性物質(zhì)發(fā)生泄漏麻敌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一掂摔、第九天 我趴在偏房一處隱蔽的房頂上張望术羔。 院中可真熱鬧,春花似錦乙漓、人聲如沸级历。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寥殖。三九已至,卻和暖如春涩蜘,著一層夾襖步出監(jiān)牢的瞬間嚼贡,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工同诫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留粤策,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓误窖,卻偏偏與公主長(zhǎng)得像叮盘,于是被迫代替她去往敵國(guó)和親秩贰。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容