iOS底層原理探索-05-類的結構分析

《目錄-iOS & OpenGL & OpenGL ES & Metal》數組

咱們以前分析了對象,那麼今天來分析一下建立對象的 類~緩存

1、準備工做

探索環境:libObjc - 779.1
Person(自定義類)中聲明一個成員變量、一個屬性、一個方法、一個類方法markdown

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface Person : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

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

@end

@implementation Person
- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end



int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Person *person = [Person alloc];
        //拿到person這個對象的類
        Class pClass = object_getClass(person);
        NSLog(@"%@ - %p",person,pClass);
         
    }
    return 0;
}
複製代碼

2、類的本質:類從哪裏來

在oc中,全部的類都是能夠用Class來接收的。app

一、Class繼承自objc_class

clang一下,看一下底層編譯,Class 是繼承自哪裏的:函數

typedef struct objc_class *Class;
複製代碼

二、objc_class繼承自objc_object

繼續看一下objc_classpost

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

這個方法好像被棄用了,那咱們去源碼libObjc中找一下objc_class,源碼裏有幾個關於它的方法,大部分都被棄用了,咱們找到了一個可用的:優化

struct objc_class : objc_object {
    // Class ISA;  //這裏是一個隱藏屬性
    Class superclass;  
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    
	···下面是這個結構體的一些方法和函數,暫時用不到,省略掉···
    
}
複製代碼

咱們看到,這裏面有一個隱藏的isa,那它必然是繼承自父類的一個屬性,咱們繼續看一下objc_object來驗證一下:ui

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

三、Class ISA

咱們以前的探索:isaisa_t類型,爲何在objc_object中的isaClass類型呢?this

一、萬物皆對象,isa是能夠用Class來接收的
二、在早期,isa的就是用來返回一個類,後來優化爲返回nonpointer和純淨的isa,這裏多是延續了一個習慣 三、在ISA()方法(分析isa的文章中有提到)中能夠看到,返回值是一個(Class)強轉類型atom

四、NSObjectobjc_object的關係 (對象)

咱們知道在oc中,萬物皆對象!其實objc_object就是一切的鼻祖! NSObject算是objc_object的一個仿寫、衍生類,結構按理來講應該和objc_object是如出一轍的,驗證一下:

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

也能夠這麼說,objc_object是c,NSObject是oc,NSObject是對objc_object的封裝,到底層編譯仍是會變成objc_object

  • NSObject(對象)的本質是objc_object

五、NSObjectobjc_class的關係 (類)

objc_class對應的,實際上是NSObject Class,是NSObject這個類。底層編譯就會變成objc_class

  • NSObject(類)的本質是objc_class

3、類結構裏存放了什麼

從上面的分析,能夠看出:類是一個結構體,裏面存放了isa、superClass、cache、bits等。

一、Class ISA

isa指針,不只實例對象中有,類對象中也有。佔8字節

二、Class superclass

superclass父類,class* 自己也是一個指針。佔8字節

三、cache_t cache

cache緩存,追蹤進去看一下cache_t結構體的類型,而不是結構體指針類型(佔8位),就須要計算一下了:

struct cache_t {
    struct bucket_t *_buckets; // 結構體*類型=對象,8
    mask_t _mask;  // int32位類型,4
    mask_t _occupied; // int32位類型,4
    
    ···一些方法函數,不佔內存,省略···
    
}

typedef uint32_t mask_t; 
複製代碼

cache_t的類型,佔16位

四、class_data_bits_t bits

bits實際上是用來存儲數據的,咱們在前面計算內存大小,是爲了在內存中偏移,直接拿到bits,來驗證裏面是否存放了咱們在最前面準備工做中的屬性、方法。

objc_class中,前面省略了一個方法data(),這個是class_rw_t類型的結構體

struct objc_class : objc_object {
  
  	//···省略前面
    
    class_data_bits_t bits;     

    class_rw_t *data() { 
        return bits.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;
    
    //···省略後面
}
複製代碼

偷偷開個上帝視角,看一下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;
    }
};
複製代碼

咱們能夠看出,這裏面實際上是經過bits來存儲咱們的methodspropertiesprotocols等等信息的。(ro中也有相關信息的屬性)咱們稍後來驗證一下~

4、類的屬性存儲在哪

咱們在第三部分,計算出從首地址偏移32位就能拿到bits。32對應的16進制是0x20,而後(class_data_bits_t *)強轉,咱們直接LLDB來操做一番(每次從新運行,內存地址可能就跟上一次不同了):

咱們看到bits裏面有properties! 咱們聲明的屬性和成員變量是否是存在這裏呢?打印一下:

(property_array_t 實際上是一個二維數組的類型。咱們繼續打印裏面的信息)

的確有!可是,有沒有發現這個裏面的信息並不全,由於直接在bits裏沒有找到咱們聲明的成員變量的地方。

開啓上帝視角:咱們直接讀ro!咱們來看一下:

屬性 - baseProperties

成員變量 - ivars

果真,在這裏面都找到了!

5、類的方法存儲在哪

一、類的實例方法

繼續看ro下的baseMethodList

哇!list有4個,分別是:類的實例方法、屬性幫咱們生成的setter、getter方法,還有C++的方法。

可是,咱們聲明的 類方法 在哪裏呢?

二、類的類方法

咱們開啓上帝視角:類方法在元類中,咱們去驗證一下:

類的類方法,的確在這裏!

以前說過,萬物皆對象,類也是個對象,對象的實例方法在類中;
那類方法能夠理解爲 類對象的實例方法,就在元類中。

6、API驗證猜測

這裏瞭解便可,直接把源碼貼出來,有興趣的小夥伴能夠自行運行:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>

@interface Person : NSObject{
    NSString *hobby;
}

@property (nonatomic, copy) NSString *nickName;

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

@end

@implementation Person
- (void)sayHello{
    NSLog(@"LGPerson say : Hello!!!");
}

+ (void)sayHappy{
    NSLog(@"LGPerson say : Happy!!!");
}
@end



void testObjc_copyIvar_copyProperies(Class pClass){
    
    unsigned int count = 0;
    Ivar *ivars = class_copyIvarList(pClass, &count);
    for (unsigned int i=0; i < count; i++) {
        Ivar const ivar = ivars[i];
        //獲取實例變量名
        const char*cName = ivar_getName(ivar);
        NSString *ivarName = [NSString stringWithUTF8String:cName];
        NSLog(@"class_copyIvarList:%@",ivarName);
    }
    free(ivars);

    unsigned int pCount = 0;
    objc_property_t *properties = class_copyPropertyList(pClass, &pCount);
    for (unsigned int i=0; i < pCount; i++) {
        objc_property_t const property = properties[i];
        //獲取屬性名
        NSString *propertyName = [NSString stringWithUTF8String:property_getName(property)];
        //獲取屬性值
        NSLog(@"class_copyProperiesList:%@",propertyName);
    }
    free(properties);
}

void testObjc_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));
        
        NSLog(@"Method, name: %@", key);
    }
    free(methods);
}

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__);
}

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

    Method method3 = class_getClassMethod(pClass, @selector(sayHappy));
    Method method4 = class_getClassMethod(metaClass, @selector(sayHappy)); // ?
    
    NSLog(@"%p-%p-%p-%p",method1,method2,method3,method4);
    NSLog(@"%s",__func__);
}

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

    IMP imp3 = class_getMethodImplementation(pClass, @selector(sayHappy));
    IMP imp4 = class_getMethodImplementation(metaClass, @selector(sayHappy));

    NSLog(@"%p-%p-%p-%p",imp1,imp2,imp3,imp4);
    NSLog(@"%s",__func__);
}



int main(int argc, const char * argv[]) {
    @autoreleasepool {
         

        Person *person = [Person alloc];
        Class pClass = object_getClass(person);
        
        
        testObjc_copyIvar_copyProperies(pClass);
        testObjc_copyMethodList(pClass);

        testInstanceMethod_classToMetaclass(pClass);
        testClassMethod_classToMetaclass(pClass);
        
        
        NSLog(@"Hello, World!");
    }
    return 0;
}


複製代碼

6、總結

今天經過LLDB一步步調試,雖然有點繁瑣,但也驗證了許多隻知其一不知其二的地方:

  • 類的本質是個結構體(萬對皆對象,類的本質是個對象,對象的本質是個結構體)

  • 類的結構裏包含了 isa、superClass、cache、bits

  • Class是一個objc_class類型

  • objc_class繼承自objc_object

  • 類的屬性、成員變量、方法、協議等 存儲在class_ro_t 這個結構體裏

  • 類的類方法,存在元類的class_ro_t

  • 類和元類的建立時機是在編譯時(探索isa時,在最後驗證過)

相關文章
相關標籤/搜索