一冰抢、從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)晾咪。畫圖表示為:
循環(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)存無法釋放的問題:
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é)果為
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);
}
}