iOS RunTime 學習記錄1_類和物件

weixin_34292287發表於2017-12-22

前言:我是參考 南峰子 的部落格加上自己理解寫的,原著專輯大家自己可看:http://southpeak.github.io/categories/objectivec/

iOS中的類(Class)和物件(objc)都是在RunTime 中實現的,自己學習人家的部落格做點筆記,提醒一下自己:

類(Class)

為什麼RunTime和這些扯上關係了,好吧,看看人家的目錄結構你會發現在OC程式碼的執行都是離不開RunTime。而程式碼無非就是類與方法,所以執行時定義了類和方法,並控制他們的執行方式。目錄結構如下:

1211077-805aa5c1eec621dc.png
Paste_Image.png

Objective-C 中的類使用的是objc_class 這個結構體表示,在目錄objc/runtime.h中檢視:

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結構本身也是一個物件,裡面的isa指標指向的就是這個物件對應的類,但是Class物件指向的這個類叫做元類metaClass(下面介紹),在這個類呼叫類方法就可以看出元類的作用了。

super_class

父類,如果該類已經是最頂層的根類(如NSObject或NSProxy),則super_class為NULL

name

類名

cache

struct objc_cache {
    unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
    unsigned int occupied                                    OBJC2_UNAVAILABLE;
    Method buckets[1]                                        OBJC2_UNAVAILABLE;
};
  • mask
    一個整數,指定分配的快取bucket的總數。在方法查詢過程中,Objective-C runtime使用這個欄位來確定開始線性查詢陣列的索引位置。指向方法selector的指標與該欄位做一個AND位操作(index = (mask & selector))。這可以作為一個簡單的hash雜湊演算法
  • occupied
    一個整數,指定實際佔用的快取bucket的總數
  • buckets
    指向Method資料結構指標的陣列。這個陣列可能包含不超過mask+1個元素。需要注意的是,指標可能是NULL,表示這個快取bucket沒有被佔用,另外被佔用的bucket可能是不連續的。這個陣列可能會隨著時間而增長

應這個訊息的物件。在實際使用中,這個物件只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次訊息來時,我們都是methodLists中遍歷一遍,效能勢必很差。這時,cache就派上用場了。在我們每次呼叫過一個方法後,這個方法就會被快取到cache列表中,下次呼叫的時候runtime就會優先去cache中查詢,如果cache沒有,才去methodLists中查詢方法。這樣,對於那些經常用到的方法的呼叫,但提高了呼叫的效率。比如:

NSArray *array = [[NSArray alloc] init];

方法執行流程
1.[NSArray alloc]先被執行。因為NSArray沒有+alloc方法,於是去父類NSObject去查詢。
2.檢測NSObject是否響應+alloc方法,發現響應,於是檢測NSArray類,並根據其所需的記憶體空間大小開始分配記憶體空間,然後把isa指標指向NSArray類。同時,+alloc也被加進cache列表裡面。
3.接著,執行-init方法,如果NSArray響應該方法,則直接將其加入cache;如果不響應,則去父類查詢。
4,在後期的操作中,如果再以[[NSArray alloc] init]這種方式來建立陣列,則會直接從cache中取出相應的方法,直接呼叫。

物件(objc)

在objc/objc.h 中我們看到如下定義

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

可以發現物件結構體就一個isa指標(剛才不是說Class也是元類(metaClass)的物件,所以Class 資料結構中也有一個isa指標)。它指向這個物件對應的類Class上。

NSObject類的alloc和allocWithZone:方法使用函式建立物件時,會呼叫class_createInstance來建立這個物件結構體objc_objc。

當我們這個呼叫這個物件的方法selector,RunTime 會通過isa指標找到這個物件對應的類,然後在這個類的方法列表(objc_method_list)中查詢這個方法selector,如果找不到就往這個類的父類上面找。找到後執行這個方法。

特殊的類--元類(Meta Class)

上面我們說了,Class本身也是各物件,它也有isa指標,那麼它的isa指向了什麼地方了?答案就是Meta Class。

meta-class是一個類物件的類。

當我們向一個物件傳送訊息時,runtime會在這個物件所屬的這個類的方法列表中查詢方法;而向一個類傳送訊息時,會在這個類的meta-class的方法列表中查詢。

比如下面我們初始化NSArray用類方法實現:

NSArray *array = [NSArray array];

這裡 +array 方法被NSArray呼叫,這裡NSArray是個類,也是個物件objc_object,這個物件不是通過alloc或allocWithZone:方法建立的,所以沒用通過NSArray 父類NSObject最後通過Class 的class_createInstance建立objc_object。它本身就是objc_object,也有isa指標,為了找到這個方法,這個指標指向那了,其實就是指向NSArray的元類。

meta-class之所以重要,是因為它儲存著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同。

再深入一下,meta-class也是一個類,也可以向它傳送一個訊息,那麼它的isa又是指向什麼呢?為了不讓這種結構無限延伸下去,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指標是指向它自己。這樣就形成了一個完美的閉環。

結構圖:
[圖片上傳失敗...(image-70292-1513910965638)]

其中 Instance of SubClass是某個類的例項物件,它的isa 指標指向SubClass類,如果把Subclass類看成一個物件,那麼它的isa指向指向Subclass的元類(meta)。其中元類和元類之間繼承關係,不一定依賴於類於類之間繼承,但是最後指向了RootClass 根類和RootClass meta根類的元類。其中根類的元類(RootClass meta)又指向根類自己Root Class。

下面有個小例子,幫組自己理解一下

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //例項方法-(Class)class,是獲取這個例項物件對應的類
    //類方法+(Class)class 是獲取這個類(本身是個例項物件)對應的類
    
    NSLog(@"這個類名是:%@ 父類名是:%@  父父類名:%@ 父父父類:%@",[self class],[self superclass],[[self superclass] superclass],[[[self superclass] superclass] superclass]);
    
    NSLog(@"NSObject這個類名:%@ 的地址:%p",[NSObject class],[NSObject class]);
    NSLog(@"NSOject meta 地址:%p",objc_getClass((__bridge void *)[NSObject class]));
    
    NSLog(@"這個類的地址:%p",[ViewController class]);
    NSLog(@"這個物件地址:%p",self);
    
    
    Class prClass = [self class];
    /**
     *  for迴圈列印物件的isa地址
     *  第一次列印的是這個物件對應的類(ViewController)的地址,也就是物件isa地址會發現其指向。
     *  第二次列印的是ViewController看成物件後對應類的地址,也就是ViewController 的isa指向地址,也就是ViewController的元類。
     *  第三次,第四次是元類的元類.....
     *
     */
    for (int i = 0; i< 5; i++) {
        
        NSLog(@"迴圈第%i次列印物件isa地址:%p",i+1,prClass);
        //objc_getClass得到一個類,根據類名(字串)得到。如果類再
        prClass = objc_getClass((__bridge void *)prClass);
    }
}

輸出結果:

**2016-07-27 11:45:40.088 RunTime_****類與物件****[73165:31955041] ****這個類名是:****ViewController ****父類名是:****UIViewController  ****父父類名:****UIResponder ****父父父類****:NSObject**
**2016-07-27 11:45:40.088 RunTime_****類與物件****[73165:31955041] NSObject****這個類名:****NSObject ****的地址:****0x109145170**
**2016-07-27 11:45:40.089 RunTime_****類與物件****[73165:31955041] NSOject meta ****地址:****0x0**
**2016-07-27 11:45:40.089 RunTime_****類與物件****[73165:31955041] ****這個類的地址:****0x1088e7d58**
**2016-07-27 11:46:54.516 RunTime_****類與物件****[73165:31955041] ****這個物件地址:****0x7facd249fb40**
**2016-07-27 11:47:21.049 RunTime_****類與物件****[73165:31955041] ****迴圈第****1****次列印物件****isa****地址****:0x1088e7d58**
**2016-07-27 11:47:54.710 RunTime_****類與物件****[73165:31955041] ****迴圈第****2****次列印物件****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****類與物件****[73165:31955041] ****迴圈第****3****次列印物件****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****類與物件****[73165:31955041] ****迴圈第****4****次列印物件****isa****地址****:0x0**
**2016-07-27 11:47:54.711 RunTime_****類與物件****[73165:31955041] ****迴圈第****5****次列印物件****isa****地址****:0x0**

程式碼註釋中我說明了這種關係。我畫了一個簡易的圖如下:

1211077-8622532d9e42d2be.jpg
元類關係.jpg

好吧,第一課學習到這吧,我要改程式碼去了! 屌絲........

相關文章