編碼篇-ARC下的內存泄漏


</br>

前言

內存泄露是一個相對挺嚴重的問題,可是它的存在未引起足夠的重視,如果程序運行時一直分配內存而不及時釋放無用的內存,程序占用的內存越來越大,直到把系統分配給該APP的內存消耗殫盡,程序因無內存可用導致崩潰,這樣的情況我們稱之為內存泄漏。如果某個對象沒有始終在內存中,并且依然會做一些事的時候,這樣的的Bug是非常嚴重而且難以排查的。

內存泄漏可能引起的問題:

  • 內存消耗殆盡的時候,程序會因沒有內存被殺死,即crash。
  • 當內存快要用完的時候,會非常的卡頓
  • 如果是ViewController沒有釋放掉,引起的內存泄露,還會引起其他嚴重的問題,尤其是和通知相關的。沒有被釋放掉的ViewController還能接收通知,還會執行相關的動作,所以會引起各種各樣的異常情況的發生。

那么ARC下內存泄漏的場景有哪些呢

值得注意的是:ARC是編譯器(時)特性,而不是運行時特性,更不是垃圾回收器(GC)。
ARC這是一種編譯期的內存管理方式,在編譯期間,編譯器會判斷對象的使用情況,并在合適的位置加上retain和release,使得對象的內存被合理的管理。所以,從本質上說ARC和MRC在本質上是一樣的,都是通過引用計數的內存管理方式。

  • CF類型內存

ARC 可以幫忙管理 Objective-C 對象, 但是不支持 Core Foundation 對象的管理,所以轉換后要注意一個問題:誰來釋放使用后的對象。
注意以creat,copy作為關鍵字的函數都是需要釋放內存的,注意配對使用。比如:CGColorCreate<-->CGColorRelease

那Objective-C 和 Core Foundation 對象相互轉換時就可能出現內存泄漏的問題,可參考這篇文章處理。

  • MRC內存使用

    這部分不做詳細介紹,也是注意配對使用,需要說明的是,如果代碼中有部分文件是MRC的,在已有文件中加代碼的時候注意一下,不能都按照ARC的方式處理。

  • 循環引用

    • block引起的循環引用。
      某個類將block作為自己的屬性變量,然后該類在block的方法體里面又使用了該類本身;相互持有,導致都釋放不了。
      下面這樣的方式就可以解決block引起的循環引用:
      __weaktypeof(self) weakSelf =self;
      block內的self,換成weakSelf就行了。
      block不是self的屬性或者變量時,在block內使用self不會循環引用;
      像這樣的方法中調用self,不會引起,但是屬性的形式中調用self就會以

        [self.myTest doSomeTest:^(NSInteger cellIndex) {
            self.allInter = cellIndex;
        }];
      
  • 引用大循環
    ?就像前面說的,引用循環可能是一個大循環。我遇到過一種情況,就是給UITableViewCell設置block屬性響應事件,在block中強引用了self,
    導致self->tableView->cell->self形成循環。
    有時候隨著代碼量的增大,邏輯的負責,很容易形成一個很大的循環引用,最后造成內存泄漏。

  • ** NSTimer的使用**

    NSTimer,NSTimer會對它的target持有強引用,如果NSTimer不釋放掉,就會一直持有它的target的強引用,如果這個NSTimer在被target強引用,會一直都釋放不掉,造成內存泄露。

    下面的代碼在書寫的時候Xcode是不會報任何錯誤和警告的。但是實際上已經形成了循環引用。造成了內存泄漏。

    @property (nonatomic, strong) NSTimer *timer;
    @property(copy,nonatomic)NSString *name;
     self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1
                                                target:self
                                              selector:@selector(handleTimer)
                                              userInfo:nil
                                               repeats:YES];
    
      - (void)handleTimer
      {
           self.name = @"123";
     }
    
  • 單例也會造成內存泄漏

    如果一個單例持有一個block,block內又使用了當前這個ViewController類,會引起循環引用。所以單例持有的代碼塊中要用弱引用,原因是:單例不會被釋放掉,它會一直持有block,導致該block所在的ViewController釋放不掉。

  • performSelector的內存問題

    • performSelector 的動態綁定

      SEL selector;
        if (/* some condition */) {
        selector = @selector(newObject);
       } else if (/* some other condition */) {
        selector = @selector(copy);
       } else {
         selector = @selector(someProperty);
      }
      id ret = [object performSelector:selector];
      

    這段代碼就相當于在動態之上再動態綁定。在 ARC 下編譯這段代碼,編譯器會發出警告

          warning: performSelector may cause a leak because its selector is unknow [-Warc-performSelector-leak]
    

    正是由于動態,編譯器不知道即將調用的 selector 是什么,不了解方法簽名和返回值,甚至是否有返回值都不懂,所以編譯器無法用 ARC 的內存管理規則來判斷返回值是否應該釋放。因此,ARC 采用了比較謹慎的做法,不添加釋放操作,即在方法返回對象的引用計數可能不會減少,從而可能導致內存泄露。

    以本段代碼為例,前兩種情況(newObject, copy)都需要再次釋放,而第三種情況不需要。這種泄露隱藏得如此之深,以至于使用 static analyzer 都很難檢測到。如果把代碼的最后一行改成

        [object performSelector:selector];
    

    不創建一個返回值變量測試分析,簡直難以想象這里居然會出現內存問題。所以如果你使用的 selector 有返回值,一定要處理掉 手動釋放(置為 nil)。

    • performSelector afterDelay 延時操作
      關于內存管理的執行原理是這樣的執行
      [self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3];
      的時候,系統會將tableLayer的引用計數加1,執行完這個方法時,還會將tableLayer的引用計數減1,有時切換場景時延時函數已經被調用但還沒有執行,這時tableLayer的引用計數并沒有減少到0,也就導致了切換場景dealloc方法沒有被調用,出現了內存泄露。

      解決辦法就是取消那些還沒有來得及執行的延時函數,代碼:

      [NSObject cancelPreviousPerformRequestsWithTarget:self]
      

      當然你也可以一個一個得這樣用:

        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(method1:) object:nil]
      

      加上了這個以后,切換場景后會順利地執行了dealloc方法,至此內存泄漏問題解決。

  • 代理未清空引起野指針

    查看iOS的一些API,發現delegate都是assign的,這樣就會引起野指針的問題,可能會引起一些莫名其妙的crash。那么這是怎么引起的,當一個對象被回收時,對應的delegate實體也就被回收,但是delegate的指針確沒有被nil,從而就變成了游蕩的野指針了。所以在delloc方法中要將對應的assign代理設置為nil,如:

    - (void)viewDidDisappear:(BOOL)animate
    {
      self.myTableView.delegate = nil;
      self.myTableView.dataSource = nil;
      通知注銷掉
      kvo remove掉~~
    }
    

那是不是所有的delegate都要這樣做呢?一般自己寫的一些delegate,我們會用weak,而不是assign,weak的好處是當對應的對象被回收時,指針也會自動被設置為nil。

  • 循環未結束

    如果某個ViewController中有無限循環,也會導致即使ViewController對應的view關掉了,ViewController也不能被釋放。
    這種問題常發生于animation處理。

    CATransition *transition = [CATransition animation];
    transition.duration = 0.5;
    tansition.repeatCount = HUGE_VALL;
    [self.view.layer addAnimation:transition forKey:"myAnimation"];
    

上例中,animation重復次數設成HUGE_VALL,一個很大的數值,基本上等于無限循環了。
解決辦法是,在ViewController關掉的時候,停止這個animation。
-(void)viewWillDisappear:(BOOL)animated {
[self.view.layer removeAllAnimations];
}

  • ** try...catch 的使用**
    但如果 doSomethingMayThrowException 方法拋出了異常,那么 object 對象就無法釋放。如果 object 對象持有了重要且稀缺的資源,就可能會造成嚴重后果。

PS其他需要注意的問題

大次數循環內存暴漲問題

記得有道比較經典的面試題,查看如下代碼有何問題:

    for (int i = 0; i < 100000; i++) {
        NSString *string = @"Abc";
        string = [string lowercaseString];
        string = [string stringByAppendingString:@"xyz"];
        NSLog(@"%@", string);
}

該循環內產生大量的臨時對象,直至循環結束才釋放,可能導致內存泄漏,解決方法為在循環中創建自己的autoReleasePool,及時釋放占用內存大的臨時變量,減少內存占用峰值。

for (int i = 0; i < 100000; i++) {
        @autoreleasepool {
            NSString *string = @"Abc";
            string = [string lowercaseString];
            string = [string stringByAppendingString:@"xyz"];
            NSLog(@"%@", string);
      }
  }

附、如何檢測App的內存泄漏問題

  • 借助Xcode自帶的Instruments工具(選取真機測試

    Instruments
  • 簡單暴力的重寫dealloc方法,加入斷點或打印判斷某類是否正常釋放。

    dealloc
  • 使用Xcode8中自帶的有內存檢測警告。


  • 通過Facebook出品的FBMemoryProfiler工具類進行檢測.

集成后的顯示

</br>

這篇ARC下的內存泄漏,洋洋灑灑說了這么多,算是總結的比較詳細和全面的。希望對大家有價值。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容