ios内存管理(三):继承体系中的内存布局

  前面章节介绍了内存的分配与释放机制,没有从基类以及子类的视角出发,本节将从这个角度,梳理类在继承体系中的内存管理。

  首先,来研究一下类成员变量在内存中的布局。

// .h文件
@interface ObjectA1 : NSObject
@property (nonatomic) NSInteger i1;
@property (nonatomic) NSInteger i2;
@property (nonatomic) NSInteger i3;
@property (nonatomic) NSInteger i4;
@end

// .m文件
@implementation ObjectA1
- (instancetype)init {
    if (self = [super init]) {
        _i1 = 0x1001;
        _i2 = 0x2002;
        _i3 = 0x3003;
        _i4 = 0x4004;
    }
    return self;
}

- (void)memoryTest {
    ObjectA1 *a1 = [[ObjectA1 alloc] init];
    NSLog(@"%@",a1);
}

  在NSLog处设置断点,用memory read命令读取该对象的内存,如下:

(lldb) po a1
<ObjectA1: 0x60400025d2b0>
(lldb) memory read 0x60400025d2b0
0x60400025d2b0: 40 e0 99 0c 01 00 00 00 01 10 00 00 00 00 00 00  @...............
0x60400025d2c0: 02 20 00 00 00 00 00 00 03 30 00 00 00 00 00 00  . .......0......
(lldb) memory read 0x60400025d2d0
0x60400025d2d0: 04 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00  .@..............

  现在调整ObjectA1对象的声明,改成如下:

@interface ObjectA1 : NSObject
@property (nonatomic) NSInteger i2;
@property (nonatomic) NSInteger i1;
@property (nonatomic) NSInteger i4;
@property (nonatomic) NSInteger i3;
@end

  再次读取a1对象的内存,结果变成了:

(lldb) po a1
<ObjectA1: 0x604000056920>

(lldb) memory read 0x604000056920
0x604000056920: 40 70 da 05 01 00 00 00 02 20 00 00 00 00 00 00  @p....... ......
0x604000056930: 01 10 00 00 00 00 00 00 04 40 00 00 00 00 00 00  .........@......
(lldb) memory read 0x604000056930
0x604000056930: 01 10 00 00 00 00 00 00 04 40 00 00 00 00 00 00  .........@......
(lldb) 

  仔细观察,不难发现,类成员变量在内存中的布局,由其声明的顺序决定。编译器根据类成员变量的声明顺序,确定相应的偏移指针值。

  下面来看继承时的内存分配,如下代码:

@interface ObjectA1 : NSObject
@property (nonatomic) NSInteger i1;
@property (nonatomic) NSInteger i2;
@end

@interface ObjectA2 : ObjectA1
@property (nonatomic) NSInteger i3;
@property (nonatomic) NSInteger i4;
@end
@implementation ObjectA1
- (instancetype)init {
    if (self = [super init]) {
        _i1 = 0x1001;
        _i2 = 0x2002;
    }
    return self;
}
@end

@implementation ObjectA2
- (instancetype)init {
    if (self = [super init]) {
        _i3 = 0x3003;
        _i4 = 0x4004;
    }
    return self;
}
@end

- (void)memoryTest {
    ObjectA2 *a2 = [[ObjectA2 alloc] init];
    NSLog(@"%@",a2);
}

  在NSLog处设置断点,查看a2的内存布局,如下:

(lldb) memory read 0x608000055a50
0x608000055a50: 80 f0 d1 04 01 00 00 00 01 10 00 00 00 00 00 00  ................
0x608000055a60: 02 20 00 00 00 00 00 00 03 30 00 00 00 00 00 00  . .......0......
(lldb) memory read 0x608000055a70
0x608000055a70: 04 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00  .@..............

  由上可知在内存中,基类的成员变量会放在本类的成员变量“前面”。假如不调用基类的初始化方法,会发生啥呢?

@implementation ObjectA2
- (instancetype)init {
    if (self) {
        _i3 = 0x3003;
        _i4 = 0x4004;
    }
    return self;
}

(lldb) po a2
<ObjectA2: 0x60000004e640>

(lldb) memory read 0x60000004e640
0x60000004e640: 78 40 d7 02 01 00 00 00 00 00 00 00 00 00 00 00  x@..............
0x60000004e650: 00 00 00 00 00 00 00 00 03 30 00 00 00 00 00 00  .........0......
(lldb) memory read 0x60000004e660
0x60000004e660: 04 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00  .@..............

  编译器给出了警告,执行后,可见为基类的成员变量分配了空间,布局不受影响,变化只是没有调用基类的初始化方法给成员变量赋初值。

  综上,在给对象分配空间时,一定是先分配基类的存储区域,然后分配子类的存储区域,基类存储区域在头部,子类存储区域在尾部。初始化该区域时,没有严格的先后顺序,可以先初始化子类再初始化基类,只不过这样做只有坏处没有好处,除非有非常特殊的目的,否则都应该先初始化基类的变量,再初始化子类。

  那么释放是如何进行的呢?其过程和初始化刚好相反。当对对象调用release方法后,如果引用计数为0,会调用对象的dealloc方法回收内存,一般,先释放自己的成员变量,然后调用[super dealloc]释放基类的成员变量。

  最后,编译器并不会强制要求对象的初始化和释放顺序,只是给出了警告,但我们为什么要遵循先初始化基类、再初始化子类,先释放子类、再释放基类这种顺序呢?主要原因是子类可能会使用基类的成员变量。如果不按照这种顺序,在初始化时如果子类用到了基类的数据,就可能会出错;同理,如果先释放了基类的数据,再释放子类的数据时,如果还要用到基类数据,就可能会出错或导致crash。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,803评论 18 399
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,147评论 0 62
  • 官方文档 初始化 Initialization是为准备使用类,结构体或者枚举实例的一个过程。这个过程涉及了在实例里...
    hrscy阅读 1,153评论 0 1
  • __block和__weak修饰符的区别其实是挺明显的:1.__block不管是ARC还是MRC模式下都可以使用,...
    LZM轮回阅读 3,407评论 0 6
  • *面试心声:其实这些题本人都没怎么背,但是在上海 两周半 面了大约10家 收到差不多3个offer,总结起来就是把...
    Dove_iOS阅读 27,227评论 30 472