編碼篇-ARC下的內(nèi)存泄漏


</br>

前言

內(nèi)存泄露是一個(gè)相對(duì)挺嚴(yán)重的問題,可是它的存在未引起足夠的重視,如果程序運(yùn)行時(shí)一直分配內(nèi)存而不及時(shí)釋放無用的內(nèi)存,程序占用的內(nèi)存越來越大,直到把系統(tǒng)分配給該APP的內(nèi)存消耗殫盡洽糟,程序因無內(nèi)存可用導(dǎo)致崩潰,這樣的情況我們稱之為內(nèi)存泄漏堕战。如果某個(gè)對(duì)象沒有始終在內(nèi)存中坤溃,并且依然會(huì)做一些事的時(shí)候,這樣的的Bug是非常嚴(yán)重而且難以排查的嘱丢。

內(nèi)存泄漏可能引起的問題:

  • 內(nèi)存消耗殆盡的時(shí)候薪介,程序會(huì)因沒有內(nèi)存被殺死,即crash屿讽。
  • 當(dāng)內(nèi)存快要用完的時(shí)候昭灵,會(huì)非常的卡頓
  • 如果是ViewController沒有釋放掉,引起的內(nèi)存泄露伐谈,還會(huì)引起其他嚴(yán)重的問題烂完,尤其是和通知相關(guān)的。沒有被釋放掉的ViewController還能接收通知诵棵,還會(huì)執(zhí)行相關(guān)的動(dòng)作抠蚣,所以會(huì)引起各種各樣的異常情況的發(fā)生。

那么ARC下內(nèi)存泄漏的場景有哪些呢

值得注意的是:ARC是編譯器(時(shí))特性履澳,而不是運(yùn)行時(shí)特性嘶窄,更不是垃圾回收器(GC)。
ARC這是一種編譯期的內(nèi)存管理方式距贷,在編譯期間柄冲,編譯器會(huì)判斷對(duì)象的使用情況,并在合適的位置加上retain和release忠蝗,使得對(duì)象的內(nèi)存被合理的管理现横。所以,從本質(zhì)上說ARC和MRC在本質(zhì)上是一樣的,都是通過引用計(jì)數(shù)的內(nèi)存管理方式戒祠。

  • CF類型內(nèi)存

ARC 可以幫忙管理 Objective-C 對(duì)象, 但是不支持 Core Foundation 對(duì)象的管理骇两,所以轉(zhuǎn)換后要注意一個(gè)問題:誰來釋放使用后的對(duì)象。
注意以creat,copy作為關(guān)鍵字的函數(shù)都是需要釋放內(nèi)存的姜盈,注意配對(duì)使用低千。比如:CGColorCreate<-->CGColorRelease

那Objective-C 和 Core Foundation 對(duì)象相互轉(zhuǎn)換時(shí)就可能出現(xiàn)內(nèi)存泄漏的問題,可參考這篇文章處理馏颂。

  • MRC內(nèi)存使用

    這部分不做詳細(xì)介紹示血,也是注意配對(duì)使用,需要說明的是救拉,如果代碼中有部分文件是MRC的矾芙,在已有文件中加代碼的時(shí)候注意一下,不能都按照ARC的方式處理近上。

  • 循環(huán)引用

    • block引起的循環(huán)引用。
      某個(gè)類將block作為自己的屬性變量拂铡,然后該類在block的方法體里面又使用了該類本身;相互持有壹无,導(dǎo)致都釋放不了。
      下面這樣的方式就可以解決block引起的循環(huán)引用:
      __weaktypeof(self) weakSelf =self;
      block內(nèi)的self感帅,換成weakSelf就行了斗锭。
      block不是self的屬性或者變量時(shí),在block內(nèi)使用self不會(huì)循環(huán)引用;
      像這樣的方法中調(diào)用self失球,不會(huì)引起岖是,但是屬性的形式中調(diào)用self就會(huì)以

        [self.myTest doSomeTest:^(NSInteger cellIndex) {
            self.allInter = cellIndex;
        }];
      
  • 引用大循環(huán)
    ?就像前面說的,引用循環(huán)可能是一個(gè)大循環(huán)实苞。我遇到過一種情況豺撑,就是給UITableViewCell設(shè)置block屬性響應(yīng)事件,在block中強(qiáng)引用了self黔牵,
    導(dǎo)致self->tableView->cell->self形成循環(huán)聪轿。
    有時(shí)候隨著代碼量的增大,邏輯的負(fù)責(zé)猾浦,很容易形成一個(gè)很大的循環(huán)引用陆错,最后造成內(nèi)存泄漏。

  • ** NSTimer的使用**

    NSTimer金赦,NSTimer會(huì)對(duì)它的target持有強(qiáng)引用音瓷,如果NSTimer不釋放掉,就會(huì)一直持有它的target的強(qiáng)引用夹抗,如果這個(gè)NSTimer在被target強(qiáng)引用绳慎,會(huì)一直都釋放不掉,造成內(nèi)存泄露。

    下面的代碼在書寫的時(shí)候Xcode是不會(huì)報(bào)任何錯(cuò)誤和警告的偷线。但是實(shí)際上已經(jīng)形成了循環(huán)引用磨确。造成了內(nèi)存泄漏。

    @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";
     }
    
  • 單例也會(huì)造成內(nèi)存泄漏

    如果一個(gè)單例持有一個(gè)block声邦,block內(nèi)又使用了當(dāng)前這個(gè)ViewController類乏奥,會(huì)引起循環(huán)引用。所以單例持有的代碼塊中要用弱引用亥曹,原因是:單例不會(huì)被釋放掉邓了,它會(huì)一直持有block,導(dǎo)致該block所在的ViewController釋放不掉媳瞪。

  • performSelector的內(nèi)存問題

    • performSelector 的動(dòng)態(tài)綁定

      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];
      

    這段代碼就相當(dāng)于在動(dòng)態(tài)之上再動(dòng)態(tài)綁定骗炉。在 ARC 下編譯這段代碼,編譯器會(huì)發(fā)出警告

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

    正是由于動(dòng)態(tài)蛇受,編譯器不知道即將調(diào)用的 selector 是什么句葵,不了解方法簽名和返回值,甚至是否有返回值都不懂兢仰,所以編譯器無法用 ARC 的內(nèi)存管理規(guī)則來判斷返回值是否應(yīng)該釋放乍丈。因此,ARC 采用了比較謹(jǐn)慎的做法把将,不添加釋放操作轻专,即在方法返回對(duì)象的引用計(jì)數(shù)可能不會(huì)減少,從而可能導(dǎo)致內(nèi)存泄露察蹲。

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

        [object performSelector:selector];
    

    不創(chuàng)建一個(gè)返回值變量測試分析亚兄,簡直難以想象這里居然會(huì)出現(xiàn)內(nèi)存問題镜雨。所以如果你使用的 selector 有返回值,一定要處理掉 手動(dòng)釋放(置為 nil)儿捧。

    • performSelector afterDelay 延時(shí)操作
      關(guān)于內(nèi)存管理的執(zhí)行原理是這樣的執(zhí)行
      [self performSelector:@selector(method1:) withObject:self.tableLayer afterDelay:3];
      的時(shí)候荚坞,系統(tǒng)會(huì)將tableLayer的引用計(jì)數(shù)加1,執(zhí)行完這個(gè)方法時(shí)菲盾,還會(huì)將tableLayer的引用計(jì)數(shù)減1颓影,有時(shí)切換場景時(shí)延時(shí)函數(shù)已經(jīng)被調(diào)用但還沒有執(zhí)行,這時(shí)tableLayer的引用計(jì)數(shù)并沒有減少到0懒鉴,也就導(dǎo)致了切換場景dealloc方法沒有被調(diào)用诡挂,出現(xiàn)了內(nèi)存泄露碎浇。

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

      [NSObject cancelPreviousPerformRequestsWithTarget:self]
      

      當(dāng)然你也可以一個(gè)一個(gè)得這樣用:

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

      加上了這個(gè)以后璃俗,切換場景后會(huì)順利地執(zhí)行了dealloc方法奴璃,至此內(nèi)存泄漏問題解決。

  • 代理未清空引起野指針

    查看iOS的一些API城豁,發(fā)現(xiàn)delegate都是assign的苟穆,這樣就會(huì)引起野指針的問題,可能會(huì)引起一些莫名其妙的crash唱星。那么這是怎么引起的雳旅,當(dāng)一個(gè)對(duì)象被回收時(shí),對(duì)應(yīng)的delegate實(shí)體也就被回收间聊,但是delegate的指針確沒有被nil攒盈,從而就變成了游蕩的野指針了。所以在delloc方法中要將對(duì)應(yīng)的assign代理設(shè)置為nil哎榴,如:

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

那是不是所有的delegate都要這樣做呢型豁?一般自己寫的一些delegate,我們會(huì)用weak尚蝌,而不是assign偷遗,weak的好處是當(dāng)對(duì)應(yīng)的對(duì)象被回收時(shí),指針也會(huì)自動(dòng)被設(shè)置為nil驼壶。

  • 循環(huán)未結(jié)束

    如果某個(gè)ViewController中有無限循環(huán),也會(huì)導(dǎo)致即使ViewController對(duì)應(yīng)的view關(guān)掉了喉酌,ViewController也不能被釋放热凹。
    這種問題常發(fā)生于animation處理。

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

上例中泪电,animation重復(fù)次數(shù)設(shè)成HUGE_VALL般妙,一個(gè)很大的數(shù)值,基本上等于無限循環(huán)了相速。
解決辦法是碟渺,在ViewController關(guān)掉的時(shí)候,停止這個(gè)animation突诬。
-(void)viewWillDisappear:(BOOL)animated {
[self.view.layer removeAllAnimations];
}

  • ** try...catch 的使用**
    但如果 doSomethingMayThrowException 方法拋出了異常苫拍,那么 object 對(duì)象就無法釋放。如果 object 對(duì)象持有了重要且稀缺的資源旺隙,就可能會(huì)造成嚴(yán)重后果绒极。

PS其他需要注意的問題

大次數(shù)循環(huán)內(nèi)存暴漲問題

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

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

該循環(huán)內(nèi)產(chǎn)生大量的臨時(shí)對(duì)象蔬捷,直至循環(huán)結(jié)束才釋放垄提,可能導(dǎo)致內(nèi)存泄漏榔袋,解決方法為在循環(huán)中創(chuàng)建自己的autoReleasePool,及時(shí)釋放占用內(nèi)存大的臨時(shí)變量铡俐,減少內(nèi)存占用峰值凰兑。

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

附、如何檢測App的內(nèi)存泄漏問題

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

    Instruments
  • 簡單暴力的重寫dealloc方法审丘,加入斷點(diǎn)或打印判斷某類是否正常釋放吏够。

    dealloc
  • 使用Xcode8中自帶的有內(nèi)存檢測警告。


  • 通過Facebook出品的FBMemoryProfiler工具類進(jìn)行檢測.

集成后的顯示

</br>

這篇ARC下的內(nèi)存泄漏备恤,洋洋灑灑說了這么多稿饰,算是總結(jié)的比較詳細(xì)和全面的。希望對(duì)大家有價(jià)值露泊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末喉镰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子惭笑,更是在濱河造成了極大的恐慌侣姆,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沉噩,死亡現(xiàn)場離奇詭異捺宗,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)川蒙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門蚜厉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人畜眨,你說我怎么就攤上這事昼牛。” “怎么了康聂?”我有些...
    開封第一講書人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵贰健,是天一觀的道長。 經(jīng)常有香客問我恬汁,道長伶椿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任氓侧,我火速辦了婚禮脊另,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘约巷。我一直安慰自己尝蠕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開白布载庭。 她就那樣靜靜地躺著看彼,像睡著了一般廊佩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上靖榕,一...
    開封第一講書人閱讀 49,079評(píng)論 1 285
  • 那天标锄,我揣著相機(jī)與錄音,去河邊找鬼茁计。 笑死漓藕,一個(gè)胖子當(dāng)著我的面吹牛是偷,可吹牛的內(nèi)容都是我干的拴事。 我是一名探鬼主播溉知,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼娜膘!你這毒婦竟也來了逊脯?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤竣贪,失蹤者是張志新(化名)和其女友劉穎军洼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體演怎,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匕争,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了爷耀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甘桑。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖歹叮,靈堂內(nèi)的尸體忽然破棺而出跑杭,到底是詐尸還是另有隱情,我是刑警寧澤盗胀,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站锄贼,受9級(jí)特大地震影響票灰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜宅荤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一屑迂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧冯键,春花似錦惹盼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蚯舱。三九已至,卻和暖如春掩蛤,著一層夾襖步出監(jiān)牢的瞬間枉昏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來泰國打工揍鸟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留兄裂,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓阳藻,卻偏偏與公主長得像晰奖,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子腥泥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容