
引用計(jì)數(shù)
引自維基百科
引用計(jì)數(shù)是計(jì)算機(jī)編程語言中的一種內(nèi)存管理技術(shù),是指將資源(可以是對象、內(nèi)存或磁盤空間等等)的被引用次數(shù)保存起來,當(dāng)被引用次數(shù)變?yōu)榱銜r就將其釋放的過程。使用引用計(jì)數(shù)技術(shù)可以實(shí)現(xiàn)自動資源管理的目的。同時引用計(jì)數(shù)還可以指使用引用計(jì)數(shù)技術(shù)回收未使用資源的垃圾回收算法。
當(dāng)創(chuàng)建一個對象的實(shí)例并在堆上申請內(nèi)存時,對象的引用計(jì)數(shù)就為1,在其他對象中需要持有這個對象時,就需要把該對象的引用計(jì)數(shù)加1,需要釋放一個對象時,就將該對象的引用計(jì)數(shù)減1,直至對象的引用計(jì)數(shù)為0,對象的內(nèi)存會被立刻釋放。
使用這種方式進(jìn)行內(nèi)存管理的語言:Objective-C
iOS是使用引用計(jì)數(shù)管理內(nèi)存,非常需要注意的一個點(diǎn)就是持有關(guān)系。持有關(guān)系就是A_View持有B_View,
[B_View removeFromSuperview]釋放A_View對B_View的持有,B_View才會釋放。
如果B_View沒有調(diào)用[B_View removeFromSuperview],即使B_View=nil,也不會釋放。因?yàn)锳_View依然在持有B_View。

所以在iOS里面想要obj釋放,不要使用obj=nil,如果持有關(guān)系沒解除,釋放不掉的。
內(nèi)存問題-定時器
定時器在持有關(guān)系上比較特殊,生成一個定時器并開啟,RunLoop會持有Timer,Timer會持有Target,Timer不關(guān)閉,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,每次觸發(fā)定時器的判斷B_View是否被釋放了,釋放了就停止定時器。這樣B_View在使用定時器的時候,不需要再操心定時器的釋放了。
weakTarget實(shí)現(xiàn)如下
@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
平時使用定時器還需要注意一點(diǎn),就是在starTimer之前,最好先stopTimer一下。有可能starTimer多次,生成了多個Timer對象,造成一堆的Timer在跑,沒釋放。
內(nèi)存問題-延遲執(zhí)行
dispatch延遲3秒執(zhí)行block,要在block里面使用weak_self,如果3秒內(nèi)weak_self釋放了,weak_self為nil。

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秒內(nèi)weak_self釋放了,后面的代碼就不會執(zhí)行了。
注意,要在block里面進(jìn)行weak_self為nil的判斷,如果不做這個判斷,后面的單例依然會執(zhí)行?。?!
內(nèi)存問題-網(wǎng)絡(luò)請求
網(wǎng)絡(luò)請求的問題和上面延遲執(zhí)行的問題類似,由于網(wǎng)絡(luò)是異步加載的,在網(wǎng)絡(luò)環(huán)境很差的時候,如果頁面退出了,由于網(wǎng)絡(luò)請求的block還在持有self頁面,導(dǎo)致頁面不能釋放,直到網(wǎng)絡(luò)請求返回執(zhí)行完block才釋放self。
注意,如果block一直沒有回調(diào),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的判斷,雖然不做判斷也沒問題,因?yàn)閎lock里面不存在單例、after等場景,最好是養(yǎng)成習(xí)慣,對self或weak_self做一次nil判斷,以后即使增加代碼,也不會有隱藏風(fēng)險(xiǎn)。
內(nèi)存問題-代理
其實(shí)代理就是一個對象持有另外一個對象,然后執(zhí)行另外一個對象的方法。如果A持有B,B的delegate剛好是A,那B的delegate要用weak修飾了。

@property (nonatomic, weak, nullable) id <UICollectionViewDelegate> delegate;
@property (nonatomic, weak, nullable) id <UICollectionViewDataSource> dataSource;
內(nèi)存問題-單例
很簡單的道理,因?yàn)閱卫灤┱麄€APP的生命周期的,單例不會釋放。如果單例持有了一個外部傳過來的view,這個view需要用weak修飾,不然view就一直被單例持有,不會釋放。

@interface GiftComboAnimaOperationManager : NSObject
/// 父視圖weak
@property (nonatomic, weak) UIView *parentView;
/// 單例
+ (instancetype)sharedManager;
@end
內(nèi)存問題-動畫
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設(shè)置為NO,CAAnimation執(zhí)行完動畫并不會主動釋放。
這就需要手動釋放CAAnimation。
[CAAnimation removeAnimationForKey:@"key"];
內(nèi)存問題-present vc

[vc_1 presentViewController:vc_2]
vc_1和vc_2相互持有,在vc_1 pop返回的時候,要先使vc_2 dissmiss,才能釋放vc_1和vc_2
vc_1調(diào)用
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
if (self.presentedViewController) {
[self.presentedViewController dismissViewControllerAnimated:NO completion:nil];
}
}
內(nèi)存問題-KVO
通知
NSNotificationCenter從iOS9之后,會在對象dealloc的時候移除Observer。
[[NSNotificationCenter defaultCenter] addObserver: self selector:@selector(appBecomeActive:)
name: UIApplicationDidBecomeActiveNotification
object: nil];
iOS9之前要手動調(diào)用removeObserver
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
為什么 iOS 9 之前需要手動移除觀察者對象?
觀察者注冊時,通知中心并不會對觀察者對象做 retain 操作,而是對觀察者對象進(jìn)行unsafe_unretained 引用。
什么是unsafe_unretained?因?yàn)?Cocoa 和 Cocoa Touch 中的一些類仍然還沒有支持 weak 引用。所以,當(dāng)我們想對這些類使用弱引用的時候,只能用unsafe_unretained來替代。
// for attribute
@property (unsafe_unretained) NSObject *unsafeProperty;
// for variables
NSObject *__unsafe_unretained unsafeReference;
不安全引用(unsafe reference)和弱引用 (weak reference) 類似,它并不會讓被引用的對象保持存活,但是和弱引用不同的是,當(dāng)被引用的對象釋放的時,不安全引用并不會自動被置為 nil,這就意味著它變成了野指針,而對野指針發(fā)送消息會導(dǎo)致程序崩潰
KVO
[self.marqueeLabel addObserver:self
forKeyPath:@"text"
options:NSKeyValueObservingOptionNew
context:nil
];
如果沒有調(diào)用removeObserver,會發(fā)生崩潰。
An instance 0x11d3c9be0 of class UILabel was deallocated
while key value observers were still registered with it.
Current observation info: <NSKeyValueObservationInfo 0x17182bb00> ( <NSKeyValueObservance 0x171c5bd20: Observer: 0x11d3c7ad0, Key path: text, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x171c42220> <NSKeyValueObservance 0x171e400c0: Observer: 0x11d3c7ad0, Key path: attributedText, Options: <New: YES, Old: NO, Prior: NO> Context: 0x0, Property: 0x171e40030> )
手動removeObserver
- (void)dealloc {
[self.marqueeLabel removeObserver:self forKeyPath:@"text"];
}
內(nèi)存問題-關(guān)聯(lián)對象
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
強(qiáng)引用
無論是BOOL,還是Object都是使用OBJC_ASSOCIATION_RETAIN_NONATOMIC
- (void)setAnimationAllowUserInteraction:(BOOL)animationAllowUserInteraction {
objc_setAssociatedObject(self, @selector(animationAllowUserInteraction), @(animationAllowUserInteraction), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (BOOL)animationAllowUserInteraction {
return [objc_getAssociatedObject(self, _cmd) boolValue];
}
上面代碼等同于
@property (nonatomic, strong) NSNumber *animationAllowUserInteraction;
弱引用
- (void)setDelegate:(id)delegate {
objc_setAssociatedObject(self,
@selector(delegate),
delegate,
OBJC_ASSOCIATION_ASSIGN);
}
- (id)delegate {
return objc_getAssociatedObject(self, @selector(delegate));
}
上面代碼等同于
@property (nonatomic, weak) id delegate;
weak修飾delegate,不會造成循環(huán)引用。
總結(jié)
對比以上場景可以發(fā)現(xiàn),對象沒有釋放的根本原因是被持有了,這也是引用計(jì)數(shù)的原理。在代碼中,建議規(guī)范使用block,養(yǎng)成習(xí)慣在block里面對self判斷nil。如下。
定義weakify、strongify.
/**
Synthsize a weak or strong reference.
Example:
weakify(self)
[self doSomething^{
strongify(self)
if (!self) return;
...
}];
*/
#ifndef weakify
#if __has_feature(objc_arc)
#define weakify(object) __weak __typeof__(object) weak##_##object = object;
#else
#define weakify(object) __block __typeof__(object) block##_##object = object;
#endif
#endif
#ifndef strongify
#if __has_feature(objc_arc)
#define strongify(object) __typeof__(object) object = weak##_##object;
#else
#define strongify(object) __typeof__(object) object = block##_##object;
#endif
#endif
代碼實(shí)現(xiàn)
weakify(self)
^() {//block example
strongify(self)
if (!self) {
return;
}
[self xxxx];
}