天天看點

引用計數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