問(wèn)題
在很多場(chǎng)景下可能需要在cell或view中添加計(jì)時(shí)器進(jìn)行倒計(jì)時(shí)的處理,但是這里會(huì)有一個(gè)問(wèn)題就是timer的釋放問(wèn)題。如果沒(méi)有在controller釋放時(shí)進(jìn)行釋放,timer會(huì)一直被強(qiáng)引用造成內(nèi)存泄露。
在網(wǎng)上看到了幾種解決方案,但是都不是很理想,最終在這篇文章中找到了覺(jué)得是最好的方法。但是在這個(gè)文章里用到了RAC的開源庫(kù),對(duì)于大部分人來(lái)說(shuō)可能并不會(huì)引用這個(gè)庫(kù)。在這里會(huì)通過(guò)類別的方式來(lái)對(duì)最后一種方案來(lái)進(jìn)行改造,使得可以在任何地方使用。
方案分析
在這里簡(jiǎn)單說(shuō)明下解決方案。由于在cell中使用了NSTimer作為屬性,在這種情況下cell不會(huì)自動(dòng)調(diào)用-dealloc方法來(lái)釋放內(nèi)存。這樣的話我們只能通過(guò)在controller釋放內(nèi)存時(shí)來(lái)釋放timer來(lái)達(dá)到目的。在這里我們會(huì)添加兩個(gè)類別,一個(gè)是UIView+ManagerCaller.h類別來(lái)獲取持有view的controller實(shí)例,一個(gè)是NSObject+DeallocBlock.h類別來(lái)添加調(diào)用-dealloc函數(shù)時(shí)的回調(diào)函數(shù)。
貼下代碼
#import "UIView+ManagerCaller.h"
@implementation UIView (ManagerCaller)
- (UIViewController*)getManagerController
{
UIResponder* responder = self;
while (responder && ![responder isKindOfClass:[UIViewController class]]) {
responder = responder.nextResponder;
}
if (!responder) {
return nil;
}
if ([responder isKindOfClass:[UIViewController class]]) {
return (UIViewController*)responder;
}
return nil;
}
@end
#import "NSObject+DeallocBlock.h"
#import <objc/runtime.h>
@interface FDOnDeallocBlockHolder : NSObject
@property (copy, nonatomic) FDObjectDeallocBlock block;
@property (assign, nonatomic) void* key;
@end
@implementation FDOnDeallocBlockHolder
- (instancetype)initWithBlock: (FDObjectDeallocBlock)block
{
self = [super init];
self.block = block;
return self;
}
- (void)dealloc
{
FDObjectDeallocBlock block = _block;
_block = nil;
if (block) {
block();
}
}
@end
@implementation NSObject (DeallocBlock)
- (void*)addOnDeallocBlock: (FDObjectDeallocBlock)block forKey: (void*)key
{
FDOnDeallocBlockHolder* holder = [[FDOnDeallocBlockHolder alloc] initWithBlock:block];
if (!key) {
key = ((__bridge void*)holder);
} else {
holder.key = key;
}
objc_setAssociatedObject(self, key, holder, OBJC_ASSOCIATION_RETAIN);
[self cacheOnDeallocBlockHolder:holder];
return key;
}
- (void*)addOnDeallocBlock: (FDObjectDeallocBlock)block
{
return [self addOnDeallocBlock:block forKey:nil];
}
- (BOOL)hasOnDeallocBlockWithKey: (void*)key
{
return objc_getAssociatedObject(self, key) != nil;
}
- (void)removeOnDeallocBlockByKey: (void*)key
{
NSMutableArray* list = [self onDeallocBlockHolderList];
for (NSValue* holderReference in list) {
FDOnDeallocBlockHolder* holder = holderReference.nonretainedObjectValue;
if (holder && (holder == key || holder.key == key)) {
holder.block = nil;
[list removeObject:holderReference];
break;
}
}
objc_setAssociatedObject(self, key, nil, OBJC_ASSOCIATION_RETAIN);
}
- (void)cacheOnDeallocBlockHolder: (FDOnDeallocBlockHolder*)holder
{
NSMutableArray* list = [self onDeallocBlockHolderList];
[list addObject:[NSValue valueWithNonretainedObject:holder]];
}
- (NSMutableArray<NSValue*>*)onDeallocBlockHolderList
{
NSMutableArray* list = objc_getAssociatedObject(self, @selector(onDeallocBlockHolderList));
if (!list) {
list = [[NSMutableArray alloc] init];
objc_setAssociatedObject(self, @selector(onDeallocBlockHolderList), list, OBJC_ASSOCIATION_RETAIN);
}
return list;
}
- (void)executeAllOnDeallocBlocks
{
NSMutableArray* list = [self onDeallocBlockHolderList];
for (NSValue* holderReference in list) {
FDOnDeallocBlockHolder* holder = holderReference.nonretainedObjectValue;
if (!holder) {
continue;
}
FDObjectDeallocBlock block = holder.block;
holder.block = nil;
if (!block) {
continue;
}
void* key = ((__bridge void*)holder);
objc_setAssociatedObject(self, key, nil, OBJC_ASSOCIATION_RETAIN);
block();
}
[list removeAllObjects];
}
@end
代碼分析
UIView+ManagerCaller.h
這個(gè)類別的邏輯就是通過(guò)遍歷View的所有Responder來(lái)找到持有View的controller。NSObject+DeallocBlock.h
在OC中釋放內(nèi)存邏輯是先釋放實(shí)例自身再釋放實(shí)例成員變量。在這里我們通過(guò)這個(gè)特性來(lái)給實(shí)例添加一個(gè)新成員變量來(lái)實(shí)現(xiàn)回調(diào),把回調(diào)函數(shù)賦值給新成員變量,在實(shí)例自身釋放后新成員變量釋放時(shí)調(diào)用-dealloc方法時(shí)執(zhí)行回調(diào)來(lái)達(dá)到目的。添加回調(diào)時(shí)機(jī)
在這里會(huì)有一個(gè)添加回調(diào)時(shí)機(jī)的問(wèn)題,因?yàn)橛捎诩虞dcell方式不同會(huì)略有不同。如果通過(guò)代碼加載cell的話,在執(zhí)行-didMoveToSuperview函數(shù)時(shí)就可以獲取到controller實(shí)例。但是如果通過(guò)XIB加載cell的情況下,在調(diào)用這個(gè)函數(shù)時(shí)是獲取不到controller實(shí)例的。為了保險(xiǎn)起見,可以統(tǒng)一放到-didMoveToWindow函數(shù)里來(lái)添加回調(diào)函數(shù)。這里也要注意需要做好判斷邏輯不要重復(fù)添加回調(diào)函數(shù),因?yàn)?code>-didMoveToWindow函數(shù)可能會(huì)在不同情況下觸發(fā)多次。
效果演示
下面貼兩張圖,分別是添加回調(diào)函數(shù)前后Log對(duì)比。


總結(jié)
在上面這個(gè)問(wèn)題里,主要解決思路就是怎么讓cell或者subview在controller觸發(fā)-dealloc后執(zhí)行回調(diào)函數(shù),這里涉及到了內(nèi)存釋放機(jī)制,我在這里也只是簡(jiǎn)單應(yīng)用了而已,可能還有一些情況沒(méi)有考慮到會(huì)有其他問(wèn)題。后續(xù)還是要再研究下內(nèi)存釋放機(jī)制的更深層的內(nèi)容。