一谆趾、從AFNet說起
對(duì)于iOS開發(fā)者躁愿,網(wǎng)絡(luò)請(qǐng)求類AFNetWorking是再熟悉不過了,對(duì)于AFNetWorking的使用我們通常會(huì)對(duì)通用參數(shù)沪蓬、網(wǎng)址環(huán)境切換彤钟、網(wǎng)絡(luò)狀態(tài)監(jiān)測(cè)、請(qǐng)求錯(cuò)誤信息等進(jìn)行封裝跷叉。在封裝網(wǎng)絡(luò)請(qǐng)求類時(shí)需注意的是需要將請(qǐng)求隊(duì)列管理者AFHTTPSessionManager聲明為單例創(chuàng)建形式逸雹。對(duì)于該問題,AFNetWorking的作者在gitHub上也指出建議使用者在相同配置下保證AFHTTPSessionManager只有一個(gè)云挟,進(jìn)行全局管理梆砸,因此我們可以通過單例形式進(jìn)行解決。下方展示部分核心代碼:
+?(AFHTTPSessionManager*)defaultNetManager?{
????static?AFHTTPSessionManager?*manager;
????static?dispatch_once_t?onceToken;
????dispatch_once(&onceToken,?^{
????????manager?=?[[AFHTTPSessionManager?alloc]init];
????????manager.responseSerializer?=?[AFHTTPResponseSerializer?serializer];
????});
????returnmanager;
}
+?(void)GET:(NSString*)url?parameters:(NSDictionary*)parameter?returnData:(void?(^)(NSData?*?resultData,NSError?*?error))returnBlock{
????//請(qǐng)求隊(duì)列管理者?單例創(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)引用就是要防止對(duì)象之間引用的閉環(huán)出現(xiàn)。舉個(gè)開發(fā)中的實(shí)際例子哪轿,就拿很多人在用的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)引用問題。我們簡(jiǎn)單分析下MJRefresh為什么會(huì)造成循環(huán)引用問題:
點(diǎn)擊進(jìn)入headerWithRefreshingBlock對(duì)應(yīng)方法即可
#pragma?mark?-?構(gòu)造方法
+?(instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock
{
????MJRefreshHeader?*cmp?=?[[self?alloc]?init];
????cmp.refreshingBlock?=?refreshingBlock;
????returncmp;
}
這里僅有三行代碼窃诉,無非就是創(chuàng)建了下拉刷新部分View然后返回杨耙,這里比較重要的是cmp.refreshingBlock = refreshingBlock;這一句,這里的refreshingBlock是屬于MJRefreshHeader的強(qiáng)引用屬性褐奴,最后header會(huì)成為我們自己tableView的強(qiáng)引用屬性mj_header按脚,也就是說self.tableView強(qiáng)引用header, header強(qiáng)引用refreshingBlock,如果refreshingBlock里面強(qiáng)引用self敦冬,就成了循環(huán)引用辅搬,所以必須使用weakSelf,破掉這個(gè)循環(huán)脖旱。畫圖表示為:
循環(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庫(kù)進(jìn)行解決溶褪,書寫為@weakify和@strongify會(huì)更方便些〖眨】
相應(yīng)的對(duì)于自定義View中的一些Block傳值問題同樣需要注意猿妈,與上述類似。
三巍虫、delegate循環(huán)引用問題
delegate循環(huán)引用問題比較基礎(chǔ)彭则,只需注意將代理屬性修飾為weak即可
1@property?(nonatomic,?weak)?id?delegate;
下圖比較形象的說明了使用weak修飾就是為了防止ViewController和UITableView相互強(qiáng)引用內(nèi)存無法釋放的問題:
delegate循環(huán)引用
四、NSTimer循環(huán)引用
對(duì)于定時(shí)器NSTimer占遥,使用不正確也會(huì)造成內(nèi)存泄漏問題俯抖。這里簡(jiǎn)單舉個(gè)例子,我們聲明了一個(gè)類TestNSTimer瓦胎,在其init方法中創(chuàng)建定時(shí)器執(zhí)行操作芬萍。
#import?"TestNSTimer.h"
@interface?TestNSTimer?()
@property?(nonatomic,?strong)?NSTimer?*timer;
@end
@implementation?TestNSTimer
-?(instancetype)init?{
????if(self?=?[superinit])?{
????????_timer?=?[NSTimer?scheduledTimerWithTimeInterval:1?target:self?selector:@selector(timeRefresh:)?userInfo:nil?repeats:YES];
????}
????returnself;
}
-?(void)timeRefresh:(NSTimer*)timer?{
????NSLog(@"TimeRefresh...");
}
-?(void)cleanTimer?{
????[_timer?invalidate];
????_timer?=?nil;
}
-?(void)dealloc?{
????[superdealloc];
????NSLog(@"銷毀");
????[self?cleanTimer];
}
@end
在外部調(diào)用時(shí),將其創(chuàng)建后5秒銷毀搔啊。
1
2
3
4
????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é)果為
NSTimer打印結(jié)果
可見TestNSTimer對(duì)象并沒有正常釋放柬祠,定時(shí)器仍然在無限的執(zhí)行下去。
我們都知道定時(shí)器使用完畢時(shí)需要將其停止并滯空负芋,但cleanTimer方法到底何時(shí)調(diào)用呢瓶盛?在當(dāng)前類的dealloc方法中嗎?并不是,若將cleanTimer方法調(diào)用在dealloc方法中會(huì)產(chǎn)生如下問題惩猫,當(dāng)前類銷毀執(zhí)行dealloc的前提是定時(shí)器需要停止并滯空芝硬,而定時(shí)器停止并滯空的時(shí)機(jī)在當(dāng)前類調(diào)用dealloc方法時(shí),這樣就造成了互相等待的場(chǎng)景轧房,從而內(nèi)存一直無法釋放拌阴。因此需要注意cleanTimer的調(diào)用時(shí)機(jī)從而避免內(nèi)存無法釋放,如上的解決方案為將cleanTimer方法外漏奶镶,在外部調(diào)用即可迟赃。
1
2
3
4
5
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對(duì)象內(nèi)存處理
對(duì)于iOS開發(fā)厂镇,ARC模式已發(fā)揚(yáng)光大多年纤壁,可能很多人早已忘記當(dāng)年retain、release的年代捺信,但ARC的出現(xiàn)并不是說我們完全可以忽視內(nèi)存泄漏的問題酌媒。對(duì)于一些非OC對(duì)象,使用完畢后其內(nèi)存仍需要我們手動(dòng)釋放迄靠。
舉個(gè)例子秒咨,比如常用的濾鏡操作調(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對(duì)象需要手動(dòng)內(nèi)存釋放
在如上代碼中的CGImageRef類型變量非OC對(duì)象,其需要手動(dòng)執(zhí)行釋放操作CGImageRelease(ref)掌挚,否則會(huì)造成大量的內(nèi)存泄漏導(dǎo)致程序崩潰雨席。其他的對(duì)于CoreFoundation框架下的某些對(duì)象或變量需要手動(dòng)釋放、C語言代碼中的malloc等需要對(duì)應(yīng)free等都需要注意吠式。
五陡厘、地圖類處理
若項(xiàng)目中使用地圖相關(guān)類,一定要檢測(cè)內(nèi)存情況特占,因?yàn)榈貓D是比較耗費(fèi)App內(nèi)存的糙置,因此在根據(jù)文檔實(shí)現(xiàn)某地圖相關(guān)功能的同時(shí),我們需要注意內(nèi)存的正確釋放摩钙,大體需要注意的有需在使用完畢時(shí)將地圖罢低、代理等滯空為nil查辩,注意地圖中標(biāo)注(大頭針)的復(fù)用胖笛,并且在使用完畢時(shí)清空標(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)生大量的臨時(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);
????????}
????}