iOS-浅谈OC对象的本质

2019-04-16  本文已影响0人  晴天ccc

目录

  • Objective-C的本质
  • NSObject的底层实现
  • 一个NSObject对象的底层内存分布情况
  • 内存占用查看工具
    ----Xcode方式查看
    ----控制台LLDB命令方式查看
  • 拓展Student对象内存布局
  • 内存对齐-继承结构
  • OC内存对齐
  • 其他补充
    ----数据类型内存占用表
    ----容易混淆的2个函数
    ----Xcode实时查看内存数据
    ----常用LLDB指令

Objective-C的本质

我们平时编写的Objective-C代码底层是由C\C++实现的。OC的面向对象是基于C\C++的数据结构实现的。OC的对象、类主要基于C\C++的结构体实现的。OC代码转换过程:

NSObject的底层实现

NSObject * obj = [[NSObject alloc] init]; 

NSObject对象的底层实现是C++的结构体:

// OC代码
@interface NSObject {
    Class isa;
}

// 底层实现
struct NSObject_IMPL {
    Class isa;
};

在C++中,NSObject是一个结构体,内部只有一个isa指针变量
isa指针的大小就是结构体所占内存大小,结构体的内存地址就是isa指针的地址。
在32位系统下占用4个字节,64位系统占用8个字节

一个NSObject对象的底层内存分布情况

// 调用class_getInstanceSize方法需要导入头文件
#import <objc/runtime.h>
// 调用malloc_size方法需要倒入头文件
#import <malloc/malloc.h>

NSObject *obj = [[NSObject alloc] init];
// 获得NSObject实例对象的成员变量所占用的大小 -> 8
NSLog(@"obj = %zd",class_getInstanceSize([NSObject class]));
// 获得obj指针所指向内存的大小 -> 16
NSLog(@"obj = %zd",malloc_size((__bridge const void*)obj));

通过打印结果观察class_getInstanceSize方法方法获取obj对象占8个字节,通过malloc_size方法获取obj对象占用16个字节,为什么会读取出两个不同的大小?
分析 objc源码可知class_getInstanceSize最终调用了alignedInstanceSize,获取的是成员变量的大小,即isa指针所占大小8

// Class's ivar size rounded up to a pointer-size boundary.
uint32_t alignedInstanceSize() const {
   return word_align(unalignedInstanceSize());
}

分析malloc源码可知malloc_size最终调用了instanceSize,获取的是对象所占内存大小,最小为16个字节,所以得出16:

inline size_t instanceSize(size_t extraBytes) const {
    if (fastpath(cache.hasFastInstanceSize(extraBytes))) {
        return cache.fastInstanceSize(extraBytes);
    }

    size_t size = alignedInstanceSize() + extraBytes;
    // CF requires all objects be at least 16 bytes.  
    if (size < 16) size = 16;
    return size;
}

NSObject对象使用了前8个字节,后8个字节是空的,NSObject对象数据结构如下:

内存查看工具

利用Xcode可以查看对象的内存布局,Debug -> Debug Workfllow -> View Memory,输入NSObject对象地址:

也可以在控制台通过LLDB命令进行查看,复制obj对象的内存地址,然后在控制台输入memory read 粘贴内存地址

Printing description of obj:
<NSObject: 0x28121c000>
(lldb) memory read 0x28121c000
0x28121c000: 49 ef 4d f0 01 00 00 01 00 00 00 00 00 00 00 00  I.M.............
0x28121c010: 49 ef 4d f0 01 00 00 01 00 00 00 00 00 00 00 00  I.M.............
(lldb) 

拓展Student对象内存布局

创建一个继承于NSObject的类Student

@interface Student : NSObject {
    @public
    int _no;
    int _age;
}

通过观察可以发现Student继承自NSObject,而NSObject底层的结构体只有一个isa,所以Student的底层实现为:

struct NSObject_IMPL {
    Class isa;
};

struct Student_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _no;
    int _age;
};

我们对进行Student类进行初始化并赋值

    Student *stu = [[Student alloc] init];
    stu->_no = 4;
    stu->_age = 5;
    NSLog(@"%zd", class_getInstanceSize([Student class]));
    NSLog(@"%zd", malloc_size((__bridge const void *)stu));
16
16

Student实例对象包含父类的isa指针和自己的成员变量,isa占八个字节,int声明的对象占四个字节,所以Student在开辟的内存空间大小就是8+4+4=16
我们看一下内存地址:

Printing description of stu:
<Student: 0x101012770>
(lldb) memory read 0x101012770
0x101012770: 61 81 00 00 01 00 00 01 04 00 00 00 05 00 00 00  a...............
0x101012780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
(lldb) 

内存地址是按照第一个对象的地址来的。所以可以推算出Student在堆中内存地址分布情况

内存对齐-继承结构

创建一个Person类继承于NSObject,创建一个Student类继承于Person,分别有各自的属性

@interface Person : NSObject {
    int _no;
}
@interface Student : Person {
    int _age;
}

NSObject、Person、Student的底层结构如下:

struct NSObject_IMPL {
    Class isa;
};
struct Person_IMPL {
    struct NSObject_IMPL NSObject_IVARS;
    int _age;
};

struct Student_IMPL {
    struct NSObject_IMPL Person_IVARS;
    int _no;
};

PersonStudent的内存结构为:

分别打印personstu对象的class_getInstanceSizemalloc_size

NSLog(@"person = %zd",class_getInstanceSize([Person class]));
NSLog(@"person = %zd",malloc_size((__bridge const void*)person));
NSLog(@"stu = %zd",class_getInstanceSize([Student class]));
NSLog(@"stu = %zd",malloc_size((__bridge const void*)stu));

打印结果:

person = 16
person = 16

stu = 16
stu = 16

虽然person成员变量所占实际大小是12个字节,但是class_getInstanceSize实际上返回的是对齐后的内存大小,所以应该是8的倍数,返回的就是16

内存对齐:结构体的最终大小必须是最大成员大小的倍数

OC的内存对齐

创建一个Student类继承于NSObject

@interface Student : NSObject {
    int _no;
    int _age;
    int _height;
}
NSLog(@"stu = %zd",class_getInstanceSize([Student class]));
NSLog(@"stu = %zd",malloc_size((__bridge const void*)stu));

打印结果:

24
32

分析:成员变量所占内存大小8 + 4 + 4 + 4 = 20,根据内存对其规则,应该是8的倍数,所以结果是24,那么为什么malloc_size读取的是32

class_getInstanceSize是获取创建这个实例对象:【至少需要多少内存】。
malloc_size是获取创建这个实例对象:【实际上分配了多少内存】。
OC内存对齐单位是16或者16的倍数
所以创建Student对象需要24个字节,但是实际上分配了32个字节

其他补充

创建一个实例对象,至少需要多少内存?
#import <objc/runtime.h>
class_getInstanceSize([NSObject class]);
创建一个实例对象,实际上分配了多少内存?
#import <malloc/malloc.h>
malloc_size((__bridge const void *)obj);
Debug -> Debug Workfllow -> View Memory  
print、p:打印
po:打印对象
// 读取内存
memory read/数量格式字节数  内存地址
x/数量格式字节数  内存地址
x/3xw  0x10010

上一篇下一篇

猜你喜欢

热点阅读