最近在對一個項目的內(nèi)存泄漏進(jìn)行排查,發(fā)現(xiàn)存在很多內(nèi)存泄漏,還有第三方類庫里面存在對象釋放不掉的情況
AFNetWorking
作為一位iOS開發(fā)者,網(wǎng)絡(luò)請求類AFNetWorking是再熟悉不過了,如果你不知道,那你就太奧特了。對于AFNetWorking的使用通常會對用參數(shù)、網(wǎng)址環(huán)境切換、網(wǎng)絡(luò)狀態(tài)監(jiān)測、請求錯誤信息等進(jìn)行封裝。在封裝請求類時需要注意的是需要請求隊列管理者AFHTTPSessionManager聲明為單例創(chuàng)建形式。對于這個問題,AFNetWorking的作者在github上也指出建議使用者在相同配置下保證AFHTTPSessionManager只有一個,進(jìn)行全局管理,因此我們可以通過單例形式進(jìn)行解決。
@implementation MXHTTPRequst
+ (AFHTTPSessionManager *)manager {
static AFHTTPSessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
});
return manager;
}
+ (void)GET:(NSString *)url parameters:(NSDictionary *)getDictionary returnData:(void (^)(NSData * resultData,NSError * error))returnBlock {
//請求隊列管理者 單例創(chuàng)建形式 防止內(nèi)存泄漏
AFHTTPSessionManager *manager = [self manager];
[manager GET:url parameters:getDictionary progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
returnBlock(responseObject,nil);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
returnBlock(nil,error);
}];
}
@end
Block循環(huán)引用
Block循環(huán)引用的問題是老生常談了,至今網(wǎng)上已經(jīng)有很多文章解釋原理及造成循環(huán)引用的原因,在下就不逼逼了??傊涀∫痪湓挕皩ο笾g不要出現(xiàn)封閉的環(huán)”,就能防止Block循環(huán)引用。我就拿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)引用問題
#pragma mark - 構(gòu)造方法
+ (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
MJRefreshHeader *cmp = [[self alloc] init];
cmp.refreshingBlock = refreshingBlock;
return cmp;
}
上面為MJRefreshNormalHeader的構(gòu)造方法,注意看一下這一行代碼“cmp.refreshingBlock = refreshingBlock”,refreshingBlock是屬于MJRefreshHeader的強(qiáng)引用屬性,最后header會成為我們自己tableView的強(qiáng)引用屬性mj_header,也就是說self.tableView強(qiáng)引用header, header強(qiáng)引用refreshingBlock,如果refreshingBlock里面強(qiáng)引用self,就成了循環(huán)引用,如圖:

所以需要破掉這個環(huán)就得使用weakSelf
__weak id weakSelf = self;
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{
__strong typeof(self) strongSelf = weakSelf;
strongSelf.page = 1;
[strongSelf.dataArr removeAllObjects];
[strongSelf loadData];
}];
結(jié)果為
delegate循環(huán)引用
delegate是一個非?;A(chǔ)的問題了,吧修飾符改成weak就??:@property (nonatomic,weak)id delegate;
這里就不再贅述
比較形象的事UITextView和UIViewController

NSTimer循環(huán)引用
對于定時器NSTimer,使用不正確也會造成內(nèi)存泄漏問題,在初始化傳入target參數(shù)時記得用weak,還有當(dāng)不需要定時器時需要執(zhí)行這兩行代碼:
[_timer invalidate];
_timer = nil;
非OC對象內(nèi)存處理
對于iOS開發(fā),ARC模式已發(fā)揚(yáng)光大多年,可能很多人早已忘記當(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)存情況,因為地圖是相當(dāng)耗費(fèi)內(nèi)存的,因此根據(jù)文檔實現(xiàn)地圖相關(guān)功能的同時,還必須注意內(nèi)存的正確釋放,大體需要注意的有需在使用完畢時將地圖、代理賦值為nil,注意地圖中的大頭針的服用,并且在使用完畢時清空大頭針數(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)存暴漲問題
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)生大量的臨時對象,直至循環(huán)結(jié)束才釋放,可能導(dǎo)致內(nèi)存泄漏,解決方法在循環(huán)體內(nèi)加入自己的@autoreleasepool,用來及時釋放大量的臨時變量,減少內(nèi)存占用峰值。
for (int i = 0; i < 100000; i++) {
@autoreleasepool{
NSString *string = @"Abc";
string = [string lowercaseString];
string = [string stringByAppendingString:@"xyz"];
NSLog(@"%@", string);
}
}