天天看点

引用计数count

Objective-C的内存管理主要有三种方式ARC(自动内存计数)、手动内存计数、内存池。

1. (Garbage Collection)自动内存计数:这种方式和java类似,在你的程序的执行过程中。始终有一个高人在背后准确地帮你收拾垃圾,你不用考虑它什么时候开始工作,怎样工作。你只需要明白,我申请了一段内存空间,当我不再使用从而这段内存成为垃圾的时候,我就彻底的把它忘记掉,反正那个高人会帮我收拾垃圾。遗憾的是,那个高人需要消耗一定的资源,在携带设备里面,资源是紧俏商品所以iPhone不支持这个功能。所以“Garbage Collection”不是本入门指南的范围,对“Garbage Collection”内部机制感兴趣的同学可以参考一些其他的资料,不过说老实话“Garbage Collection”不大适合适初学者研究。 解决: 通过alloc – initial方式创建的, 创建后引用计数+1, 此后每retain一次引用计数+1, 那么在程序中做相应次数的release就好了.

2. (Reference Counted)手动内存计数:就是说,从一段内存被申请之后,就存在一个变量用于保存这段内存被使用的次数,我们暂时把它称为计数器,当计数器变为0的时候,那么就是释放这段内存的时候。比如说,当在程序A里面一段内存被成功申请完成之后,那么这个计数器就从0变成1(我们把这个过程叫做alloc),然后程序B也需要使用这个内存,那么计数器就从1变成了2(我们把这个过程叫做retain)。紧接着程序A不再需要这段内存了,那么程序A就把这个计数器减1(我们把这个过程叫做release);程序B也不再需要这段内存的时候,那么也把计数器减1(这个过程还是release)。当系统(也就是Foundation)发现这个计数器变 成员了0,那么就会调用内存回收程序把这段内存回收(我们把这个过程叫做dealloc)。顺便提一句,如果没有Foundation,那么维护计数器,释放内存等等工作需要你手工来完成。 解决:一般是由类的静态方法创建的, 函数名中不会出现alloc或init字样, 如[NSString string]和[NSArray arrayWithObject:], 创建后引用计数+0, 在函数出栈后释放, 即相当于一个栈上的局部变量. 当然也可以通过retain延长对象的生存期.

3. (NSAutoRealeasePool)内存池:可以通过创建和释放内存池控制内存申请和回收的时机. 解决:是由autorelease加入系统内存池, 内存池是可以嵌套的, 每个内存池都需要有一个创建释放对, 就像main函数中写的一样. 使用也很简单, 比如[[[NSString alloc]initialWithFormat:@”Hey you!”] autorelease], 即将一个NSString对象加入到最内层的系统内存池, 当我们释放这个内存池时, 其中的对象都会被释放.

#import <Foundation/Foundation.h>

@protocol ____count <NSObject>

基础

1.

< 1 >assign (MRC)

//setter 方法

- ( void )setName:(NSString *)name

{ _name = name;}

//getter 方法

- (NSString *)name

{ return _name;}

< 2 >retain (MRC)

//setter 方法

- ( void )setName:(NSString *)name

{ if (_name != name){

    [_name release];

    _name = [name retain];}}

//getter 方法

- (NSString *)name

{ return _name;}

< 3 >copy (MRC)

//setter 方法

- ( void )setName:(NSString *)name

{ if (_name != name){

    [_name release];

    _name = [name copy];}}

//getter 方法

- (NSString *)name

{ return _name;}

< 4 > 初始化

self .firstName 中调用了 firstName 的 setter 方法 (setter 方法中 retain 过一次 , 所以可以用此方法 , 效率没有直接 retain 的方法高 )

- (KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{

    if ( self = [ super init]) {

        self .firstName = firstName;

        self .lastName = lastName;

        self .phoneNumber = phoneNumber;

    }

    return self ;

}

或者

- (KCContact *)initWithFirstName:(NSString *)firstName andLastName:(NSString *)lastName andPhoneNumber:(NSString *)phoneNumber{

    if ( self = [ super init]) {

        _firstName = firstName;

        _lastName = lastName;

        _phoneNumber = phoneNumber;

    }

    return self ;

}

2.

< 1 > 伪拷贝

- ( id )copyWithZone:(NSZone *)zone

{ return [ self retain];}

< 2 > 浅拷贝

- ( id )copyWithZone:(NSZone *)zone

{NSString *name = self .name;

    NSUInteger age = self .age;

    Person *Person = [[Person alloc] initWithName:name age:age];

    return Person;}

< 3 > 深拷贝

- ( id )copyWithZone:(NSZone *)zone

{   NSString *name = self .name;

    NSUInteger age = self .age;

    NSString *newName = [NSString stringWithFormat: @"%@" ,name];

    Person *newPerson = [[Person alloc] initWithName:newName age:age];

    return newPerson;}

举例

1.

NSArray, NSDictionary, NSSet 等类,会在对象加入后引用计数加一获得所有权,在对象被移除或者整个容器对象被释放的时候释放容器内对象的所有权。类似的情况还有 UIView 对 subview 的所有权关系, UINavigationController 对其栈上的 controller 的所有权关系等等。

//push 入栈显示新的视图控制器 , 被导航控制器管理 ,retain+1, 如果 back 后被推回去 , 自动 deallco,pop 后引用计数 -1

//push 后 detailVC 进入 navigationController 的 viewControllers 数组栈中 , 相当于 UIView 中的 subviews, 数组销毁 , 里面的引用计数全部 -1

[ self .navigationController pushViewController:detailVC animated: YES ];

//???

还有一些用法会让系统拥有对象的所有权。比如 NSObject 的 performSelector:withObject:afterDelay 。如果有必要,需要显示的调用 cancelPreviousPerformRequestsWithTarget:selector:object: ,否则有可能产生内存泄露。

因这种原因产生的泄露因为并不违反任何规则,是 Intrument 所无法发现的。

3. @property ( nonatomic , copy ) void (^PassVauleBlock)(UIImage*) passVaule;   必须这么写

block 是个代码片段要用 copy

@property ( nonatomic , copy ) NSMutableArray *contacts;  这么写是错的 , 不可变数组没有 copy 方法 , 实现的是父类不可变数组中 copy 的方法 ,copy 出的是不可变数组

4. @property ( nonatomic , assign ) id <UIScrollViewDelegate>delegate;

为什么 delegate 用 assign 而不使用 retain ( 应为不用管代理的死活 , 代理死了不必完成协议 )

一个对象没必要管理自己 delegate 的生命周期,或者说没必要拥有该对象,所以我们只要知道它的指针就可以了,用指针找到对象去调用方法,也就是委托实现的感觉。

或者我们换个角度,从内存管理方面也可以解释这个问题。 delegate 的生命周期不需要让该对象去控制,如果该对象对其使用 retain 很可能导致 delegate 所指向的对象无法正确的释放。

循环引用

所有的引用计数系统,都存在循环应用的问题。例如下面的引用关系:

对象 a 创建并引用到了对象 b.

对象 b 创建并引用到了对象 c.

对象 c 创建并引用到了对象 b.

这时候 b 和 c 的引用计数分别是 2 和 1 。当 a 不再使用 b ,调用 release 释放对 b 的所有权,因为 c 还引用了 b ,所以 b 的引用计数为 1 , b 不会被释放。 b 不释放, c 的引用计数就是 1 , c 也不会被释放。从此, b 和 c 永远留在内存中。

这种情况,必须打断循环引用,通过其他规则来维护引用关系。比如,我们常见的 delegate 往往是 assign 方式的属性而不是 retain 方式的属性,赋值不会增加引用计数,就是为了防止 delegation 两端产生不必要的循环引用。如果一个 UITableViewController 对象 a 通过 retain 获取了 UITableView 对象 b 的所有权,这个 UITableView 对象 b 的 delegate 又是 a ,如果这个 delegate 是 retain 方式的,那基本上就没有机会释放这两个对象了。自己在设计使用 delegate 模式时,也要注意这点。

因为循环引用而产生的内存泄露也是 Instrument 无法发现的,所以要特别小心。

5. @property ( nonatomic , retain , readonly ) UILabel *contentLabel;

  @property ( nonatomic , retain , readonly ) UIButton *submitButton;

readonly 没有 setter 方法 , 只有 getter 方法

在 .m 文件中 // 没有 setter 方法用不了 _contentLabel, 需自己创建实例变量 (@synthesize 实现 setter 和 getter 的方法 )

@synthesize contentLabel = _contentLabel;

@synthesize submitButton = _submitButton;

- (UILabel *)contentLabel{

    if (!_contentLabel) {

        // 没有 setter 方法 , 没有内部 retain, 不能加 autorelease, 保存对象的地址 , 在 dealloc 中 release, 这里引用计数需 +1

        _contentLabel = [[UILabel alloc] initWithFrame: self .view.bounds];

        // 如果创建对象时先 getter button,lable 就会遮挡 button

        //[self.view addSubview:_contentLabel];

        // 保证 lable 永远在最下面

        [ self .view insertSubview:_contentLabel atIndex: 0 ];

    }

    return _contentLabel;

}

(UIButton *)submitButton{

    if (!_submitButton) {

        // 引用计数 +1

        _submitButton = [[UIButton buttonWithType:UIButtonTypeSystem] retain];

        [ self .view addSubview:_submitButton];

    }

    return _submitButton;

}

-( void )dealloc{

    [_contentLabel release];

    [_submitButton release];

    [ super dealloc];

}

6. // 保存从数组中移除的数据要 retain 一次 , 要不然数组元素出数组内存引用计数会自动减到 0, 数据销毁

- ( void )tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {

    id object = [_datasource[fromIndexPath.row] retain];

    [_datasource removeObjectAtIndex:fromIndexPath.row];

    [_datasource insertObject:object atIndex:toIndexPath.row];

    [object release];

}

@end