02-OC对象的本质

一个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  {
#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对象的本质)