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/