iOS底层原理探索-05-类的结构分析

811 阅读6分钟

《目录-iOS & OpenGL & OpenGL ES & Metal》
我们之前分析了对象,那么今天来分析一下创建对象的 类~

一、准备工作

探索环境:libObjc - 779.1
Person(自定义类)中声明一个成员变量、一个属性、一个方法、一个类方法

#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;
}

二、类的本质:类从哪里来

在oc中,所有的类都是可以用Class来接收的。

1、Class继承自objc_class

clang一下,看一下底层编译,Class 是继承自哪里的:

typedef struct objc_class *Class;

2、objc_class继承自objc_object

继续看一下objc_class

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来验证一下:

struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};

3、Class ISA

我们之前的探索:isaisa_t类型,为什么在objc_object中的isaClass类型呢?

1、万物皆对象,isa是可以用Class来接收的
2、在早期,isa的就是用来返回一个类,后来优化为返回nonpointer和纯净的isa,这里可能是延续了一个习惯 3、在ISA()方法(分析isa的文章中有提到)中可以看到,返回值是一个(Class)强转类型

4、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

5、NSObjectobjc_class的关系 (类)

objc_class对应的,其实是NSObject Class,是NSObject这个类。底层编译就会变成objc_class

  • NSObject(类)的本质是objc_class

三、类结构里存放了什么

从上面的分析,可以看出:类是一个结构体,里面存放了isa、superClass、cache、bits等。

1、Class ISA

isa指针,不仅实例对象中有,类对象中也有。占8字节

2、Class superclass

superclass父类,class* 本身也是一个指针。占8字节

3、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位

4、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中也有相关信息的属性)我们稍后来验证一下~

四、类的属性存储在哪

我们在第三部分,计算出从首地址偏移32位就能拿到bits。32对应的16进制是0x20,然后(class_data_bits_t *)强转,我们直接LLDB来操作一番(每次重新运行,内存地址可能就跟上一次不一样了):

我们看到bits里面有properties! 我们声明的属性和成员变量是不是存在这里呢?打印一下:

(property_array_t 其实是一个二维数组的类型。我们继续打印里面的信息)

的确有!但是,有没有发现这个里面的信息并不全,因为直接在bits里没有找到我们声明的成员变量的地方。

开启上帝视角:我们直接读ro!我们来看一下:

属性 - baseProperties

成员变量 - ivars

果然,在这里面都找到了!

五、类的方法存储在哪

1、类的实例方法

继续看ro下的baseMethodList

哇!list有4个,分别是:类的实例方法、属性帮我们生成的setter、getter方法,还有C++的方法。

但是,我们声明的 类方法 在哪里呢?

2、类的类方法

我们开启上帝视角:类方法在元类中,我们去验证一下:

类的类方法,的确在这里!

之前说过,万物皆对象,类也是个对象,对象的实例方法在类中;
那类方法可以理解为 类对象的实例方法,就在元类中。

六、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;
}


六、总结

今天通过LLDB一步步调试,虽然有点繁琐,但也验证了许多只知其一不知其二的地方:

  • 类的本质是个结构体(万对皆对象,类的本质是个对象,对象的本质是个结构体)

  • 类的结构里包含了 isa、superClass、cache、bits

  • Class是一个objc_class类型

  • objc_class继承自objc_object

  • 类的属性、成员变量、方法、协议等 存储在class_ro_t 这个结构体里

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

  • 类和元类的创建时机是在编译时(探索isa时,在最后验证过)