底层原理-03-对象的本质和isa探究

1.对象的本质是什么

作为一个iOS开发,我们在进行日常使用 Objective-C开发时都知道这是苹果对c/c++的封装,事实上任何一个高级语言都是对基本语言的封装。方便我们开发者提高效率,在规定的规则下减少错误产生。

1.1实例对象探究

我们在main.m初始化一个对象

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSObject *object = [[NSObject alloc]init];
        NSLog(@"%@", object);

    }
    return 0;
}

复制代码

我们点击查看NSObject类包含了一个isa成员变量

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}
复制代码

可以知道
之前我们通过探索alloc流程知道会在堆开辟一个内存存放我们这个类的信息。我们通过Clang编译器编译这个main.m看下能得到什么
我们使用终端使用Clang编译器命令编译clang -rewrite-objc main.m -o main.cpp 把目标文件编译成c++文件 UIKit报错问题 由于不知道跑哪个平台我们可以指定对应的设备arm
xcode安装的时候顺带安装了xcrun命令,xcrun命令在clang的基础上进行了 一些封装,要更好用一些
xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-main.cpp (模拟器)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- main.cpp (手机)
得到main.cpp文件,使用xcode打开,得到了编译好的代码,我们根据int main全局搜索得到

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSObject *object = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_vh_px38rsqn3tsdd6jwr43k073h0000gp_T_main_5eb878_mi_0, object);

    }
    return 0;
}
复制代码

就是我们编译器转换为c/c++的形式

struct NSObject_IMPL {
	Class isa;
};
复制代码

和上面的@interface NSObject很相似都是包含一个 Class isa指针对象
通过查看源码可知

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
复制代码
  • NSObject的实例对象本质是一个结构体,结构体中包含一个isa指针,指向 object_class
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;
/* Use `Class` instead of `struct objc_class *` */
复制代码

我创建了2个类,继承关系。LGTeach继承LGPerson 他们有各自的属性和方法。代码如下

@interface LGTeacher : LGPerson
{
    int _age;
    int _heights;
    int _no;
}
@property (nonatomic,strong) NSString *address;

- (void)teacherSay;
@end
-    
@interface LGPerson : NSObject

@property (nonatomic,strong) NSString *name;
@property (nonatomic,strong) NSString *nickName;
@property (nonatomic,strong) NSString *nickName1;

@property (nonatomic) int hobby;
@property (nonatomic) long height;

- (void)saySomething;
- (void)saySomething1;
@end
        LGTeacher *techer = [[LGTeacher alloc]init];
        LGPerson *person = [[LGPerson alloc]init];
        NSObject *object = [[NSObject alloc]init];
        
        NSLog(@"%@-p,%@-p1,%@-p2",techer,person,object);
        NSLog(@"对象类型的内存大小--%lu",sizeof(techer));
        NSLog(@"对象实际的内存大小--%lu",class_getInstanceSize([techer class]));
        NSLog(@"系统分配的内存大小--%lu",malloc_size((__bridge const void *)techer));
        NSLog(@"%@", object);
};
复制代码

分别给LGTeachLGPerson 添加属性和方法发现对象实际内存大小只有增加属性删除属性发生变化,同时父类的属性增加和删除也会让子类的内存增加. Clang后查看

struct LGTeacher_IMPL {
	struct LGPerson_IMPL LGPerson_IVARS;
	int _age;
	int _heights;
	int _no;
};

struct LGPerson_IMPL {
	struct NSObject_IMPL NSObject_IVARS;
};
struct NSObject_IMPL {
	Class isa;
};
复制代码
  • 所以实例对象的本质是结构体,(在c++文件中查找类名_IMPL就能找到这个结构体),里面有一个isa指针和其他成员变量。所以实例对象在内存中存储的内容是 isa指针 + 其他成员变量。

1.2类对象探究

 LGTeacher *techer = [[LGTeacher alloc]init];
        
        Class t1 = [LGTeacher class];
        Class t2 = [techer class];
        Class t3 = NSClassFromString(@"LGTeacher");
    
        NSLog(@"%@-p,%@-t1,%@-t2,%@-t3",techer,t1,t2,t3);
复制代码

打印发现t1,t2,t3 内存地址都一样都是 LGTeacher说明创建多少类在内存中都指向同一个类,类型我们日常开发中的单列的概念。
我们查看源码可知

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

}
复制代码
  • 所以类对象其实是 objc_class类型的结构体,存放了isa指针,super指针,属性列表,类的对象方法信息,缓存等 类方法存放在哪呢?

1.3 元类对象探究

顾名思义,元类对象即是描述类对象的类,每个类都有自己的元类,也就是结构体objc_class中isa指向的类。Objective-C的类方法是使用元类的根本原因,因为其中存储着对应的类对象调用的方法即类方法。

instance 对象(实例对象):成员变量的具体值、isa 指针
复制代码
class 对象(类对象):对象方法、属性、成员变量、协议信息、isa 指针、superclass 指针
复制代码
meta-class 对象(元类对象,即描述对象的对象,元数据是指描述数据的数据):类方法、isa 指针、superclass 指针。
复制代码

所有元类都有一个根元类,比如所有NSObject的子类的元类都会以NSObject的元类作为他们的类。所有的元类使用根元类作为他们的类,根元类的元类则就是它自己,也就是说根元类的isa指针指向他自己。

总结他们的关系就如下,isa的走位图

isa流程图.png

  • 总结对象的本质就是一个包含isa指针类型的结构体

2.NONPOINTER_ISA

我们通过源码可知isa的组成如下

#     define ISA_MASK        0x0000000ffffffff8ULL
#     define ISA_MAGIC_MASK  0x000003f000000001ULL
#     define ISA_MAGIC_VALUE 0x000001a000000001ULL
#     define ISA_HAS_CXX_DTOR_BIT 1
#     define ISA_BITFIELD                                                      \
        uintptr_t nonpointer        : 1;     //表示是否对 isa 指针开启指针优,0代表是纯isa指针,1代表除了地址外,还包含了类的一些信息、对象的引用计数等化                                  \
        uintptr_t has_assoc         : 1;     //关联对象标志位                                 \
        uintptr_t has_cxx_dtor      : 1;     //该对象是否有C++或Objc的析构器,如果有析构函数,则需要做一些析构的逻辑处理,如果没有,则可以更快的释放对象                                \
        uintptr_t shiftcls          : 33;    //存在类指针的值,开启指针优化的情况下,arm64位中有33位来存储类的指针 /*MACH_VM_MAX_ADDRESS  0x1000000000*/ \
        uintptr_t magic             : 6;    //判断当前对象是真的对象还是一段没有初始化的空间                                      \
        uintptr_t weakly_referenced : 1;    //是否被指向或者曾经指向一个ARC的弱变量,没有弱引用的对象释放的更快                                  \
        uintptr_t unused            : 1;    //是否正在释放                                  \
        uintptr_t has_sidetable_rc  : 1;    //当对象引用计数大于10时,则需要进位                                   \
        uintptr_t extra_rc          : 19   //表示该对象的引用计数值,实际上是引用计数减一。例如:如果引用计数为10,那么extra_rc为9。如果引用计数大于10,则需要使用has_sidetable_rc
#     define RC_ONE   (1ULL<<45)
#     define RC_HALF  (1ULL<<18)
复制代码

isa指针通过打印可知占8个字节,这里说几个概念

  • 结构体:所有变量共存,不管用不用系统都会分配内存,虽然有内存对齐规则,但是是粗犷的,优点:全面,缺点:可能会浪费内存
  • 联合体:存在多个变量,但是变量是互斥的,只能存储最后一个变量。优点:可以节约内存,缺点:不够全面
  • 位域:为了优化内存,我们可以指定具体属性所占内存是多少,组合成我们想要的结构。

isa结构体后面的数字就是具体属性所占的位置大小。占满了所有的64(1+1+1+33+6+1+1+1+19 = 64)具体所代表的含义在上面代码的注释中
这个就是非纯isa的大概情况

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享