iOS中Cell或SubView的timer釋放問(wèn)題

問(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ì)比。


添加回調(diào)函數(shù)前
添加回調(diào)函數(shù)后

總結(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)容。

Demo地址

參考文章
如何釋放含有NSTimer的UITableViewCell(SubView)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容