NSTimer

NSTimer的屬性

+ @property(readonly, getter=isValid) BOOL valid :

返回Boolean 表示當前的timer是否還有效。

+ @property(copy) NSDate *fireDate :

定時器的觸發時間。如果定時器已經無效,則返回最後一次的啓動時間。也可以通過該屬性來改變定時器的觸發時間。

+ @property(readonly) NSTimeInterval timeInterval :

返回定時器的時間間隔。如果定時器的repeat爲NO,則返回0。

+ @property(readonly, retain) id userInfo :

返回定時器的userInfo 對象,如果定時器已經失效,則無權訪問,所以用之前,先通過valid 來檢測定時器是否有效。

NSTimer的方法

類方法

1、以scheduled(安排)開頭的方法,該類型方法創建的定時器,已經將定時器以默認的運行模式(NSDefaultRunLoopMode)安排到當前到run loop 中。即,表示不需要下面的方法手動將定時器加到run loop中。

- (void)addTimer:(NSTimer *)timer forMode:(NSRunLoopMode)mode;
  • 當創建後,repeat設爲NO,則會在當前的NSDate,延遲interval後,執行一次。但是調用fire方法,則會立馬執行。
  • repeat 設爲YES,則每間隔interval,執行一次。
//interval表示時間間隔,
//repeats 表示是否重複執行,
//block中是定時器的執行代碼。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
//target 表示當前定時器啓動時,接受aSelecttor消息的對象,並且定時器會對target 強引用,
//selector 表示定時器啓動時發送給target的消息,
//userInfo 表示定時器的用戶信息,同樣會強引用,一般爲nil,
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
//invocation 是NSInvocation類型,當定時啓動時,通過該對象進行消息轉發(調用某個對象的消息),同樣也是強引用。
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

2、需要手動加到runloop中,如果不加,不執行;調用fire方法則會執行一次,無論repeat是否爲ture。

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

實例方法

1、創建定時器,需要手動加到run loop中。

//date:定時器啓動的時間。
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(id)ui repeats:(BOOL)rep;

2、

  • (void)fire:啓動定時器的方法。
  • (void)invalidate :銷燬定時器。

總結

  • 有block 的方法都是iOS10++ 纔有,並且不存在循環引用的問題(已驗證)。
  • 以scheduled 開頭的方法,不需要手動將定時器加到runloop中,其他方法都需要。
  • 如果不加到run loop ,無論repeat是否爲true ,都不會執行;調用fire ,只會執行一次,無內存泄漏的問題。

NSTimer的循環引用問題的本質是:

NSTimer在初始化的時候是放在VC方法中的,而VC的self又是作爲NSTimer對象的一個參數存在的,就導致了一個死循環。

解決問題的本質:打破NSTimer對當前View的持有。

解決問題的方法:

1、調用invalidate方法,銷燬定時器。切記:不可在VC的delloc方法中調用,循環引用不會走到delloc。
2、通過代理弱引用:

  • 創建middleWeak類,定義protocol,及弱引用delegate,定義timeAction方法,並在該方法中調用代理的方法;
  • 目標VC中,實現該代理,創建NStimer實例時,將target設爲middleWeak,selector爲timeAction。
  • 過程:定時器到執行middleWeak中的timeAction,然後回調目標VC的代理方法。

3、利用NSProxy消息轉發

  • 創建middleNSProxy,實現methodSignatureForSelector 和 forwardInvocation,並創建weak修飾的id類型的target
  • 創建middleNSProxy實例,並將proxy的target設爲self。
  • 將time的target設爲middleNSproxy。
@interface ceshiProxy : NSProxy

@property (nonatomic, weak) id aTarget;      // 此對象要從外部傳過來

@end

//.m
#import "ceshiProxy.h"

@implementation ceshiProxy

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
    return [self.aTarget methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation {
    [invocation invokeWithTarget:self.aTarget];
}

@end

//使用
ceshiProxy *proxy = [ceshiProxy alloc];
proxy.aTarget = self; 
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:proxy selector:@selector(timerFire) userInfo:nil repeats:YES];

4、利用新的API
在iOS10以後,提供了三種新的API
scheduledTimerWithTimeInterval:repeats:block:
timerWithTimeInterval:repeats:block:
initWithFireDate:interval:repeats:block:

解決列表滑動,Timer不執行的問題:

[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
原因:當滑動ScrollView的時候,NSRunloop的mode並不是NSDefaultRunLoopMode,而是UITrackingRunLoopMode,爲此,我們需要設置一個包含既包含NSDefaultRunLoopMode又包含UITrackingRunLoopMode的mode,那就是NSRunLoopCommonModes。

##一位大神總結的Runloop知識點:https://blog.ibireme.com/2015/05/18/runloop/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章