iOS --NStimer

手動(dòng)目錄

  • NSTimer 打破強(qiáng)持有的方法
    方法一: 在 viewWillDisappear 中釋放NSTimer
    方法二:在didMoveToParentViewController中釋放
    方法三:消息轉(zhuǎn)發(fā)
    方法四:中介者模式
    ___普通方式
    ___runtimer方式
  • 為什么weakSelf不能打破循環(huán)引用

iOS開(kāi)發(fā)中,遇到定時(shí)器的時(shí)候很多犹撒,大多數(shù)人會(huì)選擇用NSTimer颁湖。

NSTimer 有一個(gè)新的API不用考慮循環(huán)引用
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
但是它支持的版本是10.12之后蔗候。

我們用的多的還是這個(gè)API:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
這個(gè)API就會(huì) 有一個(gè)問(wèn)題:循環(huán)引用造成VC釋放不掉健爬。
為什么會(huì)釋放不掉外永? 我們?cè)贏PI中看說(shuō)明:

target
The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to target until it (the timer) is invalidated.

target 是一個(gè)強(qiáng)持有润绎。

NSTimer 打破強(qiáng)持有的方法

方法一: 在 viewWillDisappear 中釋放NSTimer

@property (nonatomic, strong) NSTimer       *timer;

self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(fireHome) userInfo:nil repeats:YES];

- (void)fireHome {
}

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [self.timer invalidate];
    self.timer = nil;
}

這種方法很顯然包警,局限性很大书幕,大多數(shù)情況下,這種當(dāng)時(shí)都是不友好的揽趾,因?yàn)榇蠖鄶?shù)情況下台汇,都會(huì)進(jìn)行push或者present。

方法二:在didMoveToParentViewController中釋放

- (void)didMoveToParentViewController:(UIViewController *)parent{
    // 無(wú)論push 進(jìn)來(lái) 還是 pop 出去 正常跑
    if (parent == nil) {
       [self.timer invalidate];
        self.timer = nil;
        NSLog(@"timer 走了");
    }
}

方法三:消息轉(zhuǎn)發(fā)

這種方式 需要借助虛基類(lèi)來(lái)實(shí)現(xiàn)篱瞎。

思路: 創(chuàng)建一個(gè)虛基類(lèi)苟呐,指定Timer的target為這個(gè)虛基類(lèi)。然后讓虛基類(lèi)把消息在發(fā)送給原來(lái)的target

// 虛基類(lèi)  LGProxy
@interface LGProxy : NSProxy
+ (instancetype)proxyWithTransformObject:(id)object;
@end

@interface LGProxy()
@property (nonatomic, weak) id object;
@end

@implementation LGProxy
+ (instancetype)proxyWithTransformObject:(id)object{
    LGProxy *proxy = [LGProxy alloc];
    proxy.object = object;
    return proxy;
}

-(id)forwardingTargetForSelector:(SEL)aSelector {
    return self.object;
}

// 用這種方式創(chuàng)建NSTimer  --- 指定target 為 LGProxy.object
self.proxy = [LGProxy proxyWithTransformObject:self];
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self.proxy selector:@selector(fireHome) userInfo:nil repeats:YES];

- (void)fireHome {
    NSLog(@"hello word - %d",num);
}

方法四:中介者模式

普通方式

用一個(gè)類(lèi)保存Timer的target俐筋、SEL等信息

// .h
@interface JEWeakTimer : NSObject
+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats;
@end


// .m
@interface JEWeakTimerTarget : NSObject

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

@end

@implementation JEWeakTimerTarget

- (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 JEWeakTimer

+ (NSTimer *) scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                      target:(id)aTarget
                                    selector:(SEL)aSelector
                                    userInfo:(id)userInfo
                                     repeats:(BOOL)repeats {
    JEWeakTimerTarget* timerTarget = [[JEWeakTimerTarget 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;
}
@end
runtime 方式

思路: 通過(guò)runtime 動(dòng)態(tài)添加 動(dòng)態(tài)向中介者中添加一個(gè)方法實(shí)現(xiàn)(SEL(timer的SEL) - > IMP(自定義的IMP )牵素,在自定義的IMP中,通過(guò)runtime 向原來(lái)的類(lèi)中發(fā)送消息

// .h
@interface LGTimerWapper : NSObject

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;

@end

// .m
#import <objc/message.h>

@interface LGTimerWapper()
@property (nonatomic, weak) id target;
@property (nonatomic, assign) SEL aSelector;
@property (nonatomic, strong) NSTimer *timer;

@end

@implementation LGTimerWapper

- (instancetype)lg_initWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo{
    if (self == [super init]) {
        self.target     = aTarget; // vc
        self.aSelector  = aSelector; // 方法 -- vc 釋放
        
        if ([self.target respondsToSelector:self.aSelector]) {
            Method method    = class_getInstanceMethod([self.target class], aSelector);
            const char *type = method_getTypeEncoding(method);
            class_addMethod([self class], aSelector, (IMP)fireHomeWapper, type);
            
            self.timer      = [NSTimer scheduledTimerWithTimeInterval:ti target:self selector:aSelector userInfo:userInfo repeats:yesOrNo];
        }
    }
    return self;
}

void fireHomeWapper(JETimerWapper *warpper){
    
    if (warpper.target) {
        void (*lg_msgSend)(void *,SEL, id) = (void *)objc_msgSend;
         lg_msgSend((__bridge void *)(warpper.target), warpper.aSelector,warpper.timer);
    }
}
@end

為什么weakSelf不能打破循環(huán)引用

上面說(shuō)的各種解決NSTimer不釋放的問(wèn)題澄者,都沒(méi)有提到weakSelf笆呆。為什么block可以解決循環(huán)引用请琳,而NSTimer不可以?
其實(shí)把Block本質(zhì)了解之后赠幕,再來(lái)看這個(gè)問(wèn)題就很好處理了俄精。
iOS-- block中提到一個(gè)地方,block_assign (第二層拷貝) 中是對(duì)對(duì)象的指針進(jìn)行持有榕堰。

void _Block_object_assign(void *destArg, const void *object, const int flags) {
  const void **dest = (const void **)destArg;
  switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {

    case BLOCK_FIELD_IS_OBJECT:
      _Block_retain_object(object);
      *dest = object;                //  ??     持有的是指針地址竖慧,而不是對(duì)象本身
      break;

self和weakSelf雖然都是指向同一個(gè)對(duì)象,但他們是兩個(gè)不同的地址逆屡,weakSelf不強(qiáng)持有對(duì)象圾旨,也就是不操作引用計(jì)數(shù)。
block在copy的時(shí)候魏蔗,會(huì)強(qiáng)持有臨時(shí)變量的指針地址砍的,而不是指針指向的對(duì)象,所以weakSelf可以解決block循環(huán)引用問(wèn)題莺治,而NSTimer強(qiáng)持有的是對(duì)象廓鞠。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市产雹,隨后出現(xiàn)的幾起案子诫惭,更是在濱河造成了極大的恐慌翁锡,老刑警劉巖蔓挖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異馆衔,居然都是意外死亡瘟判,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)角溃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)拷获,“玉大人,你說(shuō)我怎么就攤上這事减细〈夜希” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵未蝌,是天一觀的道長(zhǎng)驮吱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)萧吠,這世上最難降的妖魔是什么左冬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮纸型,結(jié)果婚禮上拇砰,老公的妹妹穿的比我還像新娘梅忌。我一直安慰自己,他們只是感情好除破,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布牧氮。 她就那樣靜靜地躺著,像睡著了一般皂岔。 火紅的嫁衣襯著肌膚如雪蹋笼。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天躁垛,我揣著相機(jī)與錄音剖毯,去河邊找鬼。 笑死教馆,一個(gè)胖子當(dāng)著我的面吹牛逊谋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播土铺,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼胶滋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了悲敷?” 一聲冷哼從身側(cè)響起究恤,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎后德,沒(méi)想到半個(gè)月后部宿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓢湃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年理张,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绵患。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雾叭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出落蝙,到底是詐尸還是另有隱情织狐,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布筏勒,位于F島的核電站移迫,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏奏寨。R本人自食惡果不足惜起意,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望病瞳。 院中可真熱鬧揽咕,春花似錦悲酷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至蛹头,卻和暖如春顿肺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背渣蜗。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工屠尊, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人耕拷。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓讼昆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親骚烧。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浸赫,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348