爬爬爬之路:OC语言(十) 内存管理(高级),多态简介
来源:互联网 发布:java数据库编程db 编辑:程序博客网 时间:2024/06/05 06:59
retain属性setter方法写法原理解析
以retain属性的setter方法实现为例.
以assign的set get举例
Person类里有一个属性 @property (nonatomic, assign) NSString *name;
它对应的setter getter方法如下:
- (void) setName : (NSString *)name { // setter方法 _name = name;}- (NSString *)name { // getter方法 return _name;}
当我们在主函数中
写语句如下:
Person *p = [[Person alloc] init];NSString *name = [[NSString alloc] initWithFormat:@“xiaoming”];p.name = name;// 根据管理原则, 一个alloc要对应一个release.[name release];[p release];
注意p.name = name;
语句调用了一次setter方法 将p内部的实例变量name 指向NSString对象name
此时对象name的引用计数为1.
此时可以画图如下:
当执行 [name release];
语句后
name的引用结束减为0.
然后系统调用dealloc方法使其空间被回收. p内部的name指向了一个僵尸空间
若是此后还需要用到p内部的name, 就会因为野指针问题而报错.
比如在[name release];
后加上一句NSLog(@"%@", p.name);
此时就会产生野指针问题. 也就是说, 在[name release]; 语句之后无法对p内部的内进行访问.
但是实际上从面向对象的逻辑而言. NSString *name = [[NSString alloc] initWithFormat:@"xiaoming"];
这给被指针name指向的对象(下称对象name)被创建出来的功能仅仅是用于赋值. 它赋值结束了以后, 其他的操作不属于对象name的功能.
接下来无论要是要访问p的属性name 还是修改p的属性name. 此时都不应该和对象name这个对象有关系. 而是和p的属性name有关系.
所以从逻辑上而言, 当p.name = name;
语句执行结束后, 对象name已经没有作用了, 应该释放指针name的对对象name的所有权. 既执行[name release];
语句.
但是这么做的结果就是p.name(p的属性name)无法进行访问操作了. 所以明显出了问题.
问题就出在了setter方法上
由于setter方法中只有简单的一句 _name = name;
真正的操作应该是改写成_name = [name retain];
让_name(属性name)对于这个空间也拥有所有权, 才可以保证在之后的访问中没有问题.
此时setter方法改写成了
- (void) setName:(NSString *)name { _name = [name retain];}
但是问题又来了.
如果在main函数中不止一次使用p.name = name;
语句
就会导致对象name的空间的计数无限增加. 但是显然属性name对对象name的空间只需要一个拥有权即可.
所以需要一个判断, 将setter方法改写成
- (void) setName:(NSString *)name { if (_name != name) _name = [name retain];}
但是这样还有一个问题
比如说我需要给p.name(属性name)重新赋值
也就是说需要新建一个NSString *newName 对象, 让p.name = newName;
此时p.name 指向了一个新的空间newName.
但是之前的name空间的计数并没有被减为0, 也就是说此时name空间没有被回收, 当p.name = newName;
语句执行的时候, name空间就被保留在内存中无法释放, 造成了内存泄漏
所以需要在p.name 赋新值的时候完成对旧值释放所有权操作.
- (void) setName:(NSString *)name { if (_name != name) [_name release]; _name = [name retain];}
这样就完成了 只有在赋新值的时候Person的内部属性对新值拥有所有权, 并释放旧值的所有权, 这样就可以避免旧值的内存泄漏.
而这段代码正是retain属性的setter方法, 所以属性是对象属性的时候 都建议用retain
值得一提的是, OC中没有空指针错误, 当第一次调用setter方法的时候, p.name的值为nil, 此时[_name release];
就相当于[nil release];
这是没有响应的, 也就是说[nil release]是不执行的.
但是setter写完了还是不够.
但最后一次赋新值的操作结束.
旧值的空间全都被回收了.
但是新值的空间呢?
当p.name 刚指向newName的时候
name的空间消失, p.name获得newName对象的所有权, 此时newName的引用计数器值为2.
此时main函数里的代码为
Person *p = [[Person alloc] init];NSString *name = [[NSString alloc] initWithFormat:@"xiaoming"];p.name = name;[name release]; // 对不用的对象及时释放NSString *newName = [[NSString alloc] initWithFormat:@"newxiaoming"];p.name = newName;[newName release];[p release];
当[newName release];
和[p release];
语句执行结束后
p的引用计数减为0, newName的引用计数减为1
p的引用计数为0, 空间被销毁. newName的引用计数依然还是1.
结果就是newName的空间被内存泄漏了.
此时需要在Person的dealloc方法中加上一句 [_name release];
dealloc中的完整实现为
- (void) dealloc { [_name release]; [super dealloc];}
值得注意的是, Person中的所有对象, 都需要在dealloc中有相应的release操作.
另外, 自定义初始化方法中也需要将原来的_name = name
改写成 self.name = name;
总结一下
- 对象属性都用retain修饰
- 自定义初始化操作的时候都用
self.xxx = xxx;
形式(若是属性含有readonly的时候, 改写成_xxx = [xxx retain], 或者 _xxx = [xxx copy]). - 每个类的dealloc方法 都要有它对象属性的release语句.
- 每个alloc retain copy 都需要有相应的 release 或者 autorelease 语句.
以上只完成了assign的setter方法转化成retain方法的原理. 实际上对copy也适用, 只是copy的具体实现(是只对本空间retain操作 还是重新alloc一个新空间 都是由程序员自己定义的, 系统类对象的copy除外). 推理过程是一样的, 有兴趣的读者可以自己试试.
collection的内存管理
collection就是NSArray, NSDictionary, NSSet等容器类.
collection会自主管理自己内部的元素
- 加入collection中的对象会被retain
- 移出collection的对象会被release
- collection被释放会对内部的所有对象release
比如
创建一个Person类
Person.m中重写dealloc方法
- (void)dealloc { NSLog(@"Person对象被摧毁"); [super dealloc];}
main.m中写入以下代码
Person *p1 = [[Person alloc] init];Person *p2 = [[Person alloc] init];Person *p3 = [[Person alloc] init];NSArray *array = [[NSArray alloc] initWithObjects:p1, p2, p3, nil];NSLog(@"p1:%ld, p2:%ld, p3:%ld, array:%ld", p1.retainCount, p2.retainCount, p3.retainCount, array.retainCount); // 结果是p1:2, p2:2, p3:2, array:1[p1 release];[p2 release];[p3 release]; // 运行到此句结束的时候Person类中的dealloc方法没有被调用[array release]; // 此句运行结束后dealloc方法被调用, 说明array release的时候, array中的对象也被自动release.
小知识点:
如果把数组放到字典里, 数组是value, 那么数组也会被retain, 但是数组里面的对象不会被retain.
容器类只会对存放在它中间的对象自动retain和release, 不会对存放在它存放的对象中间的对象进行retain和release.
继承的多态性
多态就是指不同类型对象在响应同一方法时的不同实现.
具体的体现是:父类的指针指向子类的对象.
父类调用子类中重写父类的方法, 结果是实现了子类中的代码.
注: 用父类的指针调用子类特有的方法时, 在OC中的表现仅仅是警报.
若是子类的指针指向父类的对象
那么就会报错, 理由是, 子类的指针可以调用子类里的特有方法, 但是父类里面没有该方法的实现, 导致错误.
多态的用途
利用多态的特性, 可以写出通用的代码.
如定义一个Animals类
在Animals.h中声明一个方法 - (void)eat;
.m文件中实现
- (void)eat { NSLog(@"动物吃");}
定义一个Dog类继承于Animals类
Dog.m中重写eat方法
- (void)eat { NSLog(@"狗吃");}
再定义一个Cat类同样继承与Animals类
Cat.m中重写eat方法
- (void)eat { NSLog(@"猫吃");}
定义一个Person类, 继承与NSObject类
在Person.h中声明一个方法 - (void)feeding:(Animals *)animal;
// 参数是一个Animals类的指针
在Person.m中实现
- (void)feeding:(Animals *)animal { [animal eat];}
在main中调用Person方法的feeding方法
Dog *dog = [[Dog alloc] init]; Person *p = [[Person alloc] init]; [p feeding:dog];// 结果打印的是 狗吃. Cat *cat = [[Cat alloc] init]; [p feeding:cat];// 结果打印的是 猫吃. Animals *a = [[Animals alloc] init]; [k feeding:a];// 结果打印的是 动物吃.
这样我们就可以写出一个对Animals既其所有子类这组类的通用方法.
如: [self.window addSubview:button];
addSubview添加的参数都是UIView及其子类对象
及NSSArray存放的必须是NSObject对象或者其子类对象
等都是多态的应用.
- 爬爬爬之路:OC语言(十) 内存管理(高级),多态简介
- 爬爬爬之路:OC语言(九) 内存管理(初级)
- iOSDay18之OC内存管理高级
- 进击的KFC:OC(十)内存管理高级
- OC高级内存管理
- OC 内存高级管理
- OC内存管理高级
- OC语言--内存管理
- OC语言--内存管理
- OC语言内存管理
- OC语言 内存管理
- OC之【内存管理】
- 爬爬爬之路:OC语言(一) 语法简介
- 爬爬爬之路:OC语言(六) Block语法简介
- OC语言学习-内存管理
- OC语言中的内存管理
- OC语言中的内存管理
- OC语言4-内存管理
- 那些大牛
- 通过android API函数操作数据库
- 搭建SpringMVC项目
- 网络状态监测Reachability的使用
- NSString的个人总结
- 爬爬爬之路:OC语言(十) 内存管理(高级),多态简介
- Android签名有关问题
- 【深入Java虚拟机】之三:类初始化
- http协议_请求数据包_get/post
- DOM文本节点
- display:inline-block是什么意思
- [ubuntu]移动Terminal终端中的TAB标签
- SLua 绑定 Protobuf-Lua (protoc-gen-lua) 在SLua中使用 Protobuf
- OC第十天:内存管理⾼级