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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

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