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 }
复制代码

 

posted on   低头捡石頭  阅读(54)  评论(0编辑  收藏  举报

编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

导航

统计

点击右上角即可分享
微信分享提示