iOS OC 類原理一

1. 元類的建立時機

前面簡單提到元類的建立時機是在編譯器,今天咱們經過一下兩種方法來驗證一下:c++

1.1 打印 元類的指針

首先看下面代碼: 數組

main函數以前打印斷點,bash

經過p/x 打印指針,若是能得到指針, 說明已經在內存中申請了內存空間app

而後x/4gx打印的內存結構,獲得類 的isa,而後isa & 掩碼 ISA_MASK得到元類isa,若是這個過程當中能正常打印出相應的指針,則能簡單驗證元類的建立是在編譯期建立的,打印結果以下:函數

1.2 command + B生成可執行文件,而後使用 MachoView 打開程序二進制可執行文件查看

由此,能夠驗證元類是在編譯期建立的,在運行項目alloc以前已經被建立出來了優化

2. 指針偏移

2.1 普通指針 值拷貝
int a = 10; //
        int b = 10; //
        LGNSLog(@"%d -- %p",a,&a);
        LGNSLog(@"%d -- %p",b,&b);
//      KC打印: 10 -- 0x7ffeefbff45c
//      KC打印: 10 -- 0x7ffeefbff458
複製代碼
2.2 指針拷貝
// 對象 - 指針拷貝
        LGPerson *p1 = [LGPerson alloc];
        LGPerson *p2 = [LGPerson alloc];
        LGNSLog(@"%@ -- %p",p1,&p1);
        LGNSLog(@"%@ -- %p",p2,&p2);
//      KC打印: <LGPerson: 0x100753be0> -- 0x7ffeefbff450
//      KC打印: <LGPerson: 0x10074e740> -- 0x7ffeefbff448
複製代碼
2.3 指針偏移
// 數組指針
        int c[4] = {1,2,3,4};
        int *d   = c;
        NSLog(@"%p - %p - %p",&c,&c[0],&c[1]);
        NSLog(@"%p - %p - %p",d,d+1,d+2);

        for (int i = 0; i<4; i++) {
            // int value = c[i];
            int value = *(d+i);
            LGNSLog(@"%d",value);
        }
        NSLog(@"指針 - 內存偏移");
//      0x7ffeefbff470 - 0x7ffeefbff470 - 0x7ffeefbff474
//      0x7ffeefbff470 - 0x7ffeefbff474 - 0x7ffeefbff478
//      KC打印: 1
//      KC打印: 2
//      KC打印: 3
//      KC打印: 4
複製代碼

首地址數組的第一個元素的地址,&c[0]&c[1],相差一個元素的大小,指針d + 1,至關於偏移一個所佔位數的元素的大小ui

3. 類的結構

3.1 類的結構是什麼?

經過clang查看看下面代碼在c++文件中的編譯:this

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);

        NSLog(@"%@ - %p",person,pClass);
    }
    return 0;
}
複製代碼
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        // id, SEL
        LGPerson *person = ((LGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("LGPerson"), sel_registerName("alloc"));
        
        Class pClass = object_getClass(person);
        
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_5s_4100t0cd5rn_d7gx0n5wqh8w0000gn_T_main_60f7a3_mi_9,person,pClass);
    }
    return 0;
}
複製代碼

咱們探究的的結構,就是Class,在cpp文件中不難發現結構是:atom

typedef struct objc_class *Class;
複製代碼

能夠看出,objc_class類型的 結構體。spa

struct objc_object {
    Class _Nonnull isa __attribute__((deprecated));
};
複製代碼

咱們知道萬物皆對象,objc_class 繼承自objc_object,那麼咱們經過下圖方法查看objc_class的源碼:

struct objc_class : objc_object {
    // Class ISA; // 8
    Class superclass; // 8
    cache_t cache;    // 16 不是8         // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags

    class_rw_t *data() { 
        return bits.data();
    }
    ··· // 方法和函數
}
複製代碼

源碼中能夠看到,有個隱藏的Class isa(爲何有個隱藏的Class isa?), 隱藏的屬性必然是來自於繼承繼承objc_object ,看objc_object源碼:

object源碼:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
複製代碼

那麼NSObject的定義是什麼樣的呢?

其實NSObject的定義是結構體的一種仿寫:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
複製代碼

問: 爲何isaClass類型?

答:萬物皆對象,Clss自己繼承自object,用來接收isa能夠的,早期調用isa就是爲了返回, 後期優化了 nonpointer isa

問:objc_classNSObject的關係? objc_objectNSObject的關係?

NSObject 是一種objc_class 的類型,NSObject也是一個類class,底層也是objc_classOC底層封裝的Cobjc_objectNSObject底層編譯的寫法。 objc_objectobjc_class是底層的實現,對應當前NSObject(Class)NSObject

3.2 類的結構分析

一般咱們會在中定義屬性成員變量方法,

@interface LGPerson : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

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

@end
複製代碼

那麼在中是如何存儲這些定義的屬性 成員變量 方法的呢? 接下來咱們來研究一下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        LGPerson *person = [LGPerson alloc];
        Class pClass     = object_getClass(person);

        NSLog(@"%@ - %p",person,pClass);
    }
    return 0;
}
複製代碼

經過x/4gx pClass打印結構,經過前面的查看源碼得知以下圖:

objc_classClass ISAClass superclass分別佔8字節cache_t cache16字節

struct cache_t {
    struct bucket_t *_buckets; // 8
    mask_t _mask;  // 4  uint32_t mask_t  
    mask_t _occupied; // 4

public: // 下面是函數,函數不佔內存
    struct bucket_t *buckets();
    // 方法 
    ···
};

複製代碼

由於objc_classcache_t cache結構體,而不是結構體指針佔(結構體指針佔8字節), 因此cache_t cache佔內存8 + 4 + 4 = 16字節

猜想:屬性 成員變量存儲在class_data_bits_t bits中,經過指針偏移(偏移原理類比爲數組),偏移32字節獲取class_data_bits_t bits

探索以下:

pClass首地址0x100001278 + 32 獲得 0x100001298(16進制)bits,經過bits.data() 獲得class_rw_t *data(),打印以下:

class_rw_t的結構以下:

struct class_rw_t {
    // Be warned that Symbolication knows the layout of this structure.
    uint32_t flags;
    uint32_t version;

    const class_ro_t *ro;

    method_array_t methods;
    property_array_t properties;
    protocol_array_t protocols;

    Class firstSubclass;
    Class nextSiblingClass;

    char *demangledName;

#if SUPPORT_INDEXED_ISA
    uint32_t index;
#endif

    void setFlags(uint32_t set) 
    {
        OSAtomicOr32Barrier(set, &flags);
    }

    void clearFlags(uint32_t clear) 
    {
        OSAtomicXor32Barrier(clear, &flags);
    }

    // set and clear must not overlap
    void changeFlags(uint32_t set, uint32_t clear) 
    {
        assert((set & clear) == 0);

        uint32_t oldf, newf;
        do {
            oldf = flags;
            newf = (oldf | set) & ~clear;
        } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
    }
};
複製代碼

打印*data():

經過命名,猜想 屬性應該存儲在 properties中,打印 properties,而後並打印其中 list:

同理打印 methods,一系列操做後以下:

由此咱們探究出了屬性 方法的存儲位置,那麼成員變量存儲在什麼地方呢?

經過查看struct class_rw_t中的const class_ro_t *ro,

struct class_ro_t {
    uint32_t flags;
    uint32_t instanceStart;
    uint32_t instanceSize;
#ifdef __LP64__
    uint32_t reserved;
#endif

    const uint8_t * ivarLayout;
    
    const char * name;
    method_list_t * baseMethodList;
    protocol_list_t * baseProtocols;
    const ivar_list_t * ivars;

    const uint8_t * weakIvarLayout;
    property_list_t *baseProperties;

    method_list_t *baseMethods() const {
        return baseMethodList;
    }
};
複製代碼

裏面分別有method_list_t * baseMethodList property_list_t *baseProperties const ivar_list_t * ivars,咱們猜想方法 屬性 成員變量分別存儲在對應的變量中,打印ro結果以下:

由此能夠看出LGPerson僅有的一個成員變量 nickName存儲在bit.data()中的robaseProperties中,

那麼爲何bit.data()property_array_t properties也等打印出成員變量呢?暫時先拋出個問題。

接下來咱們用一樣的方法分別打印ivars baseMethodList,如圖:

baseMethodList打印:

打印出count = 2,分別打印ivars成員變量,分別爲hobby _nickName,再次驗證了@property生成的屬性,在系統底層會自動生成_屬性成員變量,而且會自動生成setter getter

問題:從baseMethodList中並未打印出類方法 sayHappy,那麼類方法存儲在什麼地方呢?

猜想: 實例方法存在 中,那麼其實 也是元類建立出來的類對象類方法應該存在元類中。

經過下面代碼,分別在元類中打印對象方法類方法

void testInstanceMethod_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));

    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}


打印結果
2019-12-29 12:28:17.714554+0800 LGTest[799:13098] 0x100002198-0x0-0x0-0x100002130
2019-12-29 12:28:17.715541+0800 LGTest[799:13098] testInstanceMethod_classToMetaclass

複製代碼

由打印結果看出,對象方法存在於中,不存在於元類中,類方法存在於元類中,不存在於中。

經過對結構的分析,得出:成員變量存在ivars中,屬性存儲在baseProperties中,對象方法存儲在裏面,類方法存儲在元類裏。
相關文章
相關標籤/搜索