iOS常見內存問題分析

引用計數

引自維基百科 引用計數是計算機編程語言中的一種內存管理技術,是指將資源(能夠是對象、內存或磁盤空間等等)的被引用次數保存起來,當被引用次數變爲零時就將其釋放的過程。使用引用計數技術能夠實現自動資源管理的目的。同時引用計數還能夠指使用引用計數技術回收未使用資源的垃圾回收算法。 當建立一個對象的實例並在堆上申請內存時,對象的引用計數就爲1,在其餘對象中須要持有這個對象時,就須要把該對象的引用計數加1,須要釋放一個對象時,就將該對象的引用計數減1,直至對象的引用計數爲0,對象的內存會被馬上釋放。 使用這種方式進行內存管理的語言:Objective-C算法

iOS是使用引用計數管理內存,很是須要注意的一個點就是持有關係。持有關係就是A_View持有B_View, [B_View removeFromSuperview]釋放A_View對B_View的持有,B_View纔會釋放。 若是B_View沒有調用[B_View removeFromSuperview],即便B_View=nil,也不會釋放。由於A_View依然在持有B_View。編程

因此在iOS裏面想要obj釋放,不要使用obj=nil,若是持有關係沒解除,釋放不掉的。bash

內存問題-定時器

定時器在持有關係上比較特殊,生成一個定時器並開啓,RunLoop會持有Timer,Timer會持有Target,Timer不關閉,Target和Timer都不會釋放。網絡

釋放方式一【手動中止定時器】

- (void)invalidate;
複製代碼

@interface B_View : UIView

/** timer */
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation B_View

- (void)stopTimer {
    [_timer invalidate];
}
複製代碼

B_View手動中止定時器,這樣Timer釋放了對B_View的持有,B_View就能夠dealloc了。異步

釋放方式二【Timer持有中間對象】

中間對象做爲Timer的Target,每次觸發定時器的判斷B_View是否被釋放了,釋放了就中止定時器。這樣B_View在使用定時器的時候,不須要再操心定時器的釋放了。編程語言

weakTarget實現以下oop

@interface IKWeakTimerTarget : NSObject

@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL selector;
@property (nonatomic, weak) NSTimer* timer;

@end

@implementation IKWeakTimerTarget

- (void)fire:(NSTimer *)timer {
    if(self.target) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
        [self.target performSelector:self.selector withObject:timer.userInfo afterDelay:0.0f];
#pragma clang diagnostic pop
    } else {
        [self.timer invalidate];
    }
}

@end

@implementation IKWeakTimer

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                     target:(id)aTarget
                                   selector:(SEL)aSelector
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats {
    IKWeakTimerTarget *timerTarget = [[IKWeakTimerTarget alloc] init];
    timerTarget.target = aTarget;
    timerTarget.selector = aSelector;
    timerTarget.timer = [NSTimer scheduledTimerWithTimeInterval:interval
                                                         target:timerTarget
                                                       selector:@selector(fire:)
                                                       userInfo:userInfo
                                                        repeats:repeats];
    return timerTarget.timer;
}

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      block:(IKTimerHandler)block
                                   userInfo:(id)userInfo
                                    repeats:(BOOL)repeats {
    NSMutableArray *userInfoArray = [NSMutableArray arrayWithObject:[block copy]];
    if (userInfo != nil) {
        [userInfoArray addObject:userInfo];
    }
    return [self scheduledTimerWithTimeInterval:interval
                                         target:self
                                       selector:@selector(timerBlockInvoke:)
                                       userInfo:[userInfoArray copy]
                                        repeats:repeats];
}

+ (void)timerBlockInvoke:(NSArray*)userInfo {
    IKTimerHandler block = userInfo[0];
    id info = nil;
    if (userInfo.count == 2) {
        info = userInfo[1];
    }
    
    if (block) {
        block(info);
    }
}

@end
複製代碼

平時使用定時器還須要注意一點,就是在starTimer以前,最好先stopTimer一下。有可能starTimer屢次,生成了多個Timer對象,形成一堆的Timer在跑,沒釋放。動畫

內存問題-延遲執行

dispatch延遲3秒執行block,要在block裏面使用weak_self,若是3秒內weak_self釋放了,weak_self爲nil。ui

weakify(self)
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
    if (!weak_self) {
        return;
    }
    [weak_self xxx];
    [weak_self aaaaa:@"3"];
    [[CJK_FloatHandle sharedInstance] callingEnd];
});
複製代碼

3秒內weak_self釋放了,後面的代碼就不會執行了。atom

注意,要在block裏面進行weak_self爲nil的判斷,若是不作這個判斷,後面的單例依然會執行!!!

內存問題-網絡請求

網絡請求的問題和上面延遲執行的問題相似,因爲網絡是異步加載的,在網絡環境不好的時候,若是頁面退出了,因爲網絡請求的block還在持有self頁面,致使頁面不能釋放,直到網絡請求返回執行完block才釋放self。

注意,若是block一直沒有回調,self就一直不釋放!!!

- (void)getBannerData {
    weakify(self)
    [CJActivityBannerService reqBannerList:@{@"type":@(_type)} complete:^(NSArray * _Nonnull arr) {
        strongify(self)
        if (!self) {
            return;
        }
        
        [self _initView];
        [self.bannerView configBannerCellWithModel:arr];
    }];
}
複製代碼

這裏對臨時變量self作了爲nil的判斷,雖然不作判斷也沒問題,由於block裏面不存在單例、after等場景,最好是養成習慣,對self或weak_self作一次nil判斷,之後即便增長代碼,也不會有隱藏風險。

內存問題-代理

其實代理就是一個對象持有另一個對象,而後執行另一個對象的方法。若是A持有B,B的delegate恰好是A,那B的delegate要用weak修飾了。

@property (nonatomic, weak, nullable) id <UICollectionViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <UICollectionViewDataSource> dataSource;
複製代碼

內存問題-單例

很簡單的道理,由於單例貫穿整個APP的生命週期的,單例不會釋放。若是單例持有了一個外部傳過來的view,這個view須要用weak修飾,否則view就一直被單例持有,不會釋放。

@interface GiftComboAnimaOperationManager : NSObject

/// 父視圖weak
@property (nonatomic, weak) UIView *parentView;

/// 單例
+ (instancetype)sharedManager;

@end
複製代碼

內存問題-動畫

CAAnimation的delegate爲strong,若是CAAnimation不釋放,咱們的self也不會釋放。

/* The delegate of the animation. This object is retained for the
 * lifetime of the animation object. Defaults to nil. See below for the
 * supported delegate methods. */

@property(nullable, strong) id <CAAnimationDelegate> delegate;

/* When true, the animation is removed from the render tree once its
 * active duration has passed. Defaults to YES. */

@property(getter=isRemovedOnCompletion) BOOL removedOnCompletion;

複製代碼

若是removedOnCompletion設置爲NO,CAAnimation執行完動畫並不會主動釋放。 這就須要手動釋放CAAnimation。

[CAAnimation removeAnimationForKey:@"key"];
複製代碼

總結

對比以上場景能夠發現,對象沒有釋放的根本緣由是被持有了,這也是引用計數的原理。在代碼中,建議規範使用block,養成習慣在block裏面對self判斷nil。以下。

weakify(self)
^() {//block example
	strongify(self)
	if (!self) {
	    return;
	}

	[self xxxx];
}
複製代碼
相關文章
相關標籤/搜索