日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

IOS-RunTime(刨根问底)

發布時間:2023/12/19 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 IOS-RunTime(刨根问底) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

方法調用

讓我們看一下方法調用在運行時的過程(參照前文類在runtime中的表示)

如果用實例對象調用實例方法,會到實例的isa指針指向的對象(也就是類對象)操作。
如果調用的是類方法,就會到類對象的isa指針指向的對象(也就是元類對象)中操作。

  • 首先,在相應操作的對象中的緩存方法列表中找調用的方法,如果找到,轉向相應實現并執行。
  • 如果沒找到,在相應操作的對象中的方法列表中找調用的方法,如果找到,轉向相應實現執行
  • 如果沒找到,去父類指針所指向的對象中執行1,2.
  • 以此類推,如果一直到根類還沒找到,轉向攔截調用。
  • 如果沒有重寫攔截調用的方法,程序報錯。
  • 以上的過程給我帶來的啟發:

    • 重寫父類的方法,并沒有覆蓋掉父類的方法,只是在當前類對象中找到了這個方法后就不會再去父類中找了。
    • 如果想調用已經重寫過的方法的父類的實現,只需使用super這個編譯器標識,它會在運行時跳過在當前的類對象中尋找方法的過程。

    攔截調用(消息轉發)

    在方法調用中說到了,如果沒有找到方法就會轉向攔截調用。
    那么什么是攔截調用呢。
    攔截調用就是,在找不到調用的方法程序崩潰之前,你有機會通過重寫NSObject的四個方法來處理。

    1 + (BOOL)resolveClassMethod:(SEL)sel; 2 + (BOOL)resolveInstanceMethod:(SEL)sel; 3 //后兩個方法需要轉發到其他的類處理 4 - (id)forwardingTargetForSelector:(SEL)aSelector; 5 - (void)forwardInvocation:(NSInvocation *)anInvocation;
    • 第一個方法是當你調用一個不存在的類方法的時候,會調用這個方法,默認返回NO,你可以加上自己的處理然后返回YES。
    • 第二個方法和第一個方法相似,只不過處理的是實例方法。
    • 第三個方法是將你調用的不存在的方法重定向到一個其他聲明了這個方法的類,只需要你返回一個有這個方法的target。
    • 第四個方法是將你調用的不存在的方法打包成NSInvocation傳給你。做完你自己的處理后,調用invokeWithTarget:方法讓某個target觸發這個方法。?

    1、動態方法解析

    +?(BOOL)resolveInstanceMethod:(SEL)sel;

    首先,當接受到未能識別的選擇子時,運行時系統會調用該函數用以給對象一次機會來添加相應的方法實現,如果用戶在該函數中動態添加了相應方法的實現,則跳轉到方法的實現部分,并將該實現存入緩存中,以供下次調用。

    2、備援接收者

    -?(id)forwardingTargetForSelector:(SEL)aSelector;

    如果運行時在消息轉發的第一步中未找到所調用方法的實現,那么當前接收者還有第二次機會進行未知選擇子的處理。這時運行期系統會調用上述方法,并將未知選擇子作為參數傳入,該方法可以返回一個能處理該選擇子的對象,運行時系統會根據返回的對象進行查找,若找到則跳轉到相應方法的實現,則消息轉發結束。

    3、完整的消息轉發

    -?(void)forwardInvocation:(NSInvocation?*)anInvocation;

    當運行時系統檢測到第二步中用戶未返回能處理相應選擇子的對象時,那么來到這一步就要啟動完整的消息轉發機制了。該方法可以改變消息調用目標,運行時系統根據所改變的調用目標,向調用目標方法列表中查詢對應方法的實現并實現跳轉,這種方式和第二步的操作非常相似。當然你也可以修改方法的選擇子,亦或者向所調用方法中追加一個參數等來跳轉到相關方法的實現。

    最后,如果消息轉發的第三步還未能處理該未知選擇子的話,那么最終會調用NSObject類的如下方法用以異常的拋出,表明該選擇子最終未能處理。

    -?(void)doesNotRecognizeSelector:(SEL)aSelector;

    1.Runtime簡介

    因為Objc是一門動態語言,所以它總是想辦法把一些決定工作從編譯連接推遲到運行時。也就是說只有編譯器是不夠的,還需要一個運行時系統 (runtime system) 來執行編譯后的代碼。這就是 Objective-C Runtime 系統存在的意義,它是整個Objc運行框架的一塊基石。

    Runtime其實有兩個版本:“modern”和 “legacy”。我們現在用的 Objective-C 2.0 采用的是現行(Modern)版的Runtime系統,只能運行在 iOS 和 OS X 10.5 之后的64位程序中。而OS X較老的32位程序仍采用 Objective-C 1中的(早期)Legacy 版本的 Runtime 系統。這兩個版本最大的區別在于當你更改一個類的實例變量的布局時,在早期版本中你需要重新編譯它的子類,而現行版就不需要。

    Runtime基本是用C和匯編寫的,可見蘋果為了動態系統的高效而作出的努力。你可以在這里下到蘋果維護的開源代碼。蘋果和GNU各自維護一個開源的runtime版本,這兩個版本之間都在努力的保持一致。

    ?

    ?

    前言

    關于Objective-C Runtime一篇好的文檔 :?Understanding the Objective-C Runtime

    譯文地址為:?http://blog.cocoabit.com/blog/2014/10/06/yi-li-jieobjective-cruntime/

    Objective-C Runtime源碼是開源的,下載地址為:?http://opensource.apple.com/tarballs/objc4/

    習題內容

    @唐巧_boy在微博上分享了他們技術討論會關于objc runtime的討論習題內容,習題來自 sunnyxx(博客)。以下是習題內容(圖片轉自@唐巧_boy微博):

    自己做完這些題之后,也順便復習了一些Objective-C Runtime的知識,現在整理一下,分享給大家。

    該筆記分為四篇:

    刨根問底Objective-C Runtime(1)- Self & Super

    刨根問底Objective-C Runtime(2)- Object & Class & Meta Class

    刨根問底Objective-C Runtime(3)- 消息和Category

    刨根問底Objective-C Runtime(4)- 成員變量與屬性

    ?

    ?

    刨根問底Objective-C Runtime(1)- Self & Super

    下面的代碼輸出什么?

    1 2 3 4 5 6 7 8 9 10 11 12 @implementation?Son?:?Father -?(id)init { ????self?=?[super?init]; ????if?(self) ????{ ????????NSLog(@"%@",?NSStringFromClass([self?class])); ????????NSLog(@"%@",?NSStringFromClass([super?class])); ????} ????return?self; } @end

    答案:都輸出 Son

    1 2 2014-11-05?11:06:18.060?Test[8566:568584]?NSStringFromClass([self?class])?=?Son 2014-11-05?11:06:18.061?Test[8566:568584]?NSStringFromClass([super?class])?=?Son

    解惑:這個題目主要是考察關于objc中對 self 和 super 的理解。

    self 是類的隱藏參數,指向當前調用方法的這個類的實例。而 super 是一個 Magic Keyword, 它本質是一個編譯器標示符,和 self 是指向的同一個消息接受者。上面的例子不管調用[self class]還是[super class],接受消息的對象都是當前 Son *xxx 這個對象。而不同的是,super是告訴編譯器,調用 class 這個方法時,要去父類的方法,而不是本類里的。

    當使用 self 調用方法時,會從當前類的方法列表中開始找,如果沒有,就從父類中再找;而當使用 super 時,則從父類的方法列表中開始找。然后調用父類的這個方法。

    真的是這樣嗎?繼續看:

    使用clang重寫命令:

    1 $?clang?-rewrite-objc?test.m

    發現上述代碼被轉化為:

    1 2 NSLog((NSString?*)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_0,?NSStringFromClass(((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)self,?sel_registerName("class")))); NSLog((NSString?*)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_a5cecc_mi_1,?NSStringFromClass(((Class?(*)(__rw_objc_super?*,?SEL))(void?*)objc_msgSendSuper)((__rw_objc_super){?(id)self,?(id)class_getSuperclass(objc_getClass("Son"))?},?sel_registerName("class"))));

    從上面的代碼中,我們可以發現在調用 [self class] 時,會轉化成 objc_msgSend函數。看下函數定義:

    1 id?objc_msgSend(id?self,?SEL?op,?...)

    我們把 self 做為第一個參數傳遞進去。

    而在調用 [super class]時,會轉化成 objc_msgSendSuper函數。看下函數定義:

    1 id?objc_msgSendSuper(struct?objc_super?*super,?SEL?op,?...)

    第一個參數是 objc_super 這樣一個結構體,其定義如下:

    1 2 3 4 struct?objc_super?{ ???__unsafe_unretained?id?receiver; ???__unsafe_unretained?Class?super_class; };

    結構體有兩個成員,第一個成員是 receiver, 類似于上面的 objc_msgSend函數第一個參數self 。第二個成員是記錄當前類的父類是什么。

    所以,當調用 [self class] 時,實際先調用的是 objc_msgSend函數,第一個參數是 Son當前的這個實例,然后在 Son 這個類里面去找 - (Class)class這個方法,沒有,去父類 Father里找,也沒有,最后在 NSObject類中發現這個方法。而 - (Class)class的實現就是返回self的類別,故上述輸出結果為 Son。

    objc Runtime開源代碼對- (Class)class方法的實現:

    1 2 3 -?(Class)class?{ ????return?object_getClass(self); }

    而當調用 [super class]時,會轉換成objc_msgSendSuper函數。第一步先構造 objc_super 結構體,結構體第一個成員就是 self 。第二個成員是 (id)class_getSuperclass(objc_getClass(“Son”)) , 實際該函數輸出結果為 Father。第二步是去 Father這個類里去找- (Class)class,沒有,然后去NSObject類去找,找到了。最后內部是使用 objc_msgSend(objc_super->receiver, @selector(class))去調用,此時已經和[self class]調用相同了,故上述輸出結果仍然返回 Son。

    刨根問底Objective-C Runtime(2)- Object & Class & Meta Clas

    本篇筆記主要是講述objc runtime中關于Object & Class & Meta Class的細節。

    習題內容

    下面代碼的運行結果是?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 @interface?Sark?:?NSObject @end @implementation?Sark @end int?main(int?argc,?const?char?*?argv[])?{ ????@autoreleasepool?{ ????????BOOL?res1?=?[(id)[NSObject?class]?isKindOfClass:[NSObject?class]]; ????????BOOL?res2?=?[(id)[NSObject?class]?isMemberOfClass:[NSObject?class]]; ????????BOOL?res3?=?[(id)[Sark?class]?isKindOfClass:[Sark?class]]; ????????BOOL?res4?=?[(id)[Sark?class]?isMemberOfClass:[Sark?class]]; ????????NSLog(@"%d?%d?%d?%d",?res1,?res2,?res3,?res4); ????} ????return?0; }

    運行結果為:

    1 2014-11-05?14:45:08.474?Test[9412:721945]?1?0?0?0

    這里先看幾個概念

    什么是 id

    id 在 objc.h 中定義如下:

    1 2 ///?A?pointer?to?an?instance?of?a?class. typedef?struct?objc_object?*id;

    就像注釋中所說的這樣 id 是指向一個 objc_object 結構體的指針。

    id 這個struct的定義本身就帶了一個 *, 所以我們在使用其他NSObject類型的實例時需要在前面加上 *, 而使用 id 時卻不用。

    那么objc_object又是什么呢

    objc_object 在 objc.h 中定義如下:

    1 2 3 4 ///?Represents?an?instance?of?a?class. struct?objc_object?{ ????Class?isa; };

    這個時候我們知道Objective-C中的object在最后會被轉換成C的結構體,而在這個struct中有一個 isa 指針,指向它的類別 Class。

    那么什么是Class呢

    在 objc.h 中定義如下:

    1 2 ///?An?opaque?type?that?represents?an?Objective-C?class. typedef?struct?objc_class?*Class;

    我們可以看到 Class本身指向的也是一個C的struct objc_class。

    繼續看在runtime.h中objc_class定義如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 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;

    該結構體中,isa 指向所屬Class, super_class指向父類別。

    繼續看

    下載objc源代碼,在 objc-runtime-new.h 中,我們發現 objc_class有如下定義:

    1 2 3 4 5 6 struct?objc_class?:?objc_object?{ ????//?Class?ISA; ????Class?superclass;??? ????... ????... }

    豁然開朗,我們看到在Objective-C的設計哲學中,一切都是對象。Class在設計中本身也是一個對象。而這個Class對象的對應的類,我們叫它 Meta Class。即Class結構體中的 isa 指向的就是它的 Meta Class。

    Meta Class

    根據上面的描述,我們可以把Meta Class理解為 一個Class對象的Class。簡單的說:

    1 2 當我們發送一個消息給一個NSObject對象時,這條消息會在對象的類的方法列表里查找 當我們發送一個消息給一個類時,這條消息會在類的Meta?Class的方法列表里查找

    而 Meta Class本身也是一個Class,它跟其他Class一樣也有自己的 isa 和 super_class 指針。看下圖:

    • 每個Class都有一個isa指針指向一個唯一的Meta Class

    • 每一個Meta Class的isa指針都指向最上層的Meta Class(圖中的NSObject的Meta Class)

    • 最上層的Meta Class的isa指針指向自己,形成一個回路

    • 每一個Meta Class的super class指針指向它原本Class的 Super Class的Meta Class。但是最上層的Meta Class的 Super Class指向NSObject Class本身

    • 最上層的NSObject Class的super class指向 nil

    解惑

    為了更加清楚的知道整個函數調用過程,我們使用clang -rewrite-objc main.m重寫,可獲得如下代碼:

    1 2 3 4 BOOL?res1?=?((BOOL?(*)(id,?SEL,?Class))(void?*)objc_msgSend)((id)((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("NSObject"),?sel_registerName("class")),?sel_registerName("isKindOfClass:"),?((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("NSObject"),?sel_registerName("class"))); BOOL?res2?=?((BOOL?(*)(id,?SEL,?Class))(void?*)objc_msgSend)((id)((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("NSObject"),?sel_registerName("class")),?sel_registerName("isMemberOfClass:"),?((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("NSObject"),?sel_registerName("class"))); BOOL?res3?=?((BOOL?(*)(id,?SEL,?Class))(void?*)objc_msgSend)((id)((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("Sark"),?sel_registerName("class")),?sel_registerName("isMemberOfClass:"),?((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("NSObject"),?sel_registerName("class"))); BOOL?res4?=?((BOOL?(*)(id,?SEL,?Class))(void?*)objc_msgSend)((id)((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("Sark"),?sel_registerName("class")),?sel_registerName("isMemberOfClass:"),?((Class?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("NSObject"),?sel_registerName("class")));

    先看前兩個調用:

    • 最外層是 objc_msgSend函數,轉發消息。

    • 函數第一個參數是 (id)((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class"))

    • 函數第二個參數是轉發的selector

    • 函數第三個參數是 ((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("class"))

    我們注意到第一個參數和第三個參數對應重寫的是[NSObject class],即使用objc_msgSend向 NSObject Class 發送 @selector(class) 這個消息

    打開objc源代碼,在 Object.mm 中發現+ (Class)class實現如下:

    1 2 3 +?(Class)class?{ ????return?self; }

    所以即返回Class類的對象本身。看如下輸出:

    1 2 3 4 NSLog(@"%p",?[NSObject?class]); NSLog(@"%p",?[NSObject?class]); 2014-11-05?18:48:30.939?Test[11682:865988]?0x7fff768d40f0 2014-11-05?18:48:30.940?Test[11682:865988]?0x7fff768d40f0

    繼續打開objc源代碼,在 Object.mm 中,我們發現 isKindOfClass的實現如下:

    1 2 3 4 5 6 7 8 -?(BOOL)isKindOf:aClass { ????Class?cls; ????for?(cls?=?isa;?cls;?cls?=?cls->superclass)? ????????if?(cls?==?(Class)aClass) ????????????return?YES; ????return?NO; }

    對著上面Meta Class的圖和實現,我們可以看出

    • 當 NSObject Class對象第一次進行比較時,得到它的isa為 NSObject的Meta Class, 這個時候 NSObject Meta Class 和 NSObject Class不相等。

    • 然后取NSObject 的Meta Class 的Super class,這個時候又變成了 NSObject Class, 所以返回相等。

    所以上述第一個輸出結果是 YES 。

    我們在看下 ‘isMemberOfClass’的實現:

    1 2 3 4 -?(BOOL)isMemberOf:aClass { ????return?isa?==?(Class)aClass; }

    綜上所述,當前的 isa 指向 NSObject 的 Meta Class, 所以和 NSObject Class不相等。

    所以上述第二個輸出結果為 NO 。

    繼續看后面兩個調用:

    • Sark Class 的isa指向的是 Sark的Meta Class,和Sark Class不相等

    • Sark Meta Class的super class 指向的是 NSObject Meta Class, 和 Sark Class不相等

    • NSObject Meta Class的 super class 指向 NSObject Class,和 Sark Class 不相等

    • NSObject Class 的super class 指向 nil, 和 Sark Class不相等

    所以后面兩個調用的結果都輸出為 NO 。

    刨根問底Objective-C Runtime(3)- 消息 和 Category

    理解這些概念之間關系最好的方式是:一個類(Class)維護一張調度表(dispatch table)用于解析運行時發送的消息;調度表中的每個實體(entry)都是一個方法(Method),其中key值是一個唯一的名字——選擇器(SEL),它對應到一個實現(IMP)——實際上就是指向標準C函數的指針。

    本篇筆記主要是講述objc runtime的 消息和Category。

    習題內容

    下面的代碼會?Compile Error / Runtime Crash / NSLog…?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @interface?NSObject?(Sark) +?(void)foo; @end @implementation?NSObject?(Sark) -?(void)foo { ????NSLog(@"IMP:?-[NSObject(Sark)?foo]"); } @end int?main(int?argc,?const?char?*?argv[])?{ ????@autoreleasepool?{ ????????[NSObject?foo]; ????????[[NSObject?new]?foo]; ????} ????return?0; }

    答案:代碼正常輸出,輸出結果如下:

    1 2 2014-11-06?13:11:46.694?Test[14872:1110786]?IMP:?-[NSObject(Sark)?foo] 2014-11-06?13:11:46.695?Test[14872:1110786]?IMP:?-[NSObject(Sark)?foo]

    使用clang -rewrite-objc main.m重寫,我們可以發現 main 函數中兩個方法調用被轉換成如下代碼:

    1 2 ?((void?(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("NSObject"),?sel_registerName("foo")); ?((void?(*)(id,?SEL))(void?*)objc_msgSend)((id)((NSObject?*(*)(id,?SEL))(void?*)objc_msgSend)((id)objc_getClass("NSObject"),?sel_registerName("new")),?sel_registerName("foo"));

    我們發現上述兩個方法最終轉換成使用 objc_msgSend 函數傳遞消息。

    這里先看幾個概念

    objc_msgSend函數定義如下:

    1 id?objc_msgSend(id?self,?SEL?op,?...)

    關于 id 的解釋請看objc runtime系列第二篇博文: objc runtime中Object & Class & Meta Class的細節

    什么是 SEL(選擇器)

    打開objc.h文件,看下SEL的定義如下:

    1 typedef?struct?objc_selector?*SEL;

    SEL本質上是一個指向objc_selector結構體的指針。而 objc_selector 的定義并沒有在runtime.h中給出定義。我們可以嘗試運行如下代碼:

    1 2 3 4 5 6 7 SEL?sel?=?@selector(foo); NSLog(@"%s",?(char?*)sel); NSLog(@"%p",?sel); const?char?*selName?=?[@"foo"?UTF8String]; SEL?sel2?=?sel_registerName(selName); NSLog(@"%s",?(char?*)sel2); NSLog(@"%p",?sel2);

    輸出如下:

    1 2 3 4 2014-11-06?13:46:08.058?Test[15053:1132268]?foo 2014-11-06?13:46:08.058?Test[15053:1132268]?0x7fff8fde5114 2014-11-06?13:46:08.058?Test[15053:1132268]?foo 2014-11-06?13:46:08.058?Test[15053:1132268]?0x7fff8fde5114

    方法的selector用于表示運行時方法的名字。Objective-C在編譯時,會依據每一個方法的名字、參數序列,生成一個唯一的整型標識(Int類型的地址),這個標識就是SEL

    Objective-C在編譯時,會根據方法的名字生成一個用來區分這個方法的唯一的一個ID。只要方法名稱相同,那么它們的ID就是相同的。

    兩個類之間,不管它們是父類與子類的關系,還是之間沒有這種關系,只要方法名相同,那么它的SEL就是一樣的。每一個方法都對應著一個SEL。編譯器會根據每個方法的方法名為那個方法生成唯一的SEL。這些SEL組成了一個Set集合,當我們在這個集合中查找某個方法時,只需要去找這個方法對應的SEL即可。而SEL本質是一個字符串,所以直接比較它們的地址即可。

    當然,不同的類可以擁有相同的selector。不同類的實例對象執行相同的selector時,會在各自的方法列表中去根據selector去尋找自己對應的IMP。

    我們可以通過下面三種方法來獲取SEL:

    1. sel_registerName函數

    2. Objective-C編譯器提供的@selector()

    3. NSSelectorFromString()方法

    那么什么是IMP(實現)

    繼續看定義:

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

    IMP實際上是一個函數指針,指向方法實現的首地址。

    這個函數使用當前CPU架構實現的標準的C調用約定。第一個參數是指向self的指針(如果是實例方法,則是類實例的內存地址;如果是類方法,則是指向元類的指針),第二個參數是方法選擇器(selector),接下來是方法的實際參數列表。

    前面介紹過的SEL就是為了查找方法的最終實現IMP的。由于每個方法對應唯一的SEL,因此我們可以通過SEL方便快速準確地獲得它所對應的 IMP,查找過程將在下面討論。取得IMP后,我們就獲得了執行這個方法代碼的入口點,此時,我們就可以像調用普通的C語言函數一樣來使用這個函數指針 了。

    通過取得IMP,我們可以跳過Runtime的消息傳遞機制,直接執行IMP指向的函數實現,這樣省去了Runtime消息傳遞過程中所做的一系列查找操作,會比直接向對象發送消息高效一些。

    那么 objc_msgSend 到底是怎么工作的呢?

    在Objective-C中,消息直到運行時才會綁定到方法的實現上。編譯器會把代碼中[target doSth]轉換成 objc_msgSend消息函數,這個函數完成了動態綁定的所有事情。它的運行流程如下:

  • 檢查selector是否需要忽略。(ps: Mac開發中開啟GC就會忽略retain,release方法。)

  • 檢查target是否為nil。如果為nil,直接cleanup,然后return。(這就是我們可以向nil發送消息的原因。)

  • 然后在target的Class中根據Selector去找IMP

  • 尋找IMP的過程:

  • 先從當前class的cache方法列表(cache methodLists)里去找

  • 找到了,跳到對應函數實現

  • 沒找到,就從class的方法列表(methodLists)里找

  • 還找不到,就到super class的方法列表里找,直到找到基類(NSObject)為止

  • 最后再找不到,就會進入動態方法解析和消息轉發的機制。(這部分知識,下次再細談)

  • 那么什么是方法列表呢?

    上一篇博文中提到了objc_class結構體定義,如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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; 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; }

    1) objc_method_list 就是用來存儲當前類的方法鏈表,objc_method存儲了類的某個方法的信息。

    Method(方法)

    1 typedef?struct?objc_method?*Method;

    Method 是用來代表類中某個方法的類型,它實際就指向objc_method結構體,如下:

    1 2 3 4 5 struct?objc_method?{ ????SEL?method_name??????????????????????????????????????????OBJC2_UNAVAILABLE; ????char?*method_types???????????????????????????????????????OBJC2_UNAVAILABLE; ????IMP?method_imp???????????????????????????????????????????OBJC2_UNAVAILABLE; }????????????????????????????????????????????????????????????OBJC2_UNAVAILABLE;

    method_types是個char指針,存儲著方法的參數類型和返回值類型。

    SEL 和 IMP 就是我們上文提到的,所以我們可以理解為objc_class中 method list保存了一組SEL<->IMP的映射。

    2)objc_cache 用來緩存用過的方法,提高性能。

    Cache

    1 typedef?struct?objc_cache?*Cache?????????????????????????????OBJC2_UNAVAILABLE;

    實際指向objc_cache結構體,如下:

    1 2 3 4 5 struct?objc_cache?{ ????unsigned?int?mask?/*?total?=?mask?+?1?*/?????????????????OBJC2_UNAVAILABLE; ????unsigned?int?occupied????????????????????????????????????OBJC2_UNAVAILABLE; ????Method?buckets[1]????????????????????????????????????????OBJC2_UNAVAILABLE; };
    • mask: 指定分配cache buckets的總數。在方法查找中,Runtime使用這個字段確定數組的索引位置

    • occupied: 實際占用cache buckets的總數

    • buckets: 指定Method數據結構指針的數組。這個數組可能包含不超過mask+1個元素。需要注意的是,指針可能是NULL,表示這個緩存bucket沒有被占用,另外被占用的bucket可能是不連續的。這個數組可能會隨著時間而增長。

    objc_msgSend每調用一次方法后,就會把該方法緩存到cache列表中,下次的時候,就直接優先從cache列表中尋找,如果cache沒有,才從methodLists中查找方法。

    說完了 objc_msgSend, 那么題目中的Category又是怎么工作的呢?

    繼續看概念

    我們知道Catagory可以動態地為已經存在的類添加新的方法。這樣可以保證類的原始設計規模較小,功能增加時再逐步擴展。在runtime.h中查看定義:

    1 typedef?struct?objc_category?*Category;

    同樣也是指向一個 objc_category 的C 結構體,定義如下:

    1 2 3 4 5 6 7 struct?objc_category?{ ????char?*category_name??????????????????????????????????????OBJC2_UNAVAILABLE; ????char?*class_name?????????????????????????????????????????OBJC2_UNAVAILABLE; ????struct?objc_method_list?*instance_methods????????????????OBJC2_UNAVAILABLE; ????struct?objc_method_list?*class_methods???????????????????OBJC2_UNAVAILABLE; ????struct?objc_protocol_list?*protocols?????????????????????OBJC2_UNAVAILABLE; }????????????????????????????????????????????????????????????OBJC2_UNAVAILABLE;

    通過上面的結構體,大家可以很清楚的看出存儲的內容。我們繼續往下看,打開objc源代碼,在 objc-runtime-new.h中我們可以發現如下定義:

    1 2 3 4 5 6 7 8 struct?category_t?{ ????const?char?*name; ????classref_t?cls; ????struct?method_list_t?*instanceMethods; ????struct?method_list_t?*classMethods; ????struct?protocol_list_t?*protocols; ????struct?property_list_t?*instanceProperties; };

    上面的定義需要提到的地方有三點:

  • name 是指 class_name 而不是 category_name

  • cls是要擴展的類對象,編譯期間是不會定義的,而是在Runtime階段通過name對應到對應的類對象

  • instanceProperties表示Category里所有的properties,這就是我們可以通過objc_setAssociatedObject和objc_getAssociatedObject增加實例變量的原因,不過這個和一般的實例變量是不一樣的

  • 為了驗證上述內容,我們使用clang -rewrite-objc main.m重寫,題目中的Category被編譯器轉換成了這樣:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 //?@interface?NSObject?(Sark) //?+?(void)foo; /*?@end?*/ //?@implementation?NSObject?(Sark) static?void?_I_NSObject_Sark_foo(NSObject?*?self,?SEL?_cmd)?{ ????NSLog((NSString?*)&__NSConstantStringImpl__var_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_dd1ee3_mi_0); } //?@end static?struct?_category_t?_OBJC_$_CATEGORY_NSObject_$_Sark?__attribute__?((used,?section?("__DATA,__objc_const")))?=? { ????"NSObject", ????0,?//?&OBJC_CLASS_$_NSObject, ????(const?struct?_method_list_t?*)&_OBJC_$_CATEGORY_INSTANCE_METHODS_NSObject_$_Sark, ????0, ????0, ????0, }; static?struct?_category_t?*L_OBJC_LABEL_CATEGORY_$?[1]?__attribute__((used,?section?("__DATA,?__objc_catlist,regular,no_dead_strip")))=?{ ????&_OBJC_$_CATEGORY_NSObject_$_Sark, };
    • _OBJC_$_CATEGORY_NSObject_$_Sark是按規則生成的字符串,我們可以清楚的看到是NSObject類,且Sark是NSObject類的Category

    • _category_t結構體第二項 classref_t 沒有數據,驗證了我們上面的說法

    • 由于題目中只有 - (void)foo方法,所以結構體中存儲的list只有第三項instanceMethods被填充。

    • _I_NSObject_Sark_foo代表了Category的foo方法,I表示實例方法

    • 最后這個類的Category生成了一個數組,存在了__objc_catlist里,目前數組的內容只有一個&_OBJC_$_CATEGORY_NSObject_$_Sark

    最終這些Category里面的方法是如何被加載的呢?

    1.打開objc源代碼,找到 objc-os.mm, 函數_objc_init為runtime的加載入口,由libSystem調用,進行初始化操作。

    2.之后調用objc-runtime-new.mm -> map_images加載map到內存

    3.之后調用objc-runtime-new.mm->_read_images初始化內存中的map, 這個時候將會load所有的類,協議還有Category。NSOBject的+load方法就是這個時候調用的

    這里貼上Category被加載的代碼:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 //?Discover?categories.? for?(EACH_HEADER)?{ ????category_t?**catlist?=? ????????_getObjc2CategoryList(hi,?&count); ????for?(i?=?0;?i?<?count;?i++)?{ ????????category_t?*cat?=?catlist[i]; ????????Class?cls?=?remapClass(cat->cls); ????????if?(!cls)?{ ????????????//?Category's?target?class?is?missing?(probably?weak-linked). ????????????//?Disavow?any?knowledge?of?this?category. ????????????catlist[i]?=?nil; ????????????if?(PrintConnecting)?{ ????????????????_objc_inform("CLASS:?IGNORING?category?\?\?\?(%s)?%p?with?" ?????????????????????????????"missing?weak-linked?target?class",? ?????????????????????????????cat->name,?cat); ????????????} ????????????continue; ????????} ????????//?Process?this?category.? ????????//?First,?register?the?category?with?its?target?class.? ????????//?Then,?rebuild?the?class's?method?lists?(etc)?if? ????????//?the?class?is?realized.? ????????BOOL?classExists?=?NO; ????????if?(cat->instanceMethods?||??cat->protocols?? ????????????||??cat->instanceProperties)? ????????{ ????????????addUnattachedCategoryForClass(cat,?cls,?hi); ????????????if?(cls->isRealized())?{ ????????????????remethodizeClass(cls); ????????????????classExists?=?YES; ????????????} ????????????if?(PrintConnecting)?{ ????????????????_objc_inform("CLASS:?found?category?-%s(%s)?%s",? ?????????????????????????????cls->nameForLogging(),?cat->name,? ?????????????????????????????classExists???"on?existing?class"?:?""); ????????????} ????????} ????????if?(cat->classMethods??||??cat->protocols?? ????????????/*?||??cat->classProperties?*/)? ????????{ ????????????addUnattachedCategoryForClass(cat,?cls->ISA(),?hi); ????????????if?(cls->ISA()->isRealized())?{ ????????????????remethodizeClass(cls->ISA()); ????????????} ????????????if?(PrintConnecting)?{ ????????????????_objc_inform("CLASS:?found?category?+%s(%s)",? ?????????????????????????????cls->nameForLogging(),?cat->name); ????????????} ????????} ????} }

    1) 循環調用了 _getObjc2CategoryList方法,這個方法的實現是:

    1 GETSECT(_getObjc2CategoryList,????????category_t?*,????"__objc_catlist");

    方法中最后一個參數__objc_catlist就是編譯器剛剛生成的category數組

    2) load完所有的categories之后,開始對Category進行處理。

    從上面的代碼中我們可以發現:實例方法被加入到了當前的類對象中, 類方法被加入到了當前類的Meta Class中 (cls->ISA)

    Step 1. 調用addUnattachedCategoryForClass方法

    Step 2. 調用remethodizeClass方法, 在remethodizeClass的實現里調用attachCategoryMethods

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 static?void? attachCategoryMethods(Class?cls,?category_list?*cats,?bool?flushCaches) { ????if?(!cats)?return; ????if?(PrintReplacedMethods)?printReplacements(cls,?cats); ????bool?isMeta?=?cls->isMetaClass(); ????method_list_t?**mlists?=?(method_list_t?**) ????????_malloc_internal(cats->count?*?sizeof(*mlists)); ????//?Count?backwards?through?cats?to?get?newest?categories?first ????int?mcount?=?0; ????int?i?=?cats->count; ????BOOL?fromBundle?=?NO; ????while?(i--)?{ ????????method_list_t?*mlist?=?cat_method_list(cats->list[i].cat,?isMeta); ????????if?(mlist)?{ ????????????mlists[mcount++]?=?mlist; ????????????fromBundle?|=?cats->list[i].fromBundle; ????????} ????} ????attachMethodLists(cls,?mlists,?mcount,?NO,?fromBundle,?flushCaches); ????_free_internal(mlists); }

    這里把一個類的category_list的所有方法取出來生成了method list。這里是倒序添加的,也就是說,新生成的category的方法會先于舊的category的方法插入。

    之后調用attachMethodLists將所有方法前序添加進類的method list中,如果原來類的方法列表是a,b,Category的方法列表是c,d。那么插入之后的方法列表將會是c,d,a,b。

    小發現

    看上面被編譯器轉換的代碼,我們發現Category頭文件被注釋掉了,結合上面category的加載過程。這就是我們即使沒有import category的頭文件,都能夠成功調用到Category方法的原因。

    runtime加載完成后,Category的原始信息在類結構中將不會存在。

    解惑

    根據上面提到的知識,我們對題目中的代碼進行分析。

    1) objc runtime加載完后,NSObject的Sark Category被加載。而NSObject的Sark Category的頭文件 + (void)foo 并沒有實質參與到工作中,只是給編譯器進行靜態檢查,所有我們編譯上述代碼會出現警告,提示我們沒有實現 + (void)foo 方法。而在代碼編譯中,它已經被注釋掉了。

    2) 實際被加入到Class的method list的方法是 - (void)foo,它是一個實例方法,所以加入到當前類對象NSObject的方法列表中,而不是NSObject Meta class的方法列表中。

    3) 當執行 [NSObject foo]時,我們看下整個objc_msgSend的過程:

    結合上一篇Meta Class的知識:

    • objc_msgSend 第一個參數是 ?“(id)objc_getClass("NSObject")”,獲得NSObject Class的對象。

    • 類方法在Meta Class的方法列表中找,我們在load Category方法時加入的是- (void)foo實例方法,所以并不在NSOBject Meta Class的方法列表中

    • 繼續往 super class中找,在上一篇博客中我們知道,NSObject Meta Class的super class是NSObject本身。所以,這個時候我們能夠找到- (void)foo 這個方法。

    • 所以正常輸出結果

    4) 當執行[[NSObject new] foo],我們看下整個objc_msgSend的過程:

    • [NSObject new]生成一個NSObject對象。

    • 直接在該對象的類(NSObject)的方法列表里找。

    • 能夠找到,所以正常輸出結果。

    刨根問底Objective-C Runtime(4)- 成員變量與屬性

    本篇筆記主要是講述objc runtime的 成員變量和屬性。

    習題內容

    下面代碼會? Compile Error / Runtime Crash / NSLog…?

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 @interface?Sark?:?NSObject @property?(nonatomic,?copy)?NSString?*name; @end @implementation?Sark -?(void)speak { ????NSLog(@"my?name?is?%@",?self.name); } @end @interface?Test?:?NSObject @end @implementation?Test -?(instancetype)init { ????self?=?[super?init]; ????if?(self)?{ ????????id?cls?=?[Sark?class]; ????????void?*obj?=?&cls; ????????[(__bridge?id)obj?speak]; ????} ????return?self; } @end int?main(int?argc,?const?char?*?argv[])?{ ????@autoreleasepool?{ ????????[[Test?alloc]?init]; ????} ????return?0; }

    答案:代碼正常輸出,輸出結果為:

    1 2014-11-07?14:08:25.698?Test[1097:57255]?my?name?is

    為什么呢?

    前幾節博文中多次講到了objc_class結構體,今天我們再拿出來看一下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 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;

    其中objc_ivar_list結構體存儲著objc_ivar數組列表,而objc_ivar結構體存儲了類的單個成員變量的信息。

    那么什么是Ivar呢?

    Ivar 在objc中被定義為:

    1 typedef?struct?objc_ivar?*Ivar;

    它是一個指向objc_ivar結構體的指針,結構體有如下定義:

    1 2 3 4 5 6 7 8 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;

    這里我們注意第三個成員 ivar_offset。它表示基地址偏移字節。

    在編譯我們的類時,編譯器生成了一個 ivar布局,顯示了在類中從哪可以訪問我們的 ivars 。看下圖:

    上圖中,左側的數據就是地址偏移字節,我們對 ivar 的訪問就可以通過 對象地址 + ivar偏移字節的方法。但是這又引發一個問題,看下圖:

    我們增加了父類的ivar,這個時候布局就出錯了,我們就不得不重新編譯子類來恢復兼容性。

    而Objective-C Runtime中使用了Non Fragile ivars,看下圖:

    使用Non Fragile ivars時,Runtime會進行檢測來調整類中新增的ivar的偏移量。 這樣我們就可以通過 對象地址 + 基類大小 + ivar偏移字節的方法來計算出ivar相應的地址,并訪問到相應的ivar。

    我們來看一個例子:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @interface?Student?:?NSObject { ????@private ????NSInteger?age; } @end @implementation?Student -?(NSString?*)description { ????return?[NSString?stringWithFormat:@"age?=?%d",?age]; } @end int?main(int?argc,?const?char?*?argv[])?{ ????@autoreleasepool?{ ????????Student?*student?=?[[Student?alloc]?init]; ????????student->age?=?24; ????} ????return?0; }

    上述代碼,Student有兩個被標記為private的ivar,這個時候當我們使用 -> 訪問時,編譯器會報錯。那么我們如何設置一個被標記為private的ivar的值呢?

    通過上面的描述,我們知道ivar是通過計算字節偏量來確定地址,并訪問的。我們可以改成這樣:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 @interface?Student?:?NSObject { ????@private ????int?age; } @end @implementation?Student -?(NSString?*)description { ????NSLog(@"current?pointer?=?%p",?self); ????NSLog(@"age?pointer?=?%p",?&age); ????return?[NSString?stringWithFormat:@"age?=?%d",?age]; } @end int?main(int?argc,?const?char?*?argv[])?{ ????@autoreleasepool?{ ????????Student?*student?=?[[Student?alloc]?init]; ????????Ivar?age_ivar?=?class_getInstanceVariable(object_getClass(student),?"age"); ????????int?*age_pointer?=?(int?*)((__bridge?void?*)(student)?+?ivar_getOffset(age_ivar)); ????????NSLog(@"age?ivar?offset?=?%td",?ivar_getOffset(age_ivar)); ????????*age_pointer?=?10; ????????NSLog(@"%@",?student); ????} ????return?0; }

    上述代碼的輸出結果為:

    1 2 3 4 2014-11-08?18:24:38.892?Test[4143:466864]?age?ivar?offset?=?8 2014-11-08?18:24:38.893?Test[4143:466864]?current?pointer?=?0x1001002d0 2014-11-08?18:24:38.893?Test[4143:466864]?age?pointer?=?0x1001002d8 2014-11-08?18:24:38.894?Test[4143:466864]?age?=?10

    我們可以清晰的看到指針地址的變化和偏移量,和我們上述描述一致。

    說完了Ivar, 那Property又是怎么樣的呢?

    使用clang -rewrite-objc main.m重寫題目中的代碼,我們發現Sark類中的name屬性被轉換成了如下代碼:

    1 2 3 4 5 6 7 8 9 struct?Sark_IMPL?{ ????struct?NSObject_IMPL?NSObject_IVARS; ????NSString?*_name; }; //?@property?(nonatomic,?copy)?NSString?*name; /*?@end?*/ //?@implementation?Sark static?NSString?*?_I_Sark_name(Sark?*?self,?SEL?_cmd)?{?return?(*(NSString?**)((char?*)self?+?OBJC_IVAR_$_Sark$_name));?} static?void?_I_Sark_setName_(Sark?*?self,?SEL?_cmd,?NSString?*name)?{?objc_setProperty?(self,?_cmd,?__OFFSETOFIVAR__(struct?Sark,?_name),?(id)name,?0,?1);?}

    類中的Property屬性被編譯器轉換成了Ivar,并且自動添加了我們熟悉的Set和Get方法。

    我們這個時候回頭看一下objc_class結構體中的內容,并沒有發現用來專門記錄Property的list。我們翻開objc源代碼,在objc-runtime-new.h中,發現最終還是會通過在class_ro_t結構體中使用property_list_t存儲對應的propertyies。

    而在剛剛重寫的代碼中,我們可以找到這個property_list_t:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static?struct?/*_prop_list_t*/?{ ????unsigned?int?entsize;??//?sizeof(struct?_prop_t) ????unsigned?int?count_of_properties; ????struct?_prop_t?prop_list[1]; ????}?_OBJC_$_PROP_LIST_Sark?__attribute__?((used,?section?("__DATA,__objc_const")))?=?{ ????????sizeof(_prop_t), ????????1, ????????name }; static?struct?_class_ro_t?_OBJC_CLASS_RO_$_Sark?__attribute__?((used,?section?("__DATA,__objc_const")))?=?{ ????0,?__OFFSETOFIVAR__(struct?Sark,?_name),?sizeof(struct?Sark_IMPL),? ????(unsigned?int)0,? ????0,? ????"Sark", ????(const?struct?_method_list_t?*)&_OBJC_$_INSTANCE_METHODS_Sark, ????0,? ????(const?struct?_ivar_list_t?*)&_OBJC_$_INSTANCE_VARIABLES_Sark, ????0,? ????(const?struct?_prop_list_t?*)&_OBJC_$_PROP_LIST_Sark, };

    解惑

    1)為什么能夠正常運行,并調用到speak方法?

    1 2 3 id?cls?=?[Sark?class]; void?*obj?=?&cls; [(__bridge?id)obj?speak];

    obj被轉換成了一個指向Sark Class的指針,然后使用id轉換成了objc_object類型。這個時候的obj已經相當于一個Sark的實例對象(但是和使用[Sark new]生成的對象還是不一樣的),我們回想下Runtime的第二篇博文中objc_object結構體的構成就是一個指向Class的isa指針。

    這個時候我們再回想下上一篇博文中objc_msgSend的工作流程,在代碼中的obj指向的Sark Class中能夠找到speak方法,所以代碼能夠正常運行。

    2) 為什么self.name的輸出為?

    我們在測試代碼中加入一些調試代碼和Log如下:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 -?(void)speak {? ????unsigned?int?numberOfIvars?=?0; ????Ivar?*ivars?=?class_copyIvarList([self?class],?&numberOfIvars); ????for(const?Ivar?*p?=?ivars;?p?<?ivars+numberOfIvars;?p++)?{ ????????Ivar?const?ivar?=?*p; ????????ptrdiff_t?offset?=?ivar_getOffset(ivar); ????????const?char?*name?=?ivar_getName(ivar); ????????NSLog(@"Sark?ivar?name?=?%s,?offset?=?%td",?name,?offset); ????} ????NSLog(@"my?name?is?%p",?&_name); ????NSLog(@"my?name?is?%@",?*(&_name)); } @implementation?Test -?(instancetype)init { ????self?=?[super?init]; ????if?(self)?{ ????????NSLog(@"Test?instance?=?%@",?self); ????????void?*self2?=?(__bridge?void?*)self; ????????NSLog(@"Test?instance?pointer?=?%p",?&self2); ????????id?cls?=?[Sark?class]; ????????NSLog(@"Class?instance?address?=?%p",?cls); ????????void?*obj?=?&cls; ????????NSLog(@"Void?*obj?=?%@",?obj); ????????[(__bridge?id)obj?speak]; ????} ????return?self; } @end

    輸出結果如下:

    1 2 3 4 5 2014-11-11?00:56:02.464?Test[10475:1071029]?Test?instance?=?2014-11-11?00:56:02.464?Test[10475:1071029]?Test?instance?pointer?=?0x7fff5fbff7c8 2014-11-11?00:56:02.465?Test[10475:1071029]?Class?instance?address?=?0x1000023c8 2014-11-11?00:56:02.465?Test[10475:1071029]?Void?*obj?=?2014-11-11?00:56:02.465?Test[10475:1071029]?Sark?ivar?name?=?_name,?offset?=?8 2014-11-11?00:56:02.465?Test[10475:1071029]?my?name?is?0x7fff5fbff7c8 2014-11-11?00:56:02.465?Test[10475:1071029]?my?name?is

    Sark中Propertyname最終被轉換成了Ivar加入到了類的結構中,Runtime通過計算成員變量的地址偏移來尋找最終Ivar的地址,我們通過上述輸出結果,可以看到 Sark的對象指針地址加上Ivar的偏移量之后剛好指向的是Test對象指針地址。

    這里的原因主要是因為在C中,局部變量是存儲到內存的棧區,程序運行時棧的生長規律是從地址高到地址低。C語言到頭來講是一個順序運行的語言,隨著程序運行,棧中的地址依次往下走。

    看下圖,可以清楚的展示整個計算的過程:

    我們可以做一個另外的實驗,把Test Class 的init方法改為如下代碼:

    1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @interface?Father?:?NSObject @end @implementation?Father @end @implementation?Test -?(instancetype)init { ????self?=?[super?init]; ????if?(self)?{ ????????NSLog(@"Test?instance?=?%@",?self); ????????id?fatherCls?=?[Father?class]; ????????void?*father; ????????father?=?(void?*)&fatherCls; ????????id?cls?=?[Sark?class]; ????????void?*obj; ????????obj?=?(void?*)&cls; ????????[(__bridge?id)obj?speak]; ????} ????return?self; } @end

    你會發現這個時候的輸出變成了:

    1 2 3 4 2014-11-08?21:40:36.724?Test[4845:543231]?Test?instance?=?2014-11-08?21:40:36.725?Test[4845:543231]?ivar?name?=?_name,?offset?=?8 2014-11-08?21:40:36.726?Test[4845:543231]?Sark?instance?=?0x7fff5fbff7b8 2014-11-08?21:40:36.726?Test[4845:543231]?my?name?is?0x7fff5fbff7c0 2014-11-08?21:40:36.726?Test[4845:543231]?my?name?is

    關于C語言內存分配和使用的問題可參考這篇文章?http://www.th7.cn/Program/c/201212/114923.shtml。
    (via:Chun Tips)

    ?

    轉載于:https://www.cnblogs.com/oc-bowen/p/5427515.html

    總結

    以上是生活随笔為你收集整理的IOS-RunTime(刨根问底)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。