OC语言 - 内存管理:retain内部实现
retain
1 - Xcode 勾选僵尸模式用来检测野指针。下面一步步验证 retain 的内部实现
// - Student.h
1 #import <Foundation/Foundation.h> 2 3 @interface Student : NSObject{ 4 5 NSString *_name;// 姓名 6 NSString *_sex; // 性别 7 NSInteger _age; // 年龄 8 } 9 10 - (void)sayHI; 11 - (id)initWithName:(NSString *)name 12 sex:(NSString *)sex 13 age:(NSInteger )age; 14 15 @end
// - Student.m
1 #import "Student.h" 2 @implementation Student 3 4 -(void)dealloc{ 5 6 NSLog(@"%@ 已经销毁",self); 7 [super dealloc]; 8 } 9 10 - (void)sayHI{ 11 NSLog(@"I am a student,name : %@,sex : %@,age : %ld",_name,_sex,_age); 12 } 13 14 - (id)initWithName:(NSString *)name sex:(NSString *)sex age:(NSInteger )age{ 15 16 self = [super init]; 17 if (self) { 18 _name = name; 19 _sex = sex; 20 _age = age; 21 } 22 return self; 23 } 24 25 @end
// - MyClass.h
#import <Foundation/Foundation.h> @class Student; @interface MyClass : NSObject // 首先使用 assign 进行测试 @property(nonatomic,assign)Student *stu; @end
// - MyClass.m
1 #import "MyClass.h" 2 @implementation MyClass 3 4 - (void)dealloc{ 5 6 // 从 TEST ② 开始,解除下行代码注释 7 // [_stu release];// 同 self.stu = nil; 因为点语法本质上是走 setter 方法,其内部有执行 [_stu release] 8 9 NSLog(@"%@ 已经销毁",self); 10 [super dealloc]; 11 } 12 13 14 // TEST ①:直接赋值 15 - (void)setStu:(Student *)stu{ 16 17 _stu = stu; 18 } 19 20 21 // TEST ②:赋值时,先把新值 retain 一次 22 //- (void)setStu:(Student *)stu{ 23 // _stu = [stu retain]; 24 //} 25 26 27 28 // TEST ③:先把旧值 release 29 //- (void)setStu:(Student *)stu{ 30 // 31 // // 先进行一次 release 32 // [_stu release]; 33 // _stu = [stu retain]; 34 //} 35 36 37 38 // 最后处理方式:苹果 retain 特性的默认处理机制 39 //- (void)setStu:(Student *)stu{ 40 // 41 // // 先来一次判定 42 // if(_stu != stu){ 43 // 44 // // 再进行内存操作 45 // [_stu release]; 46 // _stu = [stu retain]; 47 // } 48 //} 49 50 @end
// - main.m:分 3 个测试进行验证
1 #import <Foundation/Foundation.h> 2 #import "Student.h" 3 #import "MyClass.h" 4 int main(int argc, const char * argv[]) { 5 6 //------------------------ TEST ① ---------------------------- 7 8 // stu01 是实例对象的持有者 9 Student *stu01 = [[Student alloc] initWithName:@"Jason" sex:@"female" age:26];// stu01 = 1 10 11 MyClass *class01 = [[MyClass alloc] init];// class01 = 1 12 [class01 setStu:stu01];// stu01 = 1 13 // 修饰属性使用的是 assign, 所以 class01 的成员变量 _stu 可以获取到 stu01 但并不会拥有它的所有权 14 15 // 根据内存原则:在有 alloc 的地方需进行一次 release 16 [stu01 release];// 触发 dealloc 17 // <Student: 0x100606f50> 已经销毁 18 19 // 产生问题 20 // 如果 class01 恰恰又在它的某个地方使用了 _stu ,则程序 crash 21 // [class01.stu sayHI];// crash 22 23 [class01 release]; // 触发 dealloc 24 // <MyClass: 0x100738a40> 已经销毁 25 26 27 //------------------------ TEST ② ---------------------------- 28 29 // // 为解决 TEST ① 所产生的问题,我们在给成员变量 _stu 赋值时先进行一次 retain 操作 30 // Student *stu02 = [[Student alloc] initWithName:@"Jason" sex:@"female" age:26];// stu02 = 1;这里生成的实例对象我们称它是 旧值A 31 // MyClass *class02 = [[MyClass alloc] init]; 32 // 33 // [class02 setStu:stu02];// 在点语法内部赋值时 stu02 有 retain 处理 34 // NSLog(@"%lu---%lu(%@---%@)",class02.stu.retainCount, stu02.retainCount,class02.stu,stu02); 35 // // 2---2(<Student: 0x100495db0>---<Student: 0x100495db0>) 36 // 37 // [stu02 release];// stu02 = 1 38 // // stu02 进行 release 操作,引用计数 -1 并丢失 旧值A 的所用权 39 // // 旧值A 的所有权归 _stu 这个成员变量持有,这也就意味着由它负责释放 40 // // 所以我们会在 dealloc 中我们对成员变量进行一次 release 操作 41 // // 代码执行到这里时 Student 就不会触发 dealloc 42 // 43 // NSLog(@"%lu---%lu(%@---%@)",class02.stu.retainCount, stu02.retainCount,class02.stu,stu02); 44 // // 1---1(<Student: 0x100495db0>---<Student: 0x100495db0>) 45 // // 现在我们在别的地方可以愉快地调用,貌似没有什么问题 46 // [class02.stu sayHI]; 47 // 48 // 49 // // 那么问题来了:如果重新赋值的话 50 // Student *stu2A = [[Student alloc] initWithName:@"Lee" sex:@"male" age:33]; // 这里的实例对象我们称它 新值A 51 // NSLog(@"stu02 :%@ stu2A :%@",stu02,stu2A);// stu02 :<Student: 0x100495db0> stu2A :<Student: 0x10055e2b0> 52 // [class02 setStu:stu2A];// _stu 原来指向的对象是 旧值A,但是现在 _stu 重新指向了 新值A,就是 stu2A ! 产生内存泄露的问题 53 // 54
55 // [class02.stu sayHI]; 56 // NSLog(@"%lu---%lu",class02.stu.retainCount,stu2A.retainCount);// 2---2 57 // [stu2A release];// stu2A 放弃 新值A 的持有权;新值A 的所有权归 _stu 所有 58 // 59 // [class02 release];// 这里 class02 和 新值A 都会销毁 60 // // <Student: 0x102930850>已经销毁 <MyClass: 0x10042d560>已经销毁 61 // // 但是 旧值A 依然存在于内存之间,因为 MRC 不同于 ARC,ARC 中只要对象没有了所有者的引用,就会自动释放,但时 MRC 不会! 62 63 64 //------------------------ TEST ③ ---------------------------- 65 66 // // 为解决 TEST ② 所产生的问题,我们在每次赋值前,先把旧值 release 一次,然后再把新值 retain 67 // Student *stu03 = [[Student alloc] initWithName:@"Jason" sex:@"female" age:26];// stu3 = 1 这里生成的实例对象我们称之为 旧值C 68 // MyClass *class03 = [[MyClass alloc] init];// class03 = 1 69 // 70 // [class03 setStu:stu03]; // stu03 = 2 71 // NSLog(@"%lu---%lu",class03.stu.retainCount,stu03.retainCount);// 2---2 72 // [stu03 release];// stu03 = 1 73 // 74 // [class03.stu sayHI]; 75 // 76 // Student *stu3A = [[Student alloc] initWithName:@"Lee" sex:@"male" age:33]; // 新值C 77 // NSLog(@"stu03 的地址:%p stu3A 的地址:%p",stu03,stu3A);// stu03 的地址:0x10051c7b0 stu3A 的地址:0x100469850 78 // 79 // // 重新赋值 80 // [class03 setStu:stu3A];// 点语法中:旧值C 销毁; 新值C 的引用计数+1 81 // [stu3A release];// stu3A = 1 82 // [class03.stu sayHI]; 83 // 84 // // [class03 release];// <Student: 0x100406780>已经销毁 <MyClass: 0x10069cdc0>已经销毁 85 // // 代码运行到这里,内存泄露问题貌似解决掉了 86 // // 但是!如果我们在 class03 被 release 掉之前,重新赋相同的值呢 ? 87 // 88 // // 把上行代码 [class03 release] 屏蔽掉 89 // [class03 setStu:stu3A];// 僵尸对象出现 90 // // 重复赋值时 setter 方法中的 [_stu release] 会把旧值(就是现在的 新值C)release 掉 91 // // 那么 stu3A 已经成了野指针 92 // [class03 release];// <Student: 0x1029127d0>已经销毁 <MyClass: 0x1039a94a0>已经销毁 93 // 94 // // 最终解决方案就是把 新、旧 两值进行一次判断后再进行内存操作 95 96 return 0; 97 }
小结
1 - 苹果内部的 retain 实现
1 // setter 2 @property(nonatomic,retain)NSString *name; 3 4 // 内部实现 5 - (void)setName:(NSString *)name{ 6 if(_name != name){ 7 [_name release]; 8 _name = [name retain]; 9 10 } 11 } 12 13 // getter 14 - (NSString *)name{ 15 16 return [[_name retain] autorelease]; 17 }
分类:
OC语言
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)