Runtime術(shù)語

曾經(jīng)覺得Objc特別方便上手诵闭,面對(duì)著 Cocoa 中大量 API顷霹,只知道簡(jiǎn)單的查文檔和調(diào)用咪惠。還記得初學(xué) Objective-C 時(shí)把[receiver message]當(dāng)成簡(jiǎn)單的方法調(diào)用,而無視了“發(fā)送消息”這句話的深刻含義淋淀。其實(shí)[receiver message]會(huì)被編譯器轉(zhuǎn)化為:

objc_msgSend(receiver, selector)

如果消息含有參數(shù)遥昧,則為:

objc_msgSend(receiver, selector, arg1, arg2, ...)

如果消息的接收者能夠找到對(duì)應(yīng)的selector,那么就相當(dāng)于直接執(zhí)行了接收者這個(gè)對(duì)象的特定方法绅喉;否則渠鸽,消息要么被轉(zhuǎn)發(fā),或是臨時(shí)向接收者動(dòng)態(tài)添加這個(gè)selector對(duì)應(yīng)的實(shí)現(xiàn)內(nèi)容柴罐,要么就干脆玩完崩潰掉徽缚。

現(xiàn)在可以看出[receiver message]真的不是一個(gè)簡(jiǎn)簡(jiǎn)單單的方法調(diào)用。因?yàn)檫@只是在編譯階段確定了要向接收者發(fā)送message這條消息革屠,而receive將要如何響應(yīng)這條消息凿试,那就要看運(yùn)行時(shí)發(fā)生的情況來決定了。

Objective-C 的 Runtime 鑄就了它動(dòng)態(tài)語言的特性似芝,這些深層次的知識(shí)雖然平時(shí)寫代碼用的少一些那婉,但是卻是每個(gè) Objc 程序員需要了解的。

SEL

objc_msgSend函數(shù)第二個(gè)參數(shù)類型為SEL,它是selector在Objc中的表示類型(Swift中是Selector類)。selector是方法選擇器趴腋,可以理解為區(qū)分方法的 ID俏让,而這個(gè) ID 的數(shù)據(jù)結(jié)構(gòu)是SEL:

typedef struct objc_selector *SEL;

其實(shí)它就是個(gè)映射到方法的C字符串,你可以用 Objc 編譯器命令@selector()或者 Runtime 系統(tǒng)的sel_registerName函數(shù)來獲得一個(gè)SEL類型的方法選擇器市怎。不同類中相同名字的方法所對(duì)應(yīng)的方法選擇器是相同的鸯檬,即使方法名字相同而變量類型不同也會(huì)導(dǎo)致它們具有相同的方法選擇器伶选,于是 Objc 中方法命名有時(shí)會(huì)帶上參數(shù)類型

id

objc_msgSend第一個(gè)參數(shù)類型為id隐岛,大家對(duì)它都不陌生猫妙,它是一個(gè)指向類實(shí)例的指針:

typedef struct objc_object *id;

那objc_object又是啥呢:

struct objc_object { Class isa; };

objc_object結(jié)構(gòu)體包含一個(gè)isa指針,根據(jù)isa指針就可以順藤摸瓜找到對(duì)象所屬的類聚凹。

PS:isa指針不總是指向?qū)嵗龑?duì)象所屬的類割坠,不能依靠它來確定類型,而是應(yīng)該用class方法來確定實(shí)例對(duì)象的類妒牙。因?yàn)?code>KVO的實(shí)現(xiàn)機(jī)理就是將被觀察對(duì)象的isa指針指向一個(gè)中間類而不是真實(shí)的類彼哼,這是一種叫做 isa-swizzling的技術(shù),詳見<a >官方文檔</a>

Class

之所以說isa是指針是因?yàn)镃lass其實(shí)是一個(gè)指向objc_class結(jié)構(gòu)體的指針:

typedef struct objc_class *Class;```
而`objc_class`就是我們摸到的那個(gè)瓜单旁,里面的東西多著呢:
```objectivec
struct objc_class { 
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__ Class super_class                OBJC2_UNAVAILABLE; 
const char *name                                OBJC2_UNAVAILABLE; 
long version                                    OBJC2_UNAVAILABLE; 
long info                                       OBJC2_UNAVAILABLE; 
long instance_size                              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            OBJC2_UNAVAILABLE;
#endif} OBJC2_UNAVAILABLE;

可以看到運(yùn)行時(shí)一個(gè)類還關(guān)聯(lián)了它的超類指針沪羔,類名饥伊,成員變量象浑,方法,緩存琅豆,還有附屬的協(xié)議愉豺。

PS:OBJC2_UNAVAILABLE之類的宏定義是蘋果在 Objc 中對(duì)系統(tǒng)運(yùn)行版本進(jìn)行約束的黑魔法,為的是兼容非Objective-C 2.0的遺留邏輯茫因,但我們?nèi)阅軓闹蝎@得一些有價(jià)值的信息蚪拦,有興趣的可以查看源代碼。

Objective-C 2.0 的頭文件雖然沒暴露出objc_class結(jié)構(gòu)體更詳細(xì)的設(shè)計(jì)冻押,我們依然可以從Objective-C 1.0 的定義中小窺端倪:

objc_class結(jié)構(gòu)體中:ivarsobjc_ivar_list指針驰贷;methodLists是指向objc_method_list指針的指針。也就是說可以動(dòng)態(tài)修改*methodLists的值來添加成員方法洛巢,這也是Category實(shí)現(xiàn)的原理括袒,同樣解釋了Category不能添加屬性的原因。而最新版的 Runtime 源碼對(duì)這一塊的<a >描述</a>已經(jīng)有很大變化稿茉,可以參考下美團(tuán)技術(shù)團(tuán)隊(duì)的深入理解<a >Objective-C:Category</a>锹锰。

PS:任性的話可以在Category中添加@dynamic的屬性,并利用運(yùn)行期動(dòng)態(tài)提供存取方法或干脆動(dòng)態(tài)轉(zhuǎn)發(fā)漓库;或者干脆使用關(guān)聯(lián)度對(duì)象(AssociatedObject)

其中objc_ivar_listobjc_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;
}

如果你C語言不是特別好恃慧,可以直接理解為objc_ivar_list結(jié)構(gòu)體存儲(chǔ)著objc_ivar數(shù)組列表,而objc_ivar結(jié)構(gòu)體存儲(chǔ)了類的單個(gè)成員變量的信息渺蒿;同理objc_method_list結(jié)構(gòu)體存儲(chǔ)著objc_method數(shù)組列表痢士,而objc_method結(jié)構(gòu)體存儲(chǔ)了類的某個(gè)方法的信息。

最后要提到的還有一個(gè)objc_cache茂装,顧名思義它是緩存怠蹂,它在objc_class的作用很重要陪汽,在后面會(huì)講到。

不知道你是否注意到了objc_class中也有一個(gè)isa對(duì)象褥蚯,這是因?yàn)橐粋€(gè) ObjC 類本身同時(shí)也是一個(gè)對(duì)象挚冤,為了處理類和對(duì)象的關(guān)系,runtime 庫創(chuàng)建了一種叫做元類 (Meta Class) 的東西赞庶,類對(duì)象所屬類型就叫做元類训挡,它用來表述類對(duì)象本身所具備的元數(shù)據(jù)。類方法就定義于此處歧强,因?yàn)檫@些方法可以理解成類對(duì)象的實(shí)例方法澜薄。每個(gè)類僅有一個(gè)類對(duì)象,而每個(gè)類對(duì)象僅有一個(gè)與之相關(guān)的元類摊册。當(dāng)你發(fā)出一個(gè)類似[NSObject alloc]的消息時(shí)肤京,你事實(shí)上是把這個(gè)消息發(fā)給了一個(gè)類對(duì)象 (Class Object) ,這個(gè)類對(duì)象必須是一個(gè)元類的實(shí)例茅特,而這個(gè)元類同時(shí)也是一個(gè)根元類 (root meta class) 的實(shí)例忘分。所有的元類最終都指向根元類為其超類。所有的元類的方法列表都有能夠響應(yīng)消息的類方法白修。所以當(dāng) [NSObject alloc] 這條消息發(fā)給類對(duì)象的時(shí)候妒峦,objc_msgSend()會(huì)去它的元類里面去查找能夠響應(yīng)消息的方法,如果找到了兵睛,然后對(duì)這個(gè)類對(duì)象執(zhí)行方法調(diào)用肯骇。

bdc7f74c-f1d1-4b63-8a35-b2a5cf741f9a.jpg

上圖實(shí)線是super_class 指針,虛線是isa指針祖很。 有趣的是根元類的超類是NSObject笛丙,而isa指向了自己,而NSObject的超類為nil假颇,也就是它沒有超類胚鸯。

Method

Method是一種代表類中的某個(gè)方法的類型。

typedef struct objc_method *Method;

objc_method在上面的方法列表中提到過拆融,它存儲(chǔ)了方法名蠢琳,方法類型和方法實(shí)現(xiàn):

struct objc_method { 
SEL method_name                              OBJC2_UNAVAILABLE; 
char *method_types                           OBJC2_UNAVAILABLE;
IMP method_imp OBJC2_UNAVAILABLE;
} OBJC2_UNAVAILABLE;
  • 方法名類型為SEL,前面提到過相同名字的方法即使在不同類中定義镜豹,它們的方法選擇器也相同傲须。
  • 方法類型method_types是個(gè)char指針,其實(shí)存儲(chǔ)著方法的參數(shù)類型和返回值類型趟脂。
  • method_imp指向了方法的實(shí)現(xiàn)泰讽,本質(zhì)上是一個(gè)函數(shù)指針,后面會(huì)詳細(xì)講到。

Ivar

Ivar是一種代表類中實(shí)例變量的類型已卸。

typedef struct objc_ivar *Ivar;

objc_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
} OBJC2_UNAVAILABLE;

可以根據(jù)實(shí)例查找其在類中的名字佛玄,也就是“反射”:

-(NSString *)nameWithInstance:(id)instance { 
unsigned int numIvars = 0; 
NSString *key=nil;
 Ivar * ivars = class_copyIvarList([self class], &numIvars); 
for(int i = 0; i < numIvars; i++) { 
Ivar thisIvar = ivars[i];
 const char *type = ivar_getTypeEncoding(thisIvar);
 NSString *stringType = [NSString stringWithCString:type encoding:NSUTF8StringEncoding];
 if (![stringType hasPrefix:@"@"]) { 
continue; 
} 
if ((object_getIvar(self, thisIvar) == instance)) {//此處若 crash 不要慌! 
key = [NSString stringWithUTF8String:ivar_getName(thisIvar)];
 break;
   } 
} 
free(ivars); 
return key;
}

class_copyIvarList 函數(shù)獲取的不僅有實(shí)例變量累澡,還有屬性梦抢。但會(huì)在原本的屬性名前加上一個(gè)下劃線。

IMP

IMP在objc.h中的定義是:

typedef id (*IMP)(id, SEL, ...);

它就是一個(gè)<a >函數(shù)指針</a>愧哟,這是由編譯器生成的奥吩。當(dāng)你發(fā)起一個(gè) ObjC消息之后,最終它會(huì)執(zhí)行的那段代碼蕊梧,就是由這個(gè)函數(shù)指針指定的霞赫。而IMP這個(gè)函數(shù)指針就指向了這個(gè)方法的實(shí)現(xiàn)。既然得到了執(zhí)行某個(gè)實(shí)例某個(gè)方法的入口肥矢,我們就可以繞開消息傳遞階段端衰,直接執(zhí)行方法。

你會(huì)發(fā)現(xiàn)IMP指向的方法與objc_msgSend函數(shù)類型相同甘改,參數(shù)都包含idSEL類型旅东。每個(gè)方法名都對(duì)應(yīng)一個(gè)SEL類型的方法選擇器,而每個(gè)實(shí)例對(duì)象中的SEL對(duì)應(yīng)的方法實(shí)現(xiàn)肯定是唯一的楼誓,通過一組idSEL參數(shù)就能確定唯一的方法實(shí)現(xiàn)地址玉锌;反之亦然名挥。

Cache

runtime.hCache的定義如下:

typedef struct objc_cache *Cache

還記得之前objc_class結(jié)構(gòu)體中有一個(gè)struct objc_cache *cache吧疟羹,它到底是緩存啥的呢,先看看objc_cache的實(shí)現(xiàn):

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指向的類的方法列表中遍歷查找能夠響應(yīng)消息的方法救湖,因?yàn)檫@樣效率太低了愧杯,而是優(yōu)先在Cache中查找。Runtime 系統(tǒng)會(huì)把被調(diào)用的方法存到Cache中(理論上講一個(gè)方法如果被調(diào)用鞋既,那么它有可能今后還會(huì)被調(diào)用)力九,下次查找的時(shí)候效率更高。這根計(jì)算機(jī)組成原理中學(xué)過的 CPU繞過主存先訪問Cache的道理挺像邑闺,而我猜蘋果為提高Cache命中率應(yīng)該也做了努力吧跌前。

Property

@property標(biāo)記了類中的屬性,這個(gè)不必多說大家都很熟悉陡舅,它是一個(gè)指向objc_property結(jié)構(gòu)體的指針:

typedef struct objc_property *Property;
typedef struct objc_property *objc_property_t;//這個(gè)更常用

可以通過class_copyPropertyListprotocol_copyPropertyList方法來獲取類和協(xié)議中的屬性:

objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount)
objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount)

返回類型為指向指針的指針抵乓,哈哈,因?yàn)閷傩粤斜硎莻€(gè)數(shù)組,每個(gè)元素內(nèi)容都是一個(gè)objc_property_t指針灾炭,而這兩個(gè)函數(shù)返回的值是指向這個(gè)數(shù)組的指針茎芋。舉個(gè)例子,先聲明一個(gè)類:

interface Lender : NSObject { 
float alone;
}
@property float alone;
@end

你可以用下面的代碼獲取屬性列表:

id LenderClass = objc_getClass("Lender");
unsigned int outCount;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);

你可以用property_getName函數(shù)來查找屬性名稱:

const char *property_getName(objc_property_t property)

你可以用class_getPropertyprotocol_getProperty通過給出的名稱來在類和協(xié)議中獲取屬性的引用:

objc_property_t class_getProperty(Class cls, const char *name)
objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty)

你可以用property_getAttributes函數(shù)來發(fā)掘?qū)傩缘拿Q和@encode類型字符串:

const char *property_getAttributes(objc_property_t property)

把上面的代碼放一起蜈出,你就能從一個(gè)類中獲取它的屬性啦:

id LenderClass = objc_getClass("Lender");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(LenderClass, &outCount);
for (i = 0; i < outCount; i++) { 
objc_property_t property = properties[i]; 
fprintf(stdout, "%s %s\n", property_getName(property), property_getAttributes(property));
}

對(duì)比下class_copyIvarList 函數(shù)田弥,使用 class_copyPropertyList 函數(shù)只能獲取類的屬性,而不包含成員變量铡原。但此時(shí)獲取的屬性名是不帶下劃線的皱蹦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市眷蜈,隨后出現(xiàn)的幾起案子沪哺,更是在濱河造成了極大的恐慌,老刑警劉巖酌儒,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辜妓,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡忌怎,警方通過查閱死者的電腦和手機(jī)籍滴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榴啸,“玉大人孽惰,你說我怎么就攤上這事∨赣。” “怎么了勋功?”我有些...
    開封第一講書人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)库说。 經(jīng)常有香客問我狂鞋,道長(zhǎng),這世上最難降的妖魔是什么潜的? 我笑而不...
    開封第一講書人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任骚揍,我火速辦了婚禮,結(jié)果婚禮上啰挪,老公的妹妹穿的比我還像新娘信不。我一直安慰自己,他們只是感情好亡呵,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開白布抽活。 她就那樣靜靜地躺著,像睡著了一般政己。 火紅的嫁衣襯著肌膚如雪酌壕。 梳的紋絲不亂的頭發(fā)上掏愁,一...
    開封第一講書人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音卵牍,去河邊找鬼果港。 笑死,一個(gè)胖子當(dāng)著我的面吹牛糊昙,可吹牛的內(nèi)容都是我干的辛掠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼释牺,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼萝衩!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起没咙,我...
    開封第一講書人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤猩谊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后祭刚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體牌捷,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年涡驮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暗甥。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捉捅,死狀恐怖撤防,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情棒口,我是刑警寧澤寄月,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站陌凳,受9級(jí)特大地震影響剥懒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜合敦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望验游。 院中可真熱鬧充岛,春花似錦、人聲如沸耕蝉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垒在。三九已至蒜魄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谈为。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來泰國(guó)打工旅挤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伞鲫。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓粘茄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親秕脓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子柒瓣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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