iOS 造成內(nèi)存泄露的原因有哪些【面試】

一冰抢、從AFNet說起

對于iOS開發(fā)者例衍,網(wǎng)絡(luò)請求類AFNetWorking是再熟悉不過了,對于AFNetWorking的使用我們通常會對通用參數(shù)恍箭、網(wǎng)址環(huán)境切換刻恭、網(wǎng)絡(luò)狀態(tài)監(jiān)測、請求錯誤信息等進行封裝。在封裝網(wǎng)絡(luò)請求類時需注意的是需要將請求隊列管理者AFHTTPSessionManager聲明為單例創(chuàng)建形式鳍贾。對于該問題鞍匾,AFNetWorking的作者在gitHub上也指出建議使用者在相同配置下保證AFHTTPSessionManager只有一個,進行全局管理骑科,因此我們可以通過單例形式進行解決橡淑。下方展示部分核心代碼:

+ (AFHTTPSessionManager*)defaultNetManager {
    
    static AFHTTPSessionManager *manager;
    
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        
        manager = [[AFHTTPSessionManager alloc]init];
        
        manager.responseSerializer = [AFHTTPResponseSerializer serializer];
        
    });
    
    return manager;
    
}


+ (void)GET:(NSString*)url parameters:(NSDictionary*)parameter returnData:(void (^)(NSData * resultData,NSError * error))returnBlock{
    
    //請求隊列管理者 單例創(chuàng)建形式 防止內(nèi)存泄漏`
    
    AFHTTPSessionManager * manager = [HttpRequest defaultNetManager];
    
    [manager GET:url parameters:parameter progress:^(NSProgress * _Nonnull downloadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        
        returnBlock(responseObject,nil);
        
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        
        returnBlock(nil,error);
        
    }];
    
}

二、Block循環(huán)引用

Block循環(huán)引用的問題已是老經(jīng)常談了咆爽,至今已有多篇文章詳細(xì)解釋其原理及造成循環(huán)引用的原因等梁棠,不泛畫圖或?qū)嵗信e,這里不一一贅述斗埂£瑁總結(jié)一句話防止Block循環(huán)引用就是要防止對象之間引用的閉環(huán)出現(xiàn)。舉個開發(fā)中的實際例子蜜笤,就拿很多人在用的MJRefresh說起

 self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        
        self.page = 1;
        
        [self.dataArr removeAllObjects];
        
        [self loadData];
        
    }];

若在MJRefresh的執(zhí)行Block中調(diào)用當(dāng)前self或其所屬屬性濒蒋,一定要注意循環(huán)引用問題。我們簡單分析下MJRefresh為什么會造成循環(huán)引用問題:

點擊進入headerWithRefreshingBlock對應(yīng)方法即可

#pragma mark - 構(gòu)造方法

+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock

{
    
    MJRefreshHeader *cmp = [[self alloc] init];
    
    cmp.refreshingBlock = refreshingBlock;
    
    return cmp;
    
}

這里僅有三行代碼把兔,無非就是創(chuàng)建了下拉刷新部分View然后返回沪伙,這里比較重要的是cmp.refreshingBlock = refreshingBlock;這一句,這里的refreshingBlock是屬于MJRefreshHeader的強引用屬性县好,最后header會成為我們自己tableView的強引用屬性mj_header围橡,也就是說self.tableView強引用header, header強引用refreshingBlock,如果refreshingBlock里面強引用self缕贡,就成了循環(huán)引用翁授,所以必須使用weakSelf,破掉這個循環(huán)晾咪。畫圖表示為:

1767950-e4fea03eee29eba3.png

循環(huán)引用示意圖

閉環(huán)為:

self--->self.tableView--->self.tableView.mj_header---
    
    >self.tableView.mj_header.refreshingBlock--->self
    
    解決方案大家應(yīng)該也不陌生
    
    __weak typeof(self) weakself = self;
    
    self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
        
        __strong typeof(self) strongself = weakself;
        
        strongself.page = 1;
        
        [strongself.dataArr removeAllObjects];
        
        [strongself loadData];
        
    }];

【同strongself是為了防止內(nèi)存提前釋放收擦,有興趣的童鞋可深入了解,這里不做過多解釋了谍倦。當(dāng)然也可借助libextobjc庫進行解決塞赂,書寫為@weakify和@strongify會更方便些≈缰】

相應(yīng)的對于自定義View中的一些Block傳值問題同樣需要注意宴猾,與上述類似。

三叼旋、delegate循環(huán)引用問題

delegate循環(huán)引用問題比較基礎(chǔ)仇哆,只需注意將代理屬性修飾為weak即可

@property (nonatomic, weak) id delegate;

下圖比較形象的說明了使用weak修飾就是為了防止ViewController和UITableView相互強引用內(nèi)存無法釋放的問題:

1767950-2d4403294c5b1d4a.jpg

delegate循環(huán)引用

四、NSTimer循環(huán)引用

對于定時器NSTimer夫植,使用不正確也會造成內(nèi)存泄漏問題讹剔。這里簡單舉個例子,我們聲明了一個類TestNSTimer,在其init方法中創(chuàng)建定時器執(zhí)行操作辟拷。

#import "TestNSTimer.h"

@interface TestNSTimer ()

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation TestNSTimer

- (instancetype)init {
    
    if (self = [``super init]) {
        
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeRefresh:) userInfo:nil repeats:YES];
        
    }
    
    return self;
    
}

- (void)timeRefresh:(NSTimer*)timer {
    
    NSLog(@``"TimeRefresh..."``);
    
}

- (void)cleanTimer {
    
    [_timer invalidate];
    
    _timer = nil;
    
}

- (void)dealloc {
    
    [``super dealloc];
    
    NSLog(@``"銷毀"``);
    
    [self cleanTimer];
    
}

@end

在外部調(diào)用時撞羽,將其創(chuàng)建后5秒銷毀。

 TestNSTimer *timer = [[TestNSTimer alloc]init];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        [timer release];
        
    });

最后的執(zhí)行結(jié)果為

1767950-62b8e5e44447f4d7.png

NSTimer打印結(jié)果

可見TestNSTimer對象并沒有正常釋放衫冻,定時器仍然在無限的執(zhí)行下去诀紊。

我們都知道定時器使用完畢時需要將其停止并滯空,但cleanTimer方法到底何時調(diào)用呢隅俘?在當(dāng)前類的dealloc方法中嗎邻奠?并不是,若將cleanTimer方法調(diào)用在dealloc方法中會產(chǎn)生如下問題为居,當(dāng)前類銷毀執(zhí)行dealloc的前提是定時器需要停止并滯空碌宴,而定時器停止并滯空的時機在當(dāng)前類調(diào)用dealloc方法時,這樣就造成了互相等待的場景蒙畴,從而內(nèi)存一直無法釋放贰镣。因此需要注意cleanTimer的調(diào)用時機從而避免內(nèi)存無法釋放,如上的解決方案為將cleanTimer方法外漏膳凝,在外部調(diào)用即可碑隆。

   
    TestNSTimer *timer = [[TestNSTimer alloc]init];
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
        [timer cleanTimer];
        
        [timer release];
        
    });

打印結(jié)果

五、非OC對象內(nèi)存處理

對于iOS開發(fā)蹬音,ARC模式已發(fā)揚光大多年上煤,可能很多人早已忘記當(dāng)年retain、release的年代著淆,但ARC的出現(xiàn)并不是說我們完全可以忽視內(nèi)存泄漏的問題劫狠。對于一些非OC對象,使用完畢后其內(nèi)存仍需要我們手動釋放永部。

舉個例子独泞,比如常用的濾鏡操作調(diào)節(jié)圖片亮度

 CIImage *beginImage = [[CIImage alloc]initWithImage:[UIImage imageNamed:@``"yourname.jpg"``]];
    
    CIFilter *filter = [CIFilter filterWithName:@``"CIColorControls"``];
    
    [filter setValue:beginImage forKey:kCIInputImageKey];
    
    [filter setValue:[NSNumber numberWithFloat:.5] forKey:@``"inputBrightness"``];``//亮度-1~1
    
    CIImage *outputImage = [filter outputImage];
    
    //GPU優(yōu)化
    
    EAGLContext * eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    
    eaglContext.multiThreaded = YES;
    
    CIContext *context = [CIContext contextWithEAGLContext:eaglContext];
    
    [EAGLContext setCurrentContext:eaglContext];
    
    CGImageRef ref = [context createCGImage:outputImage fromRect:outputImage.extent];
    
    UIImage *endImg = [UIImage imageWithCGImage:ref];
    
    _imageView.image = endImg;
    
    CGImageRelease(ref);//非OC對象需要手動內(nèi)存釋放

在如上代碼中的CGImageRef類型變量非OC對象,其需要手動執(zhí)行釋放操作CGImageRelease(ref)扬舒,否則會造成大量的內(nèi)存泄漏導(dǎo)致程序崩潰阐肤。其他的對于CoreFoundation框架下的某些對象或變量需要手動釋放、C語言代碼中的malloc等需要對應(yīng)free等都需要注意讲坎。

五、地圖類處理

若項目中使用地圖相關(guān)類愧薛,一定要檢測內(nèi)存情況晨炕,因為地圖是比較耗費App內(nèi)存的,因此在根據(jù)文檔實現(xiàn)某地圖相關(guān)功能的同時毫炉,我們需要注意內(nèi)存的正確釋放瓮栗,大體需要注意的有需在使用完畢時將地圖、代理等滯空為nil,注意地圖中標(biāo)注(大頭針)的復(fù)用费奸,并且在使用完畢時清空標(biāo)注數(shù)組等弥激。

- (void)clearMapView{
    
    self.mapView = nil;
    
    self.mapView.delegate =nil;
    
    self.mapView.showsUserLocation = NO;
    
    [self.mapView removeAnnotations:self.annotations];
    
    [self.mapView removeOverlays:self.overlays];
    
    [self.mapView setCompassImage:nil];
    
}

六、大次數(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)生大量的臨時對象微服,直至當(dāng)前runloop休眠前才釋放掉,可能導(dǎo)致內(nèi)存泄漏缨历,解決方法為在循環(huán)中創(chuàng)建自己的autoReleasePool以蕴,及時釋放占用內(nèi)存大的臨時變量,減少內(nèi)存占用峰值辛孵。

 for (int i = 0; i < 100000; i++) {
        
        @autoreleasepool {
            
            NSString *string = @"Abc";
            
            string = [string lowercaseString];
            
            string = [string stringByAppendingString:@"xyz"];
            
            NSLog(@"%@", string);
            
        }
        
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末丛肮,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子魄缚,更是在濱河造成了極大的恐慌宝与,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冶匹,死亡現(xiàn)場離奇詭異习劫,居然都是意外死亡,警方通過查閱死者的電腦和手機徙硅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門榜聂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嗓蘑,你說我怎么就攤上這事须肆。” “怎么了桩皿?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵豌汇,是天一觀的道長。 經(jīng)常有香客問我泄隔,道長拒贱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任佛嬉,我火速辦了婚禮逻澳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘暖呕。我一直安慰自己斜做,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布湾揽。 她就那樣靜靜地躺著瓤逼,像睡著了一般笼吟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上霸旗,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天贷帮,我揣著相機與錄音,去河邊找鬼诱告。 笑死撵枢,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔬啡。 我是一名探鬼主播诲侮,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼箱蟆!你這毒婦竟也來了沟绪?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤空猜,失蹤者是張志新(化名)和其女友劉穎绽慈,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體辈毯,經(jīng)...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡坝疼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了谆沃。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钝凶。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖唁影,靈堂內(nèi)的尸體忽然破棺而出耕陷,到底是詐尸還是另有隱情,我是刑警寧澤据沈,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布哟沫,位于F島的核電站,受9級特大地震影響锌介,放射性物質(zhì)發(fā)生泄漏嗜诀。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一孔祸、第九天 我趴在偏房一處隱蔽的房頂上張望隆敢。 院中可真熱鬧,春花似錦崔慧、人聲如沸筑公。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匣屡。三九已至,卻和暖如春拇涤,著一層夾襖步出監(jiān)牢的瞬間捣作,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工鹅士, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留券躁,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓掉盅,卻偏偏與公主長得像也拜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子趾痘,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,446評論 2 348

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