爬爬爬之路: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;

总结一下

  1. 对象属性都用retain修饰
  2. 自定义初始化操作的时候都用self.xxx = xxx; 形式(若是属性含有readonly的时候, 改写成_xxx = [xxx retain], 或者 _xxx = [xxx copy]).
  3. 每个类的dealloc方法 都要有它对象属性的release语句.
  4. 每个alloc retain copy 都需要有相应的 release 或者 autorelease 语句.

以上只完成了assign的setter方法转化成retain方法的原理. 实际上对copy也适用, 只是copy的具体实现(是只对本空间retain操作 还是重新alloc一个新空间 都是由程序员自己定义的, 系统类对象的copy除外). 推理过程是一样的, 有兴趣的读者可以自己试试.


collection的内存管理

collection就是NSArray, NSDictionary, NSSet等容器类.
collection会自主管理自己内部的元素

  1. 加入collection中的对象会被retain
  2. 移出collection的对象会被release
  3. 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对象或者其子类对象
等都是多态的应用.

0 0
原创粉丝点击