美文网首页
02-OC对象的本质

02-OC对象的本质

作者: BLUEVIPIOS_ | 来源:发表于2021-08-17 01:25 被阅读0次

    一个NSObject对象占用多少内存.

    我们平时编写的Objective-C代码,底层实现其实都是C\C++代码.
    因为C语言不能通过写一个函数,去跳转到任意的指针,汇编可以利用寄存器实现,C语言使用的是“静态绑定”,也就是说,在编译时就能决定运行时所应调用的函数,如果待调用的函数地址无法硬编码在指令之中,那就要在运行期读取出来,使用“动态绑定”。而c语言是面向过程,由编译器进行处理,显然无法实现这样的需求。在运行的时候会进行特殊操作访问不同的内存空间,这就用到了runtime运行时,因此oc具备动态特性。

    1.将Object-C 语言转换为C++:

    xcrun -sdk iphonesimulator clang -rewrite-objc main.m
    
    @autoreleasepool {
            YJPerson * P = [YJPerson new];
            [P run];    
    }
    //编译后 (环境依赖部分代码暂不考虑 此处没有粘贴出来)
    
    #pragma clang assume_nonnull begin
    #ifndef _REWRITER_typedef_YJPerson
    #define _REWRITER_typedef_YJPerson
    typedef struct objc_object YJPerson;
    typedef struct {} _objc_exc_YJPerson;
    #endif
    
    struct YJPerson_IMPL {
        struct NSObject_IMPL NSObject_IVARS;   //等价于  Class isa
    };
    
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            YJPerson * P = ((YJPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("YJPerson"), sel_registerName("new"));
            ((void (*)(id, SEL))(void *)objc_msgSend)((id)P, sel_registerName("run"));
    }
    void runImp (id self ,SEL _cmd){
    }
    
    

    将.m文件转换为C++文件 即可得出

    • 对象的本质:结构体
    • 结构体占用内存大小
      1.这个结构体只有一个成员,isa指针,而指针在64位架构中占8个字节。也就是说一个NSObjec对象所占用的内存是8个字节
      2.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
    }
    

    一个NSObject对象占用多少内存: NSObjcet实际上是只有一个名为isa的指针的结构体,因此占用一个指针变量所占用的内存空间大小,如果64bit占用8个字节,如果32bit占用4个字节。

    class_getInstanceSize([self class])
    
    • 内存结构
       YGPerson * p1 = [YGPerson alloc];
        p1.age = 10;
        p1.nickName = @"123456";
    
    WeChatbcce812f213eca2d008990181f56c533.png

    实时查看内存数据有俩种方式
    方式一:通过打断点。 Debug Workflow -> viewMemory address中输入p1的地址
    方式二:通过lldb指令xcode自带的调试器

    OC的类信息

    • instance对象(实例对象)
      1. isa指针
      2. 其他成员变量的值
      3. 实例对象(不包含实现方法)
    • class对象(类对象)
      1.每个类在内存中有且只有一个class对象
      验证方式1
       NSObject *obj1 = [[NSObject alloc] init];
        Class objClass1 = [obj1 class];
        Class objClass2 = [NSObject class];
        //class 方法返回的一直是class对象,类对象,而不是元类对象
        Class objClass3 = [[NSObject class] class];
        Class objClass4 = object_getClass(obj1);
        NSLog(@"%p-%p-%p-%p",objClass1,objClass2,objClass3,objClass4);
    //0x7fff80030660-0x7fff80030660-0x7fff80030660-0x7fff80030660 
    

    验证方式2

    WeChatc016b0f33f7d8addace59ddb45efb760.png
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    
    • isa指针
    • superclass指针
    • 类的属性信息(@property)
    • 类的成员变量信息(ivar)
    • 类的对象方法信息(instance method),类的协议信息(protocol)
    • 缓存 objc_cache

    ...

    2.meta-class对象

    • meta-class对象(元类对象)
    Class metaClass = object_getClass([NSObject class]);
    
    • isa指针
    • superclass指针
    • 类的类方法信息(class method)
    • ......

    总结isa、superclass

    isa、superclass
    
    instance的isa指向class
    class的isa指向meta-class
    meta-class的isa指向基类的meta-class,基类的isa指向自己
    class的superclass指向父类的class,如果没有父类,superclass指针为nil
    meta-class的superclass指向父类的meta-class,基类的meta-class的superclass指向基类的class
    instance调用对象方法的轨迹,isa找到class,方法不存在,就通过superclass找父类
    class调用类方法的轨迹,isa找meta-class,方法不存在,就通过superclass找父类
    
    
    • 代码验证1:
            YGPerson *instanceObj = [[YGPerson alloc] init];
            // 第一条线 (instance的isa值是Class对象)
            Class classCls  = object_getClass(instanceObj);
            // 第二条线 (Class对象的isa值是meta-class)
            Class metaCls = object_getClass(classCls);
            // 第三条线 (meta-class的Superclass 是 RootClass的meta-class)
            Class rootMetaCls0 = class_getSuperclass(metaCls);
            // 第四条线 (meta-class的isa值是RootClass 的meta-class)
            Class rootMetaCls1 = object_getClass(metaCls);
            //RootClass的meta-class
            Class rootMetaCls = object_getClass(rootMetaCls0);
            
            NSLog(@"\n instanceObj = %p \n classCls = %p \n metaCls = %p \n rootMetaCls0 = %p \n rootMetaCls1 = %p \n rootMetaCls  = %p\n", instanceObj, classCls, metaCls, rootMetaCls0, rootMetaCls1, rootMetaCls);
    
            NSLog(@"\n instanceObj-isa = %p \n classCls-isa = %p \n metaCls-isa = %p \n rootMetaCls0-isa = %p \n", [instanceObj valueForKey:@"isa"], [classCls valueForKey:@"isa"], [metaCls valueForKey:@"isa"], [rootMetaCls0 valueForKey:@"isa"]);
    
    
    2021-08-17 01:19:37.463948+0800 OC对象原理[32945:2611851] 
     instanceObj = 0x6000037f7e00 
     classCls = 0x100ac1790 
     metaCls = 0x100ac1768 
     rootMetaCls0 = 0x7fff80030638 
     rootMetaCls1 = 0x7fff80030638 
     rootMetaCls  = 0x7fff80030638
    2021-08-17 01:19:38.342631+0800 OC对象原理[32945:2611851] 
     instanceObj-isa = 0x100ac1790 
     classCls-isa = 0x100ac1768 
     metaCls-isa = 0x7fff80030638 
     rootMetaCls0-isa = 0x7fff80030638
    
    • lldb验证2:
    inline Class
    isa_t::getClass(MAYBE_UNUSED_AUTHENTICATED_PARAM bool authenticated) {
    #if SUPPORT_INDEXED_ISA
        return cls;
    #else
    
        uintptr_t clsbits = bits;
    
    #   if __has_feature(ptrauth_calls)
    #       if ISA_SIGNING_AUTH_MODE == ISA_SIGNING_AUTH
        // Most callers aren't security critical, so skip the
        // authentication unless they ask for it. Message sending and
        // cache filling are protected by the auth code in msgSend.
        if (authenticated) {
            // Mask off all bits besides the class pointer and signature.
            clsbits &= ISA_MASK;
            if (clsbits == 0)
                return Nil;
            clsbits = (uintptr_t)ptrauth_auth_data((void *)clsbits, ISA_SIGNING_KEY, ptrauth_blend_discriminator(this, ISA_SIGNING_DISCRIMINATOR));
        } else {
            // If not authenticating, strip using the precomputed class mask.
            clsbits &= objc_debug_isa_class_mask;
        }
    #       else
        // If not authenticating, strip using the precomputed class mask.
        clsbits &= objc_debug_isa_class_mask;
    #       endif
    
    #   else
        clsbits &= ISA_MASK;
    #   endif
    
        return (Class)clsbits;
    #endif
    }
    // 重要运算符
    clsbits &= ISA_MASK;
    
    // 重要 洪
    #     define ISA_MASK        0x007ffffffffffff8ULL
    这里要验证 需要lldb指令 打印地址 这些地址只是编译优化后的地址,
    获取真地址需要  &p & ISA_MASK 运算。
    以上是 objc源代码
    

    面试题总结:

    一个NSObject对象占用多少内存?

    答:一个指针变量所占用的大小(64bit占8个字节,32bit占4个字节)

    对象的isa指针指向哪里?

    instance对象的isa指针指向class对象,class对象的isa指针指向meta-class对象,meta-class对象的isa指针指向基类的meta-class对象,基类自己的isa指针也指向自己。

    OC的类信息存放在哪里?

    成员变量的具体值存放在instance对象。对象方法,协议,属性,成员变量信息存放在class对象。类方法信息存放在meta-class对象。

    相关文章

      网友评论

          本文标题:02-OC对象的本质

          本文链接:https://www.haomeiwen.com/subject/rruvbltx.html