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

OC-对象的本质

作者: 比特_0bd7 | 来源:发表于2021-01-04 16:49 被阅读0次

前言

探寻OC对象的本质,我们平时编写的Objective-C代码,底层实现其实都是C\C++代码。
OC的对象都是通过基础C\C++的结构体实现的。


image.png

OC 转换为 C++
我们通过创建OC对象,并将OC文件转化为C++文件来探寻OC对象的本质,OC如下代码

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSObject *objc = [[NSObject alloc] init];
        NSLog(@"Hello, World!");
    }
    return 0;
}

使用 clang 将 OC 代码转换为 C++ 代码

// 这种方式没有指定架构例如arm64架构 其中cpp代表(c plus plus)生成 main.cpp
clang -rewrite-objc main.m -o main.cpp 

使用 XCode 工具 xcrun 进行转换

// 可以指定 arm64 架构, 如果需要链接其他框架,使用-framework参数。比如-framework UIKit
// -o 输出的 cpp 文件
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 

一.先来看看我们平时接触的NSObject

  • NSObject *objc = [[NSObject alloc]init]的本质
    在内存中,这行代码就把objc转在底层实现中转成了一个结构体,其底层C++编译成结构体为:
struct NSObject_IMPL {
    Class isa;
};

在64位机中,一个isa占8个字节,在32位机中,一个isa占4个字节(当然苹果后面的机型都是64位的,这里我们着重讲解64位机)

  • 我们先来看看这个创建好的objc占多少个字节
int main(int argc, char * argv[]) {

    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        //定义一个objc
        NSObject *objc = [[NSObject alloc]init];
        //打印内存
        NSLog(@"tu-%zd",class_getInstanceSize([NSObject class]));
        NSLog(@"tu-%zd",malloc_size((__bridge const void *)(objc)));
    }

}

其打印结果为

image
  • 为什么一个是8一个是16
    • 我们先来认识一下class_getInstanceSize、malloc_size的区别
      1.class_getInstanceSize:是一个函数(调用时需要开辟额外的内存空间),程序运行时才获取,计算的是类的大小(至少需要的大小)即实例对象的大小->结构体内存对齐
      2.创建的对象【至少】需要的内存大小不考虑malloc函数的话,内存对齐一般是以【8】对齐
      3.#import <objc/runtime.h>使用这个函数时倒入runtime运行时

    • malloc_size:堆空间【实际】分配给对象的内存大小 -系统内存对齐

      1. 在Mac、iOS中的malloc函数分配的内存大小总是【16】的倍数 即指针指向的内存大小
      2. import <malloc/malloc.h>使用时倒入这个框架
  • sizeof:是一个运算符,获取的是类型的大小(int、size_t、结构体、指针变量等),这些数值在程序编译时就转成常数,程序运行时是直接获取的

看到上面对两个函数的认识,应该知道为什么输出的一个是8,一个是16了吧,当内存申请<16时,在底层分配的时候,系统会默认最低16个字节,系统给objc16个字节,而objc用到的是8个字节(没添加任何成员变量之前)

二.内存对齐

  • 在上面的基础上我们新建一个类Student继承NSObject,那么对于student的底层C++编译实现就变成了:
struct Student {
    struct NSObject_IMPL NSOBJECT_IVARS;
};

也就是说,继承关系,子类直接将父类的isa引用进来

  • 对于class_getInstanceSize(也就是类本质的内存对其)
    1.在student中创建成员变量:
@interface Student : NSObject
{
    @public
    int _age;
    int _no;
    int _tc;
}
@end

其底层C++编译结构体就变成了

struct Student {
    struct NSObject_IMPL NSOBJECT_IVARS;
    int _age;
    int _no;
    int _tc;
};

  • 打印结果:
 //定义一个objc
        Student *objc = [[Student alloc]init];
        //打印内存
        NSLog(@"tu-%zd",class_getInstanceSize([Student class]));
        NSLog(@"tu-%zd",malloc_size((__bridge const void *)(objc)));

2020-09-08 12:35:27.158568+0800 OC底层[1549:79836] tu-24
2020-09-08 12:35:27.159046+0800 OC底层[1549:79836] tu-32

  • 先来说说24的由来

由于创建对象的时候,内存是以8对齐,上面我们讲到一个对象里面包含了一个isa占8个字节,对于student来说它有四个成员变量,isa,age,no,tc,共占8+4+4+4=20字节,但是由于内存以8对齐的原因,我们看到的输出是24,

image

所以class_getInstanceSize在计算实例大小的时候就是24,其白色区域表示空出了四个字节

  • 再来看看32的由来
    上面我们说到malloc_size指的是实际堆分配的空间,它以16字节对齐

    image

可以看到,空白的区域为空出了12个字节,总共为32个字节

三.添加属性

  • 添加属性
@interface Student : NSObject
{
    @public
    int _age;
    int _no;
    int _tc;

}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSArray *array;
@end

其在底层C++编译就变成了

struct Student {
    struct NSObject_IMPL NSOBJECT_IVARS;
    int _age;
    int _no;
    int _tc;
    NSString _name;
    NSArray _array;
};

默认的会将属性生成的_name添加进结构体中,计算相应的大小

四、更复杂的继承关系

/* Person */
@interface Person : NSObject
{
    int _age;
}
@end
/* Student */
@interface Student : Person
{
    int _no;
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"%zd  %zd",
              class_getInstanceSize([Person class]),
              class_getInstanceSize([Student class])
              );
    }
    return 0;
}
image.png

我们发现只要是继承自NSObject的对象,那么底层结构体内一定有一个isa指针。那么他们所占的内存空间是多少呢?单纯的将指针和成员变量所占的内存相加即可吗?上述代码实际打印的内容是16 16,也就是说,person对象和student对象所占用的内存空间都为16个字节。
其实实际上person对象确实只使用了12个字节。但是因为内存对齐的原因。使person对象也占用16个字节。

展开来讲另外的一个点。

比如一个Person对象,他的内存地址假设说是0x10010,那么其实这个内存地址也是他isa指针的内存地址,因为只要集成自NSObject 往上展开,第一个元素一定是isa指针内存地址,所有isa指针也就是这个对象的内存地址了

五、窥探内存结构

方式一:通过打断点。 Debug Workflow -> viewMemory Address 中输入stu的地址


image.png
image.png

六 代码方式获取对象占用空间大小

获取一个实例对象至少需要多少内存

#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);
在程序运行的过程中,返回这个类创建出来的最小内存

sizeof() 是一个运算符,编译的时候就会转化为常数,实际上计算的是一种类型

JWJPerson *p = [[JWJPerson alloc] init];
sizeof(p)---->  8 
编译的时候确定p,是一个指针而已

获取一个实例对象实际上分配了多少内存
由于一些系统内存分配机制,内存对齐等,实际分配的内存可能要比实际需要的内存大

#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);
实际分配了多少内存

总结:

所以在实际计算类的占用空间大小的时候,根据添加的成员变量就可以计算出一个实例占用的内存大小(即计算出结构体的大小24,然后告诉系统,系统调用calloc分配内存的时候按照16对齐原则分配)

相关文章

  • OC-对象的本质

    前言 探寻OC对象的本质,我们平时编写的Objective-C代码,底层实现其实都是C\C++代码。OC的对象都是...

  • OC-初探-对象原理-【Alloc init】

    [TOC] 前言 *更多OC对象相关文章请关注01、OC-初探-对象原理-【Alloc init】[https:/...

  • OC-再探-对象原理-【Alloc init】

    [TOC] 前言 更多OC对象相关文章请关注01、OC-初探-对象原理-【Alloc init】[https://...

  • OC-终探-对象原理-【Alloc init】

    [TOC] 前言 更多OC对象相关文章请关注01、OC-初探-对象原理-【Alloc init】[https://...

  • OC-对象方法

    //// main.m// 02-对象方法//// Created by zhaokai on 15/4/2...

  • OC-关联对象

    分类(Category):是OC中的特有语法,它是表示一个指向分类的结构体指针。原则上它只能增加方法,不能增加成员...

  • OC-关联对象

    类扩展和分类 category:类别、分类用来给类增加方法、属性、协议不能增加成员变量,在分类的结构体中并没有存储...

  • OC-关联对象AssociatedObject

    关联对象 前言 我们都知道ARC环境下, 在一个类中声明一个属性@property (nonatomic, ass...

  • OC-对象与方法

    一.@pragma mark指令的使用(实用性技能) #pragma mark用于标记位置,方便查找. #prag...

  • OC-类与对象

    一、new方法只要通过一个类调用类的new方法,也就是给类发送一个叫做new的消息之后,系统内部就回做3件事情:1...

网友评论

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

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