OC學習篇之---內存管理介紹和使用 分類: IOS 2014-12-14 16:16 1510人閱讀 評論(1) 收藏

在以前的一片文章咱們說了OC中謂詞操做:http://blog.csdn.net/jiangwei0910410003/article/details/41923507,從今天開始咱們就來看一下OC中最難的一部份內容:內存管理java

爲何說他難呢?由於內存若是須要咱們程序員去管理的話,那個難度確定是很大的,若是是Java,垃圾回收器會把這份工做給作了,咱們不須要關心,可是就是由於如此,Android運行速度上會慢一下,緣由很簡單,Java的垃圾回收器有不少收集算法的,這個在回收的過程當中是很浪費時間的,效率天然就低了,可是若是這份工做給程序員本身去作的話,效率上確定會增長,可是對於程序員來講任務就比較繁重了,並且還要特別的當心,千萬不能形成內存溢出和泄露。程序員

這裏咱們主要從四個方面來介紹一下內存管理算法

一、簡單的例子來了解引用計數的使用測試

二、set方法來控制引用計數問題spa

三、銷燬方法來控制引用計數問題.net

四、初始化方法來控制引用計數問題指針


下面就來簡單看一下OC中的內存管理code

這個例子有點複雜,咱們慢慢分析對象

Dog.hblog

//
//  Dog.h
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Dog : NSObject{
    NSString *_name;
}

- (void) setName:(NSString *)name;

@end

Dog.m

//
//  Dog.m
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Dog.h"

@implementation Dog

- (void) setName:(NSString *)name{
    _name = name;
}

@end

Dog類中定義了name屬性,而且給他提供了一個set方法


Person.h

//
//  Person.h
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

@class Dog;
@interface Person : NSObject{
    Dog *_dog;
    NSString * _name;
}

- (id)initWithDog:(Dog*)dog;
- (void)setName:(NSString *)name;
- (void)setDog:(Dog *)dog;
- (void)playDog;
- (Dog *)dog;

- (void)dealloc;
@end


Person.m

//
//  Person.m
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import "Person.h"

@implementation Person

- (id)initWithDog:(Dog*)dog{
    //使用初始化的時候傳入dog
    self = [super init];
    if(self != nil){
        //由於初始化方法只會調用一次,因此這裏就沒有作判斷了
        [_dog release];
        _dog = [dog retain];
    }
    return self;
}

- (void)setName:(NSString *)name{
    //這裏name也是對象,因此也是須要進行改寫
    _name = name;
    
    /*
     //這裏的判斷是由於setName方法可能會被屢次調用
     if(_name != name){
        [_name release];
        [name copy];//這裏使用了copy,而沒有使用retain,這個是字符串獨有的,其餘對象類型都是使用retain的
     }
     */
}

//第一種方式
/*
- (void)setDog:(Dog *)dog{
    //引用計數須要+1
    _dog = [dog retain];
    
    //有時候可能須要替換Dog對象,因此這裏還要注意釋放Dog的引用
}
 */

//第二種方式
/*
- (void)setDog:(Dog *)dog{
    //使用nil去調用方法是沒有錯誤的
    //可是當一個對象被銷燬的時候,指針就變成野指針了,這時候調用方法會出錯的
    [_dog release];
    _dog = [dog retain];
}
 */

//第三種方式
- (void)setDog:(Dog *)dog{
    //這裏的判斷是由於setName方法可能會被屢次調用
    if(_dog != dog){
        [_dog release];
        _dog = [dog retain];
    }
}

- (void)playDog{
    NSLog(@"playDog");
}

- (Dog *)dog{
    return _dog;
}

- (void)dealloc{
    //對象類型的屬性都須要在這裏進行釋放引用
    //對狗進行釋放
    [_dog release];
    NSLog(@"dealloc is Executing");
    [super dealloc];
}

@end
Person類中有一個Dog的屬性,而後提供了set方法。代碼有點複雜,咱們後面會詳細說明

下面來看一下測試代碼

main.m

//
//  main.m
//  24_MemeryManager
//
//  Created by jiangwei on 14-10-12.
//  Copyright (c) 2014年 jiangwei. All rights reserved.
//

#import <Foundation/Foundation.h>

#import "Person.h"
#import "Dog.h"

//內存管理
//alloc用來建立對象,建立完成以後,引用計數爲1,只調用一次
//retain使引用計數+1,能夠多調用幾回
//release使引用計數-1,能夠多調用幾回
//當引用計數爲0的時候會調用dealloc
//最新的Xcode版本默認狀況下會開啓ARC機制的,當開啓這個機制以後,咱們就不能手動的顯示調用這些方法,編譯器會報錯
//因此咱們能夠將這個默認狀態的ARC關閉,可是這個只是爲了測試使用

int main(int argc, const char * argv[]) {

    /*
    Person *person = [[Person alloc] init];//引用計數爲1
    NSLog(@"引用計數:%ld",[person retainCount]);
    
    //引用計數加1
    [person retain];
    
    [person release];
    
    NSLog(@"引用計數:%ld",[person retainCount]);
    [person release];
     */
    
    
    Dog *dog = [[Dog alloc] init];
    [dog setName:@"小黑"];
    
    Dog *dog1 = [[Dog alloc] init];//引用計數爲1
    [dog setName:@"大黃"];
    
    Person *p1 = [[Person alloc] init];
    [p1 setName:@"張三"];
    [p1 setDog:dog];
    [p1 setDog:dog1];//狗的引用替換成了大黃
    
    Person *p2 = [[Person alloc] init];
    [p2 setName:@"李四"];
    [p2 setDog:dog];
    
    //這裏引用計數爲1,這個和咱們以前說的引用計數管理有矛盾,因此咱們在使用的時候須要手動的retain
    NSLog(@"引用計數爲:%ld",[dog retainCount]);
    
    [dog1 release];//由於alloc的時候引用計數就爲1了
    
    //這裏就有一個問題了,dog1對象已經被銷燬了,可是setDog對象仍是用了dog1對象調用方法了,這就會報錯了
    //因此又對set方法進行改進了
    [p1 setDog:dog1];
    
    //當人銷燬的時候,還須要對狗的引用-1
    //在人的dealloc方法中實現
    
    Person *p3 = [[Person alloc] initWithDog:dog1];
    [dog1 release];//dog1的引用計數:0
    
    [p3 playDog];
    
    [p3 release];
    
    return 0;
}


下面咱們來詳細說明一下:

首先若是想演示這個例子的話,須要修改一下設置:



最新的XCode默認是會自動選上ARC(Automatic Reference Counting),若是咱們不把這個手動關閉的話,代碼中會報錯的。

alloc用來建立對象,建立完成以後,引用計數爲1,只調用一次

retain使引用計數+1,能夠多調用幾回

release使引用計數-1,能夠多調用幾回

當引用計數爲0的時候會調用dealloc

黃金法則:每次調用alloc一次,都須要調用release一次,他們兩是成對出現的

最新的Xcode版本默認狀況下會開啓ARC機制的,當開啓這個機制以後,咱們就不能手動的顯示調用這些方法,編譯器會報錯

因此咱們能夠將這個默認狀態的ARC關閉,可是這個只是爲了測試使用


同時咱們會發現main.m文件中沒有自動釋放池了@autoreleasepool了。


一、簡單的例子

Person *person = [[Person alloc] init];//引用計數爲1
NSLog(@"引用計數:%ld",[person retainCount]);

//引用計數加1
[person retain];

[person release];

NSLog(@"引用計數:%ld",[person retainCount]);
[person release];
咱們建立了一個Person類,而後能夠打印一下的引用計數值:1

當咱們調用retain方法的時候,引用計數就會+1,當咱們調用release方法的時候引用計數會-1

一旦系統發現引用計數爲0的時候,就會銷燬這個對象,調用dealloc方法


二、set方法來控制引用計數

這個例子簡單吧,沒什麼問題的,如今咱們把問題複雜化

Dog *dog = [[Dog alloc] init];
[dog setName:@"小黑"];

Dog *dog1 = [[Dog alloc] init];//引用計數爲1
[dog setName:@"大黃"];

Person *p1 = [[Person alloc] init];
[p1 setName:@"張三"];
[p1 setDog:dog];
[p1 setDog:dog1];//狗的引用替換成了大黃

Person *p2 = [[Person alloc] init];
[p2 setName:@"李四"];
[p2 setDog:dog];

//這裏引用計數爲1,這個和咱們以前說的引用計數管理有矛盾,因此咱們在使用的時候須要手動的retain
NSLog(@"引用計數爲:%ld",[dog retainCount]);

[dog1 release];//由於alloc的時候引用計數就爲1了

//這裏就有一個問題了,dog1對象已經被銷燬了,可是setDog對象仍是用了dog1對象調用方法了,這就會報錯了
//因此又對set方法進行改進了
[p1 setDog:dog1];

//當人銷燬的時候,還須要對狗的引用-1
//在人的dealloc方法中實現
咱們定義了兩條狗,而後將這兩條狗經過setDog方法設置到p1對象上,同時將第一條狗設置到p2對象上,這時候咱們打印一下第一條狗的引用計數,發現他的引用計數是1,緣由很簡單,這個1是在alloc時候有的,可是如今是有問題的,由於第一條狗被p1和p2引用者,按照正常狀況,第一條狗的引用計數爲3的。因此這時候咱們就須要修改一下Person類中setDog方法了

- (void)setDog:(Dog *)dog{
    //引用計數須要+1
    _dog = [dog retain];
    
    //有時候可能須要替換Dog對象,因此這裏還要注意釋放Dog的引用
}
咱們須要手動的增長dog的引用計數,這樣就正常了。


如今又有一個問題了,上面p1對象設置了第二條狗

[p1 setDog:dog1];//狗的引用替換成了大黃
按照上面的setDog方法,咱們又發現一個問題,如今p1引用的狗是第二條狗了,不是第一條,那麼這時候按照正常狀況第一條狗的引用計數-1,由於他已經再也不被p1引用了。因此setDog方法還得修改:

- (void)setDog:(Dog *)dog{
    //使用nil去調用方法是沒有錯誤的
    //可是當一個對象被銷燬的時候,指針就變成野指針了,這時候調用方法會出錯的
    [_dog release];
    _dog = [dog retain];
}
這時候咱們就在賦值以前,對_dog調用一次release方法,這樣就解決了上面的那個問題。


可是如今還有一個問題:

[dog1 release];//由於alloc的時候引用計數就爲1了
NSLog(@"dog1:%ld",[dog1 retainCount]);
//這裏就有一個問題了,dog1對象已經被銷燬了,可是setDog對象仍是用了dog1對象調用方法了,這就會報錯了
//因此又對set方法進行改進了
[p1 setDog:dog1];
在執行release代碼以後,dog1的引用計數爲1,這是繼續調用setDog方法:

- (void)setDog:(Dog *)dog{
    //使用nil去調用方法是沒有錯誤的
    //可是當一個對象被銷燬的時候,指針就變成野指針了,這時候調用方法會出錯的
    [_dog release];
    _dog = [dog retain];
}
這時候_dog是dog1,繼續調用release方法,執行完以後,引用繼續爲0了,對象被釋放了,可是這時候dog參數的值仍是dog1,那麼在執行retain方法就會報錯了,這個緣由很簡單,就是咱們兩次調用了setDog方法,將dog1設置了兩次。中間調用一次release方法。因此解決這樣的問題,咱們在修改一下setDog方法:

//第三種方式
- (void)setDog:(Dog *)dog{
    //這裏的判斷是由於setName方法可能會被屢次調用
    if(_dog != dog){
        [_dog release];
        _dog = [dog retain];
    }
}
我只須要判斷一下,以前的屬性值和當前須要設置的值是否相同。這種方式就是沒有問題了,因此咱們之後在寫set方法的時候,這就是模板了。


三、銷燬方法dealloc控制引用計數

如今假如p1被銷燬了,那麼對於dog1來講引用計數應該-1的,因此須要在Person類中的dealloc方法中修改一下:

- (void)dealloc{
    //對象類型的屬性都須要在這裏進行釋放引用
    //對狗進行釋放
    [_dog release];
    NSLog(@"dealloc is Executing");
    [super dealloc];
}


四、初始化方法控制引用計數

如今還有一種狀況,若是咱們使用初始化方法來設置Dog屬性值:

- (id)initWithDog:(Dog*)dog{
    //使用初始化的時候傳入dog
    self = [super init];
    if(self != nil){
        //由於初始化方法只會調用一次,因此這裏就沒有作判斷了
        [_dog release];
        _dog = [dog retain];
    }
    return self;
}
咱們這裏的處理和setDog方法的處理方式同樣,可是這裏不須要作一次判斷了,由於初始化方法只會調用一次。因此不會出現setDog的第三種狀況。


總結

這一篇主要介紹了OC中內存管理的概念和用法,後面會繼續介紹內存管理的相關知識。

相關文章
相關標籤/搜索