1. 程式人生 > >黑馬程式設計師_OC之記憶體管理

黑馬程式設計師_OC之記憶體管理

記憶體管理

------ IOS培訓、android培訓、期待與您交流!-------

一、基本原理:

    1、記憶體管理的定義

       1>、移動移動裝置的記憶體極其有限,每個app所能佔用的記憶體是有限制的

       2>、當app所佔用的記憶體較多時,系統會發出記憶體警告,這時得回收一些不需要再使用的記憶體空間。比如回收一些不需要使用的物件、變數等

       3>、管理範圍:任何繼承了NSObject的物件,對其他基本資料型別(int、char、float、double、struct、enum等)無效

    2、計數器的作用

       1>、每個OC物件都有自己的引用計數器,表示“物件被引用的次數”,即有多少人正在使用這個OC物件


       2>、當一個物件的引用計數器值為0時,物件佔用的記憶體就會被系統回收。換句話說,如果物件的計數器不為0,那麼在整個程式執行過程,它佔用的記憶體就不可能被回收,除非整個程式已經退出

       3>、當使用alloc、new或者copy建立一個新物件時,新物件的引用計數器預設就是1

    3、計數器的操作

       1>、給物件傳送一條retain訊息,可以使引用計數器值+1(retain方法返回物件本身)

       2>、給物件傳送一條release訊息,可以使引用計數器值-1

       3>、可以給物件傳送retainCount訊息獲得當前的引用計數器值


    4、物件的銷燬

       1>、當一個物件的引用計數器值為0時,那麼它將被銷燬,其佔用的記憶體被系統回收

       2>、當一個物件被銷燬時,系統會自動向物件傳送一條dealloc訊息

一般會重寫dealloc方法,在這裡釋放相關資源,dealloc就像物件的遺言

一旦重寫了dealloc方法,就必須呼叫[super dealloc],並且放在最後面呼叫

不要直接呼叫dealloc方法

       3>、一旦物件被回收了,它佔用的記憶體就不再可用,堅持使用會導致程式崩潰(野指標錯誤)

二、引用計數器的基本操作:

    在main.h檔案中

    #import <Foundation/Foundation.h>

    #import "Person.h"

    int main()
    {

        Person *p= [[Person alloc] init];  //計數器為1

        NSUInteger c = [p retainCount]; 

        NSlog(@"計數器:%ld",c);  

        [p retain];//計數器為2

        [p release]; //計數器為1

        return 0;
     }  

     驗證物件是否被回收,在Person.m檔案中重寫dealloc方法,在dealloc中列印,dealloc方法一定要呼叫super,否則會發出警告;

     #import "Person.h"

     @implementation Person

     //當一個Person物件被回收的時候,就會自動呼叫這個方法

     - (void)dealloc

     {  //super的dealloc一定要呼叫,而且放在後面

         NSLog(@"Person物件被回收");

         [super dealloc];
      }

     @end

  1、小結:1> retain :計數器+1,會返回物件本身

     2> release :計數器-1,沒有返回值

     3> retainCount :獲取當前的計數器

     4> dealloc

         * 當一個物件要被回收的時候,就會呼叫

         * 一定要呼叫[super dealloc],這句呼叫要放在最後面

         - (void)dealloc
        {
            NSLog(@"物件被回收");

            // super的dealloc一定要呼叫,而且放在最後面

            [super dealloc];
        }

  2、概念:

    1>、殭屍物件 :所佔用記憶體已經被回收的物件,殭屍物件不能再使用

    2>、 野指標 :指向殭屍物件(不可用記憶體)的指標,給野指標傳送訊息會報錯(EXC_BAD_ACCESS)

    3> 、空指標 :沒有指向任何東西的指標(儲存的東西是nil、NULL、0),給空指標傳送訊息不會報錯;

三、多物件記憶體管理

    1.你想使用(佔用)某個物件,就應該讓物件的計數器+1(讓物件做一次retain操作)

    2.你不想再使用(佔用)某個物件,就應該讓物件的計數器-1(讓物件做一次release)

    3.誰retain,誰release

    4.誰alloc,誰release

    @property會遮蔽一些記憶體管理細節

    5.程式碼例項

     在Book.h檔案中

     #import<Foundation/Foundation.h>

     @interface Book : NSObject
     {
         int _price;    
      }

      - (void)setPrice : (int)price;

      - (int)price;

     @end
     在Book.m檔案中

     #import "Book.h"

     @implementation Book

     - (void)setPrice : (int)price
     {
         _price = price;
     }

      - (int)price
      {
          return _price;
       }

        - (void)dealloc
      {

          NSLog(@"Book物件被回收");

          [super dealloc];
       }

      @end
     在Person.h檔案中

      #import<Foundation/Foundation.h>

      #import "Book.h"

     @interface Person : NSObject
     {
         Book  *_book;    
      }

      - (void)setBook : (Book *)book;

      - (Book *)book;

      @end

     在Person.m檔案中

     #import"Person.h"

     @implementation Person

     - (void)setBook : (Book *)book
     {
        _book = [book retain];//物件計數器加1
     }

      - (Book *)book
      {

       }

      - (void)dealloc
      {
          [_book release];

          NSLog(@"Person物件被回收");

          [super dealloc];
       }

      @end
     在main.m檔案中

     #import<Foundation/Foundation.h>

     #import"Person.h"

     #import "Book.h"

     int main()
     {

         Book *b = [[Book alloc] init];

         Person *p1 = [[Person alloc] init];

         [p1 setBook:b];//p1想佔用b這本書

         [p1 release];//release只是計數器見,並不是釋放物件

         [b release]; //有alloc必須有release

         return 0;
      }

四、set方法中記憶體管理

    1.set方法的程式碼規範

      1> 基本資料型別:不需要管理記憶體,直接複製

        - (void)setAge:(int)age
        { 
            _age = age;
         }

      2> OC物件型別

         - (void)setCar:(Car *)car
        {
             // 1.先判斷是不是新傳進來物件

            if ( car != _car )
           {
              // 2.對舊物件做一次release
                [_car release];

                // 3.對新物件做一次retain
                 _car = [car retain];
            }
         }

    注:1>如果新物件和舊物件一樣,沒用

        2>、如果物件不是由alloc產生的,就不需要release

    2.dealloc方法的程式碼規範

      1> 一定要[super dealloc],而且放到最後面

      2> 對self(當前)所擁有的其他物件做一次release

         - (void)dealloc
        {
           [_car release];

           [super dealloc];
        }

    3.property引數:property生成的set和get方法是最簡單的方法,是直接賦值,並不管記憶體。

      1>、使用@property時,@property (retain) Book *book;

         retain: 生成的set方法中,release舊值,retain新值(用於OC物件)
         相當於下列的程式碼:  [_book release];

                            _book = [book retain];

         //釋放到以前的名稱,在重新賦值

         //property中生成retain,dealloc中實現release,故還需要寫dealloc方法,set方法可以用@property實現

         //需要在實現中重寫dealloc
              - (void)dealloc
            {
                [_namerelease];

                [superdealloc]; //注意一定要放在最後
             }

    4、記憶體管理相關的引數

       1>、retain: release舊值,retain新值(用於OC物件)

       2>、assign:直接賦值,不做任何記憶體管理(預設,用於非OC物件型別)

       3>、copy  :release舊值,copy新值(一般用於NSString *)

     5、是否要生成set方法

        readwrite:同時生成set方法和get方法的宣告和實現(預設)

        readonly  :只會生成get方法的宣告和實現(只讀)

        注:上述的關鍵字可以寫在一起:@property (readwrite assign) int height;

     6、多執行緒管理

        atomic   :效能低(預設)

        nonatomic:效能高(一般用這個)

     7、控制set方法和get方法的名稱

        setter:設定set方法的名稱,一定有個冒號:

        getter:設定get方法的名稱(一般用在bool型別)

        @property (getter = abc,set = setAbc:) int weight;

        @property (getter = isRich )BOOL rich;

        //返回bool型別的方法名一般以is開頭

     8、模型設計

        在User.h檔案中

        /*  作者:MJ

            描述:微博使用者

            時間:

            檔名:User.h  */

        #inport<Foundation/Foundation.h>

        typedef enum{

            SexMan,

            SexWoman

         }Sex;

         typedef struct{

             int year;

             int month;

             int day;

         }Date;//結構體並不是物件

        @interface User : NSObject

        @property  (nonatomic, retain) NSString *name;

        @property  (nonatomic, retain) NSString *account;  

        @property  (nonatomic, retain) NSString *password;

        //頭像,存路徑 http://weibo.com/a.png URL

        @property  (nonatomic, retain) NSString *icon; //頭像

        @property  (nonatomic,assign ) Sex sex; //性別

        @property  (nonatomic, retain) NSString *phone; //電話

        @property  (nonatomic, assign) Date birthday;

        @end

        在User.m檔案中

        @import "User.h"

        @implementation User

        - (void)dealloc

        {  
             [_name release];

             [_account release];

             [_icon release];

             [_password release];  

             [super dealloc];
         }

         @end 

         //如果在User.h檔案中有很多@property,在這裡需要就需要寫很多release,太麻煩了,所以設計一個類Status表示微博

         在Status.h檔案中

         #import<Foundation/Foundation.h>

     //微博內容、微博配圖、傳送時間、轉發的微博、被評論數、被轉發數

         @interface Status : NSObject

         @property  (nonatomic, retain) NSString *text;

         @property  (nonatomic, retain) NSString *icon;  //配圖

         @property  (nonatomic, assign) time_t time;
         //time_t是C語言表示時間的資料型別,其實就是long

         @property  (nonatomic, retain) User *user;

         @property  (nonatomic, retain) Status *retweetStatus;
        //轉發的微博

         @property  (nonatomic, assign)int commentCount;//評論數

         @property  (nonatomic, retain) Status *retweetCount;
         //轉發微博數

         @end

         在Status.m檔案中

         #import "Status.h"

         @implementation Status

         - (void)dealloc

         {
              [_text release];

              [_user release];

              [_retweetStatus release];

               [super dealloc];
          }

         @end

         在main.m檔案中

         #import<Foundation/Foundation.h>

         @import "User.h"

         #import "Status.h"

         int main()
         {       
             //新建兩條微博
             Status *s = [[Status alloc] init];

             s.text = @"今天天氣真好!!!";

             Status *s2 = [[Status alloc] init];

             s2.text = @"今天天氣真的很好!";

             s2.retweetStatus = s;

              //新建兩個使用者
             User *u = [[User alloc] init ];

             u.name = @"AAA";

             s.user = u;

             User *u2 = [[User alloc] init ];

             u2.name = @"BBB";

             s2.user = u2;

             [s2 release];

             [s release];

             return 0;
          }
五、迴圈引用:類與類之間的迴圈引用

     1. @class使用場景:對於迴圈依賴關係來說,比方A類引用B類,同時B類也引用A類

     這種程式碼編譯會報錯。但是當使用@class在兩個類相互宣告,就不會出現編譯報錯

     2. 用法概括:

        1>、使用 @class 類名; 就可以引用一個類,說明一下它是一個類

        2>、開發中引用一個類的規範

            在.h檔案中用@class來宣告類

            在.m檔案中用#import來包含類的所有東西

        3>、和#import的區別

            #import方式會包含被引用類的所有資訊,包括被引用類的變數和方法;

            @class方式只是告訴編譯器在A.h檔案中 B *b 只是類的宣告,具體這個類裡有什麼資訊,這裡不需要知道,等實現檔案中真正要用到時,才會真正去檢視B類中資訊

        4>、如果有上百個標頭檔案都#import了同一個檔案,或者這些檔案依次被#improt,那麼一旦最開始的標頭檔案稍有改動,後面引用到這個檔案的所有類都需要重新編譯一遍,這樣的效率也是可想而知的,而相對來講,使用@class方式就不會出現這種問題了

        5>、在.m實現檔案中,如果需要引用到被引用類的實體變數或者方法時,還需要使用#import方式引入被引用類

      3.迴圈retain

        比如A物件retain了B物件,B物件retain了A物件

        這樣會導致A物件和B物件永遠無法釋放

        解決方案當兩端互相引用時,應該一端用retain、一端用assign

六、 autorelease自動釋放池

     1、autorelease的基本用法

       1> 會返回物件本身 
               Person *p = [[[Person alloc] init] autolease]

       2> 會將物件放到一個自動釋放池中

       3> 當自動釋放池被銷燬時,會對池子裡面的所有物件做一次release操作,不用再每次都對物件release

       4>、@autoreleasepool
           {  //開始代表建立了一個釋放池

               Person *p = [[[Person alloc] init] autolease]

           }//結束代表銷燬

       5> 呼叫完autorelease方法後,物件的計數器不變

     2、autorelease的好處

      1> 不用再關心物件釋放的時間

       2> 不用再關心什麼時候呼叫release

     3、autorelease的使用注意

       1> 佔用記憶體較大的物件不要隨便使用autorelease

       2> 佔用記憶體較小的物件使用autorelease,沒有太大影響

     4、常見的錯誤

       1> alloc之後呼叫了autorelease,又呼叫release

          @autoreleasepool
          {
              // 1
             Person *p = [[[Person alloc] init] autorelease];

             // 0
             [p release];
           }//-1

           這樣會發生野指標錯誤

       2> 連續呼叫多次autorelease

          @autoreleasepool
          {
            Person *p = [[[[Person alloc] init] autorelease] autorelease];
           }

     5、自動釋放池

       1> 在iOS程式執行過程中,會建立無數個池子。這些池子都是以棧結構存在(先進後出)

       2> 當一個物件呼叫autorelease方法時,會將這個物件放到棧頂的釋放池

     6、程式碼案例:

      int main()
     {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

        Person *pp = [[[Person alloc] init] autorelease];

        [pool release]; // [pool drain];

        @autoreleasepool
        {

            // 1
            Person *p = [[[[Person alloc] init] autorelease] autorelease];

           // 0
           // [p release];
         }

         return 0;
     }

     void test()
    {
        @autoreleasepool
        {  // {開始代表建立了釋放池

           // autorelease方法會返回物件本身
           //呼叫完autorelease方法後,物件的計數器不變
           // autorelease會將物件放到一個自動釋放池中
          // 當自動釋放池被銷燬時,會對池子裡面的所有物件做一次release操作
           Person *p = [[[Person alloc] init] autorelease];

            p.age = 10;

           @autoreleasepool
          {
              // 1
              Person *p2 = [[[Person alloc] init] autorelease];
              p2.age = 10;

           }

           Person *p3 = [[[Personalloc]init]autorelease];

        }//  結束代表銷燬釋放池
     }

     注:1>、系統自帶的方法裡面沒有包含alloc、new、copy,說明返回的物件都是autorelease

         2>、開發中經常會提供類方法,快速建立一個autorelease

------ IOS培訓、android培訓、期待與您交流!-------