iOS開發筆記(一):內存管理

這是個人第一篇文章,有什麼不對的地方還煩請你們指出啦,總以爲作開發頗有必要作筆記,方便記錄本身的所得也能與你們探討,可是以前一直比較懶因此無所做爲,那接下來的時間一塊兒好好努力吧各位加油加油。編程

1.總起

1.1 瞭解內存管理

瞭解內存管理的最普通的方式是考慮全部權。若是Manny對Jack曾說過alloc,retain或者copy,則表示Manny已經宣稱對Jack的全部權。多個對象能夠同時擁有Jack,但每一個對象只負責正確地管理本身對Jack的全部權,最終釋放Jack是每一個Jack全部者的責任,而Jack的非全部者永遠不須要釋放Jack。只要全部擁有Jack全部權的對象都以這種方式執行,Jack就不會泄漏也不會有任何指向Jack的指針留下來搖晃。安全

1.2 相關知識點

  • 野指針:指針變量沒有進行初始化或指向的空間已經釋放。多線程

    • 使用野指針調用對象方法,會報異常,程序崩潰。
    • 一般在調用完release方法後,把保存對象指針的地址清空(不是銷燬對象,引用計數爲0時纔會調用dealloc銷燬對象),賦值爲nil,但OC中沒有空指針異常,因此[nil retain]調用方法不會有異常。
  • 內存泄漏:框架

    • 在ARC自動引用計數模式下,形成內存泄漏的狀況:函數

      • 如Person *person = [Person new];(對象指針提早賦值nil或者清空,⚠️提早)在棧區中的person已經釋放,而堆區new產生的對象尚未釋放,就會形成內存泄漏。
    • 在MRC手動引用計數模式下,形成內存泄漏的狀況:優化

      • 沒有配對釋放,不符合內存管理原則。
      • 對象指針提早賦值nil或者清空,致使release不起做用。
  • 殭屍對象:堆中已經被釋放的對象(retainCount=0)。atom

  • 空指針:指針賦值爲空(nil)。spa

提醒

一個變量的名稱,包括實例變量,只是一個指針。當你向該指針發送消息時,你實際上就是經過該指針將消息發送到它指向的對象。內存管理的規則是關於對象的原則,而不是關於名稱、引用或指針的規則。你不能遞增或遞減一個指針的保留計數,由於沒有這個東西。指針所佔用的內存是自動管理的(並且很小)。內存管理所關注的是指針所指向的對象。.net

2.MRC

2.1 實現原理

Objective-C對象中保存着引用計數這一整數值。調用alloc或者retain方法後,引用計數+1。調用release後,引用計數-1。引用計數爲0時,調用dealloc方法廢棄對象。線程

2.2 相關操做

對象操做 Objective-C方法 引用計數
生成並持有對象 alloc/new/copy/mutablecopy 1
持有對象 retain +1
釋放對象 release -1
廢棄對象 dealloc 0

2.3 內存管理的思考方式

  • 本身生成的對象,本身持有

    NSObject *obj = [[NSObject alloc] init];
      [obj retain];
      NSLog(@"obj - %lu",[obj retainCount]);
    複製代碼
  • 非本身生成的對象,本身也能持有

    id obj = [NSMutableArray array];
      [obj retain];
      NSLog(@"obj - %lu",[obj retainCount]);
    複製代碼
  • 再也不須要本身持有的對象時釋放

    NSObject *obj = [[NSObject alloc] init];//本身生成本身持有
      [obj retain];
      NSObject *obj2 = [obj retain];//非本身生成,本身持有
      [obj release];
      [obj2 release];
      NSLog(@"obj - %lu",[obj retainCount]);
    複製代碼
  • 非本身持有的對象本身沒法釋放

    id obj = [NSMutableArray array];
      [obj release];
    複製代碼

提醒

MRC下對象的引用計數能夠經過[object retainCount]方法得到。

2.4 NSAutoreleasePool

autorelease故名思議就是自動釋放,看上去很像ARC,但其實更相似於C語言中的局部變量,也就是說,超出變量做用域的時候將自動被廢棄,但這裏與C語言不一樣的是,編程人員能夠手動設置其做用域。 autorelease的具體使用方法以下:

  • 生成並持有NSAutoreleasePool對象
  • 調用已分配的autorelease方法
  • 廢棄NSAutoreleasePool對象

NSAutoreleasePool對象的生命週期至關於C語言變量的做用域,對於全部調用過autorelease方法的對象,在廢棄NSAutoreleasePool對象時,都將對對象統一調用release方法,代碼以下所示:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init] ;
id obj = [ [NSObject alloc]init];
[obj autorelease] ;
[pool drain];
複製代碼

提醒

在Cocoa框架中,若是不是使用alloc/new/copy/mutablecopy這幾個方法返回的對象,其他方法返回的對象都將自動註冊到NSAutoreleasePool中,id array = [NSMutableArray arrayWithCapacity:10];其實也就等同於:id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];

3.ARC

ARC是Objective-C編譯器的特性,而不是運行時特性或者垃圾回收機制,ARC所作的只不過是在代碼編譯時爲你自動在什麼時候的位置插入release或者autorelease,減小了開發的工做量。但咱們有時仍須要四種全部權修飾符來配合ARC來進行內存管理。

3.1 四種全部權修飾符

  • __strong:強引用,持有所指向對象的全部權,無修飾符狀況下的默認值。如需強制釋放,可置nil(這裏先建立一個指針,將新的值retain一次,將指針動態指向新的值,並將舊的值release一次)。

    NSObject *obj = [[NSObject alloc]init];
      //他們是等價的
      NSObject __strong *obj = [[NSObject alloc]init];
    複製代碼
  • __weak:弱引用,不持有所指向對象的全部權,引用指向的對象內存被回收以後,引用自己會置nil,避免野指針.避免循環引用,會將對象註冊到autoreleasepool(既不保留新值,也不釋放舊值,動態地將指針指向新的值,若是這個值剛被dealloc,就會將指針更新爲一個nil指針)。

  • unsafe_unretained:至關於assign。直接賦值。引用計數不變。他會發生野指針現象。因此不安全。不像weak,當指向的對象爲空的時候,將指針置爲nil。

  • _autoreleasing:將對象賦值給附有 _ autoreleasing 修飾符的變量等同於ARC 無效時調用對象的autorelease方法。

    @autoreleasepool {
      	id __autoreleasing obj = [[NSObject alloc] init];
      }
    複製代碼

3.2 底層實現

  • __strong底層實現

    • alloc/new/copy/mutablecopy狀況下

      //alloc爲例的模擬底層代碼爲:
        id __Strong obj = [[NSObject alloc] init];
        id obj = objc_msgSend(NSobject, @selector(alloc));
        objc_msgSend(obj, @selector(init));
        objc_release(obj);
      
        //編譯器自動幫咱們加入了release,來釋放對象
      複製代碼
    • 非alloc/new/copy/mutablecopy狀況下

      //array爲例的模擬底層代碼爲:
        id obj = objc_msgSend(NSMutableArray, @selector(array));
        objc_retainAutoreleasedReturnValue(obj);
        objc_release(obj);
        
        //objc_retainAutoreleasedReturnValue(obj)是主要用於最優化程序運行
        
        //array方法的底層模擬爲:
        id obj = objc_msgSend(NSmutableArray,@selector(alloc));
        objc_msgSend(obj,@selector(init));
        return objc_autorealeaseReturnValue(obj);
      複製代碼

      objc_autorealeaseReturnValue(obj)與objc_retainAutoreleasedReturnValue(obj)是成對的,objc_autorealeaseReturnValue(obj)會把對象註冊到autorealeasepool中,可是若是它檢測到方法執行列表中出現objc_retainAutoreleasedReturnValue(obj)方法,那麼就不會將返回的對象註冊到autorealeasepool,而是直接傳遞到方法和函數的調用方。這樣直接傳遞能夠達到最優化。

  • __weak底層實現

    • __weak源碼

      id __weak obj1 = obj;
        
        //模擬代碼
        id obj1;
        obj1 = 0;
        objc_storeWeak(&obj1,obj);
        objc_destoryWeak(&obj1);  等同於objc_storeWeak(&obj1,0);
      複製代碼

      objc_storeWeak(&obj1,obj)函數將第二個參數的賦值對象的地址做爲鍵值,將第一個參數的附有__weak修飾符的變量的地址註冊到weak表中,若是第二個參數爲0,則把變量的地址從weak中刪除。一個鍵值能夠註冊多個變量的地址因而可知,若是大量的weak變量,則會消耗CPU資源,因此weak 只用來避免循環引用。

    • __weak與@autoreleasepool

      id __weak obj1 = obj;
        NSLog(@"%@",obj1);
        
        //模擬代碼
        id obj1;
        objc_initWeak(&obj1,obj);
        id temp = objc_loadWeakRetained(&obj1);
        objc_autorelease(temp);
        NSLog(@"%@",temp);
        objc_destoryWeak(&obj1);
      複製代碼

      因此在@autorealeasepool塊結束前能夠放心使用weak修飾變量

    • __autoreleasing底層實現

      將對象賦值給附有__autoreleasing修飾符的變量等同於ARC無效時,調用對象的autorelease方法。

      • 使用alloc/new/copy/mutableCopy時

        @autoreleasepool {
          	id __autoreleasing obj = [[NSObject alloc] init];
          }
          
          //模擬代碼
          id pool = objc_autoreleasePoolPush();
          id obj = objc_msgSend(NSObject, @selector(alloc));
          objc_msgSend(obj, @selector(init));
          objc_autorelease(obj);
          objc_autoreleasPoolPop(pool);
        複製代碼
      • 使用alloc/new/copy/mutableCopy之外的方法時

        @autoreleasepool {
            	id __autoreleasing obj = [NSMutableArray array];
          }
          
          // 模擬代碼
          id pool = objc_autoreleasePoolPush();
          id obj = objc_msgSend(NSMutableArray,@selector(array));
          objc_retainAutoreleasedReturnValue(obj);
          objc_autorelease(obj);
          objc_autoreleasPoolPop(pool);
          // 雖然 obj 持有對象的方法變爲 objc_retainAutoreleasedReturnValue, 可是將 obj 所引用的對象註冊到 autoreleasepool 中的方法並無改變
        複製代碼

      關於@autoreleasepool,在ARC下應該使用@autoreleasepool而不是NSAutoreleasePool,@autoreleasepool的具體實現將在以後的文章中繼續探討。

3.3 使用規則

  • 不能使用retain/release/retainCount/autorelease
  • 不重載dealloc(若是是釋放對象內存之外的處理,是能夠重載該函數的,可是不能調用[super dealloc])
  • 不能使用NSAllocateObject, NSDeallocateObject
  • 不能在C結構體中使用對象指針
  • id與void *間的若是cast時須要用特定的方法(__bridge關鍵字)
  • 不能使用NSAutoReleasePool,而須要使用@autoreleasepool塊
  • 不能使用區域(NSZone)

4.屬性的內存管理

ObjC2.0引入了@property,提供成員變量訪問方法、權限、環境、內存管理類型的聲明,下面主要說明ARC中屬性的內存管理。屬性的參數分爲三類,基本數據類型默認爲(atomic,readwrite,assign),對象類型默認爲(atomic,readwrite,strong),其中第三個參數就是該屬性的內存管理方式修飾,修飾詞能夠是如下之一:

  • assign

    直接賦值,通常用來修飾基本數據類型。固然也能夠修飾ObjC對象,可是不推薦,由於被assign修飾的對象釋放後,指針仍是指向釋放前的內存,在後續操做中可能會致使內存問題引起崩潰。

    @property (nonatomic, assign) NSInteger count;
    複製代碼
  • retain

    retain和strong同樣,都用來修飾ObjC對象,使用set方法賦值時,實質上是會先保留新值,再釋放舊值,再設置新值,避免新舊值同樣時致使對象被釋放的的問題。

    //MRC寫法以下
      - (void)setCount:(NSObject *)count {
      		[count retain];
       		[_count release];
       		_count = count;
       	}
       
     	//ARC對應寫法
     	- (void)setCount:(NSObject *)count {
      		_count = count;
      	}
    複製代碼
  • copy

    通常用來修飾String、Dict、Array等須要保護其封裝性的對象,尤爲是在其內容可變的狀況下,所以會拷貝(深拷貝)一分內容給屬性使用,避免可能形成的對源內容進行改動。使用set方法賦值時,實質上是會先拷貝新值,再釋放舊值,再設置新值。實際上,遵照NSCopying的對象均可以使用copy,固然,若是你肯定是要共用同一份可變內容,你也可使用strong或retain。

  • weak

    ARC新引入修飾詞,可代替assign,比assign多增長一個特性(置nil)。weak和strong同樣用來修飾ObjC對象。使用set方法賦值時,實質上不保留新值,也不釋放舊值,只設置新值。

    @property (weak) id<MyDelegate> delegate;
    複製代碼
  • strong

    ARC新引入修飾詞,可代替retain,ARC通常都寫strong。

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

    self.person = per;

    若是是strong,對象的retainCount爲2,若是爲weak,對象的retainCount爲1。

  • unsafe_unretained

    等價於assign,能夠用來修飾數據類型和OC對象,可是不會使計數器加1,且對象銷燬時也不會將對象指向nil,容易形成野指針錯誤。

5.block的內存管理

OC中使用block必須本身管理內存,錯誤的內存管理將致使循環引用等內存泄漏問題,這裏主要說明在ARC下block聲明和使用的時候須要注意的兩點:

  • 若是你使用@property去聲明一個block的時候,通常使用copy來進行修飾(固然也能夠不寫,編譯器自動進行copy操做),儘可能不要使用retain。

    @property (nonatomic, copy) void(^block)(NSData * data);
    複製代碼
  • block會對內部使用的對象進行強引用,所以在使用的時候應該肯定不會引發循環引用,固然保險的作法就是添加弱引用標記。

    __weak typeof(self) weakSelf = self;
    複製代碼

6.參考

相關文章
相關標籤/搜索