1、從AFNetgit
對於iOS開發者,網絡請求類AFNetWorking是再熟悉不過了,對於AFNetWorking的使用咱們一般會對通用參數、網址環境切換、網絡狀態監測、請求錯誤信息等進行封裝。在封裝網絡請求類時需注意的是須要將請求隊列管理者AFHTTPSessionManager聲明爲單例建立形式。對於該問題,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{ //請求隊列管理者 單例建立形式 防止內存泄漏 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); }]; }
2、Block循環引用網絡
Block循環引用的問題已經是老常常談了,至今已有多篇文章詳細解釋其原理及形成循環引用的緣由等,不泛畫圖或實例列舉,這裏不一一贅述。總結一句話防止Block循環引用就是要防止對象之間引用的閉環出現。舉個開發中的實際例子,就拿不少人在用的MJRefresh提及框架
self.tableView.mj_header = [MJRefreshNormalHeader headerWithRefreshingBlock:^{ self.page = 1; [self.dataArr removeAllObjects]; [self loadData]; }];
若在MJRefresh的執行Block中調用當前self或其所屬屬性,必定要注意循環引用問題。咱們簡單分析下MJRefresh爲何會形成循環引用問題:優化
點擊進入headerWithRefreshingBlock對應方法便可atom
#pragma mark - 構造方法 + (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock { MJRefreshHeader *cmp = [[self alloc] init]; cmp.refreshingBlock = refreshingBlock; return cmp; }
這裏僅有三行代碼,無非就是建立了下拉刷新部分View而後返回,這裏比較重要的是cmp.refreshingBlock = refreshingBlock;這一句,這裏的refreshingBlock是屬於MJRefreshHeader的強引用屬性,最後header會成爲咱們本身tableView的強引用屬性mj_header,也就是說self.tableView強引用header, header強引用refreshingBlock,若是refreshingBlock裏面強引用self,就成了循環引用,因此必須使用weakSelf,破掉這個循環。url
__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是爲了防止內存提早釋放,有興趣的童鞋可深刻了解,這裏不作過多解釋了。固然也可藉助libextobjc庫進行解決,書寫爲@weakify和@strongify會更方便些。spa
相應的對於自定義View中的一些Block傳值問題一樣須要注意,與上述相似。代理
3、delegate循環引用問題code
delegate循環引用問題比較基礎,只需注意將代理屬性修飾爲weak便可
@property (nonatomic, weak) id delegate;
4、NSTimer循環引用
對於定時器NSTimer,使用不正確也會形成內存泄漏問題。這裏簡單舉個例子,咱們聲明瞭一個類TestNSTimer,在其init方法中建立定時器執行操做。
#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
在外部調用時,將其建立後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]; });
最後的執行結果爲TestNSTimer對象並無正常釋放,定時器仍然在無限的執行下去。
咱們都知道定時器使用完畢時須要將其中止並滯空,但cleanTimer方法到底什麼時候調用呢?在當前類的dealloc方法中嗎?並非,若將cleanTimer方法調用在dealloc方法中會產生以下問題,當前類銷燬執行dealloc的前提是定時器須要中止並滯空,而定時器中止並滯空的時機在當前類調用dealloc方法時,這樣就形成了互相等待的場景,從而內存一直沒法釋放。所以須要注意cleanTimer的調用時機從而避免內存沒法釋放,如上的解決方案爲將cleanTimer方法外漏,在外部調用便可。
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]; });
5、非OC對象內存處理
對於iOS開發,ARC模式已發揚光大多年,可能不少人早已忘記當年retain、release的年代,但ARC的出現並非說咱們徹底能夠忽視內存泄漏的問題。對於一些非OC對象,使用完畢後其內存仍須要咱們手動釋放。
舉個例子,好比經常使用的濾鏡操做調節圖片亮度
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優化 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對象須要手動內存釋放
在如上代碼中的CGImageRef類型變量非OC對象,其須要手動執行釋放操做CGImageRelease(ref),不然會形成大量的內存泄漏致使程序崩潰。其餘的對於CoreFoundation框架下的某些對象或變量須要手動釋放、C語言代碼中的malloc等須要對應free等都須要注意。
5、地圖類處理
若項目中使用地圖相關類,必定要檢測內存狀況,由於地圖是比較耗費App內存的,所以在根據文檔實現某地圖相關功能的同時,咱們須要注意內存的正確釋放,大致須要注意的有需在使用完畢時將地圖、代理等滯空爲nil,注意地圖中標註(大頭針)的複用,而且在使用完畢時清空標註數組等。
- (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]; }
6、大次數循環內存暴漲問題
for (int i = 0; i < 100000; i++) { NSString *string = @"Abc"; string = [string lowercaseString]; string = [string stringByAppendingString:@"xyz"]; NSLog(@"%@", string); }
該循環內產生大量的臨時對象,直至循環結束才釋放,可能致使內存泄漏,解決方法爲在循環中建立本身的autoReleasePool,及時釋放佔用內存大的臨時變量,減小內存佔用峯值。
for (int i = 0; i < 100000; i++) { @autoreleasepool { NSString *string = @"Abc"; string = [string lowercaseString]; string = [string stringByAppendingString:@"xyz"]; NSLog(@"%@", string); } }