iOS開發(fā)系列之內(nèi)存泄漏分析(上)

iOS自從引入ARC機(jī)制后暇番,一般的內(nèi)存管理就可以不用我們碼農(nóng)來負(fù)責(zé)了崩哩,但是一些操作如果不注意,還是會引起內(nèi)存泄漏戏自。

本文主要介紹一下內(nèi)存泄漏的原理邦投、常規(guī)的檢測方法以及出現(xiàn)的常用場景和修改方法。

1擅笔、 內(nèi)存泄漏原理

內(nèi)存泄漏的在百度上的解釋就是“程序中已動態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無法釋放尼摹,造成系統(tǒng)內(nèi)存的浪費,導(dǎo)致程序運行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果”剂娄。

在我的理解里就是蠢涝,公司給一個入職的員工分配了一個工位,但是這個員工離職后阅懦,這個工位卻不能分配給下一位入職的員工使用和二,造成了大量的資源浪費。

2耳胎、 常規(guī)的檢測方法

2.1惯吕、Analyze靜態(tài)分析 (command + shift + b)。

2.2怕午、動態(tài)分析方法(Instrument工具庫里的Leaks)废登,product->profile ->leaks 打開可以工具主窗口,具體使用方法可以參考這篇文章郁惜。

3堡距、 內(nèi)存泄漏的場景和分析:

3.1、代理的屬性關(guān)鍵字設(shè)置為strong造成的內(nèi)存泄漏

請看下面這段代碼:

@protocol MFMemoryLeakViewDelegate <NSObject>

@end

@interface MFMemoryLeakView : UIView

@property (nonatomic, strong) id<MFMemoryLeakViewDelegate> delegate;

@end
    MFMemoryLeakView *view = [[MFMemoryLeakView alloc] initWithFrame:self.view.bounds];
    view.delegate = self;
    [self.view addSubview:view];

造成的后果就是控制器得不到釋放兆蕉,原因是控制器對視圖進(jìn)行了強(qiáng)引用羽戒,而控制器又是視圖的代理,視圖對代理進(jìn)行了強(qiáng)引用虎韵,導(dǎo)致了控制器和視圖的循環(huán)引用易稠。
解決方法也很簡單,strong改成weak就行:

@property (nonatomic, weak) id<MFMemoryLeakViewDelegate> delegate;

3.2包蓝、CoreGraphics框架里申請的內(nèi)存忘記釋放

請看下面這段代碼:

- (UIImage *)coreGraphicsMemoryLeak{
    CGRect myImageRect = self.view.bounds;
    CGImageRef imageRef = [UIImage imageNamed:@"MemoryLeakTip.jpeg"].CGImage;
    CGImageRef subImageRef = CGImageCreateWithImageInRect(imageRef, myImageRect);
    UIGraphicsBeginImageContext(myImageRect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawImage(context, myImageRect, subImageRef);
    UIImage *newImage = [UIImage imageWithCGImage:subImageRef];
    CGImageRelease(subImageRef);
//    CGImageRelease(imageRef);
    UIGraphicsEndImageContext();
    return newImage;
}

如果"CGImageRelease(subImageRef)"這行代碼缺失驶社,就會引起內(nèi)存泄漏,使用靜態(tài)分析可以輕易發(fā)現(xiàn)测萎。

需要注意的是:只有當(dāng)CGImageRef使用create或retain后才要手動release亡电,沒有就不需要手動處理了,系統(tǒng)會進(jìn)行自動的釋放绳泉。上面的imageRef對象就是這樣逊抡,如果進(jìn)行了手動release,會引起不確定性的崩潰零酪。

為什么是不確定性的崩潰呢冒嫡,目前我支持的一種說法是:CFRelease的對象不能是NULL,若是NULL的話四苇,會引起runtime的錯誤并且程序要崩潰孝凌,本來imageRef的管理者是會在某個時刻調(diào)用release的,但是因為這里已經(jīng)release過了月腋,已經(jīng)成了NULL蟀架,所以當(dāng)這個調(diào)用時期到來的時候就crash掉了。

關(guān)于這個問題榆骚,大家可以使用我的demo進(jìn)行嘗試片拍,打開后圖中注釋的代碼后運行,先進(jìn)入內(nèi)存泄漏的頁面妓肢,然后返回上級捌省,再進(jìn)入這個頁面,程序崩潰碉钠,demo地址見底部纲缓。

3.3、 CoreFoundation框架里申請的內(nèi)存忘記釋放

請看下面這段代碼:

- (NSString *)coreFoundationMemoryLeak{
    CFUUIDRef uuid_ref = CFUUIDCreate(NULL);
    CFStringRef uuid_string_ref= CFUUIDCreateString(NULL, uuid_ref);
//    NSString *uuid = (__bridge NSString *)uuid_string_ref;
    NSString *uuid = (__bridge_transfer NSString *)uuid_string_ref;
    CFRelease(uuid_ref);
//    CFRelease(uuid_string_ref);
    return uuid;
}

如果"CFRelease(uuid_ref)"這行代碼缺失喊废,就會引起內(nèi)存泄漏祝高,使用靜態(tài)分析可以輕易發(fā)現(xiàn)。

需要注意的是:“ __bridge”是將CoreFoundation框架的對象所有權(quán)交給Foundation框架來使用污筷,但是Foundation框架中的對象并不能管理該對象的內(nèi)存工闺。“ __bridge_transfer”是將CoreFoundation框架的對象所有權(quán)交給Foundation來管理瓣蛀,如果Foundation中對象銷毀斤寂,那么我們之前的對象(CoreFoundation)會一起銷毀。

所以__bridge_transfer這種橋接方式揪惦,以后就不用再自己手動管理內(nèi)存了遍搞。如果上面代碼里的“CFRelease(uuid_string_ref)”的注釋,uuid就會被銷毀器腋,程序運行到reurn 就崩潰溪猿。

3.4、NSTimer 不正確使用造成的內(nèi)存泄漏

3.4.1纫塌、NSTimer重復(fù)設(shè)置為NO的時候诊县,不會引起內(nèi)存泄漏

3.4.2、NSTimer重復(fù)設(shè)置為YES的時候措左,有執(zhí)行invalidate就不會內(nèi)存泄漏依痊,沒有執(zhí)行invalidate就會內(nèi)存泄漏,在 timer的執(zhí)行方法里調(diào)用invalidate也可以。

3.4.3胸嘁、中間target:控制器無法釋放瓶摆,是因為timer對控制器進(jìn)行了強(qiáng)引用,使用類方法創(chuàng)建的timer默認(rèn)加入了runloop性宏,所以群井,timer只要不持有控制器,控制器就能釋放了毫胜。

[NSTimer scheduledTimerWithTimeInterval:1 target:[MFTarget target:self] selector:@selector(timerActionOtherTarget:) userInfo:nil repeats:YES];
#import "MFTarget.h"

@implementation MFTarget

- (instancetype)initWithTarget:(id)target {
    _target = target;
    return self;
}

+ (instancetype)target:(id)target {
    return [[MFTarget alloc] initWithTarget:target];
}

//這里將selector 轉(zhuǎn)發(fā)給_target 去響應(yīng)
- (id)forwardingTargetForSelector:(SEL)selector {
    if ([_target respondsToSelector:selector]) {
        return _target;
    }
    return nil;
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    void *null = NULL;
    [invocation setReturnValue:&null];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
    return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}

這樣控制器的確是釋放了书斜,但是timer的方法還是會在不斷的調(diào)用,如果對性能要求不那么嚴(yán)謹(jǐn)?shù)慕褪梗梢允褂眠@種方法荐吉,具體代碼見demo。

3.4.4口渔、重寫NSTimer:結(jié)合上面中間target的思路样屠,在timer內(nèi)部進(jìn)行invalidate操作,請看一下代碼搓劫。

@interface MFTimer : NSObject

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;

@end
#import "MFTimer.h"

@interface MFTimer ()

@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer *timer;

@end

@implementation MFTimer

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo {
    MFTimer *mfTimer = [[MFTimer alloc] init];
    mfTimer.timer = [NSTimer timerWithTimeInterval:ti target:mfTimer selector:@selector(timerAction:) userInfo:userInfo repeats:yesOrNo];
    mfTimer.target = aTarget;
    mfTimer.selector = aSelector;
    return mfTimer.timer;
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo {
    MFTimer *mfTimer = [[MFTimer alloc] init];
    mfTimer.timer = [NSTimer scheduledTimerWithTimeInterval:ti target:mfTimer selector:@selector(timerAction:) userInfo:userInfo repeats:yesOrNo];
    mfTimer.target = aTarget;
    mfTimer.selector = aSelector;
    return mfTimer.timer;
}

- (void)timerAction:(NSTimer *)timer {
    if (self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        //不判斷是否響應(yīng),是為了不實現(xiàn)定時器的方法就報錯
        [self.target performSelector:self.selector withObject:timer];
#pragma clang diagnostic pop
    }else {
        [self.timer invalidate];
        self.timer = nil;
    }
}

@end

3.4.5瞧哟、使用block創(chuàng)建定時器,需要正確使用block枪向,要執(zhí)行invalidate勤揩,否則也會內(nèi)存泄漏。這里涉及到block的內(nèi)存泄漏問題秘蛔,我會在下篇中一起講解陨亡。

其他內(nèi)存泄漏如通知和KVO、block循環(huán)引用 深员、NSThread造成的內(nèi)存泄漏請見下篇负蠕。

demo地址請點擊這里

歡迎大家來我的小窩做客啊,里面記錄下了我進(jìn)步的點點滴滴倦畅,一切逆境只是前進(jìn)的理由遮糖,與君共勉。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叠赐,一起剝皮案震驚了整個濱河市欲账,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌芭概,老刑警劉巖赛不,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異罢洲,居然都是意外死亡踢故,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來殿较,“玉大人耸峭,你說我怎么就攤上這事⌒敝” “怎么了抓艳?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵触机,是天一觀的道長帚戳。 經(jīng)常有香客問我,道長儡首,這世上最難降的妖魔是什么片任? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮蔬胯,結(jié)果婚禮上对供,老公的妹妹穿的比我還像新娘。我一直安慰自己氛濒,他們只是感情好产场,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舞竿,像睡著了一般京景。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骗奖,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天确徙,我揣著相機(jī)與錄音,去河邊找鬼执桌。 笑死鄙皇,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的仰挣。 我是一名探鬼主播伴逸,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼膘壶!你這毒婦竟也來了错蝴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤香椎,失蹤者是張志新(化名)和其女友劉穎漱竖,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體畜伐,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡馍惹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片万矾。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡悼吱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出良狈,到底是詐尸還是另有隱情后添,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布薪丁,位于F島的核電站遇西,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏严嗜。R本人自食惡果不足惜粱檀,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漫玄。 院中可真熱鬧茄蚯,春花似錦、人聲如沸睦优。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽汗盘。三九已至皱碘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衡未,已是汗流浹背尸执。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留缓醋,地道東北人如失。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像送粱,于是被迫代替她去往敵國和親褪贵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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

  • 一抗俄、NSTimer簡介 二脆丁、NSTimer與RunLoop 三、NSTimer內(nèi)存泄露分析1.NSTimer引用分...
    浮游lb閱讀 5,614評論 2 23
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,089評論 1 32
  • 再一次面試中被問到nstimer的爭取使用方法动雹,原理槽卫,我當(dāng)時就說了[_timer invalidate],time...
    iOS開發(fā)小平哥閱讀 4,049評論 1 13
  • # 前言 反復(fù)地復(fù)習(xí)iOS基礎(chǔ)知識和原理胰蝠,打磨知識體系是非常重要的歼培,本篇就是重新溫習(xí)iOS的內(nèi)存管理震蒋。 內(nèi)存管理是...
    Vein_閱讀 780評論 0 2
  • 今日體驗:晚上快下班的時候來了一輛奧迪A4L做保養(yǎng)。該車這次需要做6萬公里的大保了躲庄,在拉完項目之后和客戶報價經(jīng)過客...
    其實_1d17閱讀 131評論 0 0