日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

NSTimer和實現(xiàn)弱引用的timer的方式

 最初九月雪 2018-03-30

NikonPicTNW.jpg

本文是投稿文章,作者:yohunl 


目錄

  • 我們常用NSTimer的方式

  • 上面的NSTimer無論采用何種方式都是在主線程上跑的那么怎么在非主線程中跑一個NSTimer呢

  • GCD的方式

  • 一次性的timer方式的GCD模式

  • 另一種dispatch_after方式的定時器

  • 利用GCD的弱引用型的timer

  • 使用NSTimer方式創(chuàng)建的Timer使用時候需要注意

  • 參考文檔

  • 我們常用NSTimer的方式

如下代碼所示,是我們最常見的使用timer的方式

1
2
3
4
5
@property (nonatomic , strong) NSTimer *animationTimer;self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:(self.animationDuration = animationDuration)
                                                               target:self
                                                             selector:@selector(animationTimerDidFired:)
                                                             userInfo:nil
                                                              repeats:YES];123456

當(dāng)使用NSTimer的scheduledTimerWithTimeInterval方法時。事實上此時Timer會被加入到當(dāng)前線程的Run Loop中,且模式是默認的NSDefaultRunLoopMode。而如果當(dāng)前線程就是主線程,也就是UI線程時,某些UI事件,比如UIScrollView的拖動操作,會將Run Loop切換成NSEventTrackingRunLoopMode模式,在這個過程中,默認的NSDefaultRunLoopMode模式中注冊的事件是不會被執(zhí)行的。也就是說,此時使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不會執(zhí)行。
我們可以通過添加一個UICollectionView,然后滑動它后打印定時器方法

1
2
3
4
5
6
7
2016-01-27 11:41:59.770 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:00.339 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:01.338 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:02.338 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:03.338 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:15.150 TimerAbout[89719:1419729] enter timer
2016-01-27 11:42:15.338 TimerAbout[89719:1419729] enter timer

從中可以看到,當(dāng)UICollectionView滑動時候,定時器方法并沒有打印(從03.338到15.150)

為了設(shè)置一個不被UI干擾的Timer,我們需要手動創(chuàng)建一個Timer,然后使用NSRunLoop的addTimer:forMode:方法來把Timer按照指定模式加入到Run Loop中。這里使用的模式是:NSRunLoopCommonModes,這個模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結(jié)合,官方參考文檔

還是上面的例子,換為:

1
2
self.animationTimer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(animationTimerDidFired:) userInfo:nil repeats:YES];
    [[NSRunLoop mainRunLoop] addTimer:self.animationTimer forMode:NSRunLoopCommonModes];12

則,無論你滑動不滑動UICollectionView,定時器都是起作用的!

上面的NSTimer無論采用何種方式,都是在主線程上跑的,那么怎么在非主線程中跑一個NSTimer呢?

我們簡單的可以使用如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
//創(chuàng)建并執(zhí)行新的線程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
    [thread start];
     
- (void)newThread
{
    @autoreleasepool
    {        //在當(dāng)前Run Loop中添加timer,模式是默認的NSDefaultRunLoopMode
        [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(animationTimerDidFired:) userInfo:nil repeats:YES];        //開始執(zhí)行新線程的Run Loop
        [[NSRunLoop currentRunLoop] run];
    }
}1234567891011121314

當(dāng)然了,因為是開啟的新的線程,在定時器的回調(diào)方法中,需要切換到主線程才能操作UI。

GCD的方式

1
2
3
4
5
6
7
8
9
10
11
12
//GCD方式
    uint64_t interval = 1 * NSEC_PER_SEC;
    //創(chuàng)建一個專門執(zhí)行timer回調(diào)的GCD隊列
    dispatch_queue_t queue = dispatch_queue_create("timerQueue", 0);
    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //使用dispatch_source_set_timer函數(shù)設(shè)置timer參數(shù)
    dispatch_source_set_timer(_timer, dispatch_time(DISPATCH_TIME_NOW, 0), interval, 0);
    //設(shè)置回調(diào)
    dispatch_source_set_event_handler(_timer, ^(){
        NSLog(@"Timer %@", [NSThread currentThread]);
    });
    dispatch_resume(_timer);//dispatch_source默認是Suspended狀態(tài),通過dispatch_resume函數(shù)開始它123456789101112

其中的dispatch_source_set_timer的最后一個參數(shù),是最后一個參數(shù)(leeway),它告訴系統(tǒng)我們需要計時器觸發(fā)的精準程度。所有的計時器都不會保證100%精準,這個參數(shù)用來告訴系統(tǒng)你希望系統(tǒng)保證精準的努力程度。如果你希望一個計時器每5秒觸發(fā)一次,并且越準越好,那么你傳遞0為參數(shù)。另外,如果是一個周期性任務(wù),比如檢查email,那么你會希望每10分鐘檢查一次,但是不用那么精準。所以你可以傳入60,告訴系統(tǒng)60秒的誤差是可接受的。他的意義在于降低資源消耗。

一次性的timer方式的GCD模式

1
2
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{        NSLog(@"dispatch_after enter timer");
    });123

另一種dispatch_after方式的定時器

這個是使用上面的dispatch_after來創(chuàng)建的,通過遞歸調(diào)用來實現(xiàn)。

1
2
3
4
5
6
7
- (void)dispatechAfterStyle {
    __weak typeof (self) wself = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"dispatch_after enter timer,thread = %@", [NSThread currentThread]);
        [wself dispatechAfterStyle];
    });
}

利用GCD的弱引用型的timer

MSWeaker實現(xiàn)了一個利用GCD的弱引用的timer。原理是利用一個新的對象,在這個對象中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
NSString *privateQueueName = [NSString stringWithFormat:@"com.mindsnacks.msweaktimer.%p", self];
        self.privateSerialQueue = dispatch_queue_create([privateQueueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
        dispatch_set_target_queue(self.privateSerialQueue, dispatchQueue);
        self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
                                            0,
                                            0,
                                            self.privateSerialQueue);
                                             
- (void)resetTimerProperties
{
    int64_t intervalInNanoseconds = (int64_t)(self.timeInterval * NSEC_PER_SEC);
    int64_t toleranceInNanoseconds = (int64_t)(self.tolerance * NSEC_PER_SEC);
    dispatch_source_set_timer(self.timer,
                              dispatch_time(DISPATCH_TIME_NOW, intervalInNanoseconds),
                              (uint64_t)intervalInNanoseconds,
                              toleranceInNanoseconds
                              );
}
- (void)schedule
{
    [self resetTimerProperties];
     
    __weak MSWeakTimer *weakSelf = self;
     
    dispatch_source_set_event_handler(self.timer, ^{
        [weakSelf timerFired];
    });
     
    dispatch_resume(self.timer);
}

創(chuàng)建了一個隊列self.timer = dispatch_source_create,然后在這個隊列中創(chuàng)建timer dispatch_source_set_timer.

注意其中用到了dispatch_set_target_queue(self.privateSerialQueue, dispatchQueue); 這個是將dispatch隊列的執(zhí)行操作放到隊列dispatchQueue 中去。

這份代碼中還用到了原子操作!值得好好研讀,以便以后可以在自己的多線程設(shè)計中使用原子操作。
為什么用原子操作呢,因為作者想的是在多線程的環(huán)境下設(shè)置定時器的開關(guān)與否。

1
2
3
4
5
6
7
8
9
10
if (OSAtomicAnd32OrigBarrier(1, &_timerFlags.timerIsInvalidated))
if (!OSAtomicTestAndSet(7, &_timerFlags.timerIsInvalidated))
    {
        dispatch_source_t timer = self.timer;
        dispatch_async(self.privateSerialQueue, ^{
            dispatch_source_cancel(timer);
            ms_release_gcd_object(timer);
        });
    }

至于其中

1
2
3
4
 struct
    {
        uint32_t timerIsInvalidated;
    } _timerFlags;

這里為什么要用結(jié)構(gòu)體呢?為什么不直接使用一個uint32_t 的變量?

使用NSTimer方式創(chuàng)建的Timer,使用時候需要注意。

由于

1
2
3
4
5
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                                           target:self
                                                         selector:@selector(animationTimerDidFired:)
                                                         userInfo:nil
                                                          repeats:YES];

會導(dǎo)致timer 強引用 self,而animationTimer又是self的一個強引用,這造成了強引用的循環(huán)了。 
如果不手工停止timer,那么self這個VC將不能夠被釋放,尤其是當(dāng)我們這個VC是push進來的時候,pop將不會被釋放!!! 
怎么解決呢?
當(dāng)然了,可以采用上文提到的MSWeakerGCD的弱引用的timer

可是如果有時候,我們不想使用它,覺得它有點復(fù)雜呢?

1.在VC的disappear方法中應(yīng)該調(diào)用 invalidate方法,將定時器釋放掉,這里可能有人要說了,我直接在vc的dealloc中釋放不行么?

1
2
3
-(void)dealloc {
[_animationTimer invalidate];
}

很遺憾的告訴你,都已經(jīng)循環(huán)引用了,vc壓根就釋放不了,怎么調(diào)dealloc方法?

在vc的disappear方法中

1
2
3
4
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[_animationTimer invalidate];
}

這樣的確能解決問題,可是不一定是我們想要的呀,當(dāng)我們vc 再push了一個新的頁面的時候,本身vc沒有釋放,按理說,其成員timer不應(yīng)該被釋放呀,你可能會說,那還不容易,在appear方法中再重新生成一下唄…但是這樣的話,又要增加一個變量,標(biāo)識定時器在上一次disappear時候是不是啟動了吧,是啟動了,被invaliate的時候,才能在appear中重新啟動吧。這樣是不是覺得很麻煩?

3.你可能會說,那簡單啊,直接若引用就可以了想想我們使用block的時候

1
2
3
4
5
@property (nonatomic, copy) void  (^ myblock)(NSInteger i);
__weak typeof (self) weakSelf = self;
self.myblock = ^(NSInteger i){
    [weakSelf view];
};

在其中,我們需要在block中引用self,如果直接引用,也是循環(huán)引用了,采用先定義一個weak變量,然后在block中引用weak對象,避免循環(huán)引用 你會直接想到如下的方式

1
2
3
4
5
6
__weak typeof (self) wself = self;
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                                       target:wself
                                                     selector:@selector(animationTimerDidFired:)
                                                     userInfo:nil
                                                      repeats:YES];

是不是瞬間覺得完美了,呵呵,我只能說少年,你沒理解兩者之間的區(qū)別。在block中,block是對變量進行捕獲,意思是對使用到的變量進行拷貝操作,注意是拷貝的不是對象,而是變量自身。拿上面的來說,block中只是對變量wself拷貝了一份,也就是說,block中也定義了一個weak對象,相當(dāng)于,在block的內(nèi)存區(qū)域中,定義了一個__weak blockWeak對象,然后執(zhí)行了blockWeak = wself;注意到了沒,這里并沒有引起對象的持有量的變化,所以沒有問題,再看timer的方式,雖然你是將wself傳入了timer的構(gòu)造方法中,我們可以查看NSTimer的

1
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats1

定義,其target的說明The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated,是要強應(yīng)用這個變量的 也就是說,大概是這樣的,__strong strongSelf = wself 強引用了一個弱應(yīng)用的變量,結(jié)果還是強引用,也就是說strongSelf持有了wself所指向的對象(也即是self所只有的對象),這和你直接傳self進來是一樣的效果,并不能達到解除強引用的作用!看來只能換個思路了,我直接生成一個臨時對象,讓Timer強用用這個臨時對象,在這個臨時對象中弱引用self,可以了吧。

4.考慮引入一個對象,在這個對象中弱引用self,然后將這個對象傳遞給timer的構(gòu)建方法 這里可以參考YYWeakProxy建立這個對象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
@interface YYWeakProxy : NSProxy
@property (nonatomic, weak, readonly) id target;
- (instancetype)initWithTarget:(id)target;
+ (instancetype)proxyWithTarget:(id)target;
@end
@implementation YYWeakProxy
- (instancetype)initWithTarget:(id)target {
_target = target;
return self;
}
+ (instancetype)proxyWithTarget:(id)target {
return [[YYWeakProxy alloc] initWithTarget:target];
}
//當(dāng)不能識別方法時候,就會調(diào)用這個方法,在這個方法中,我們可以將不能識別的傳遞給其它對象處理
//由于這里對所有的不能處理的都傳遞給_target了,所以methodSignatureForSelector和forwardInvocation不可能被執(zhí)行的,所以不用再重載了吧
//其實還是需要重載methodSignatureForSelector和forwardInvocation的,為什么呢?因為_target是弱引用的,所以當(dāng)_target可能釋放了,當(dāng)它被釋放了的情況下,那么forwardingTargetForSelector就是返回nil了.然后methodSignatureForSelector和forwardInvocation沒實現(xiàn)的話,就直接crash了!!!
//這也是為什么這兩個方法中隨便寫的!!!
- (id)forwardingTargetForSelector:(SEL)selector {
return _target;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
void *null = NULL;
[invocation setReturnValue:&null];
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSObject instanceMethodSignatureForSelector:@selector(init)];
}
- (BOOL)respondsToSelector:(SEL)aSelector {
return [_target respondsToSelector:aSelector];
}
- (BOOL)isEqual:(id)object {
return [_target isEqual:object];
}
- (NSUInteger)hash {
return [_target hash];
}
- (Class)superclass {
return [_target superclass];
}
- (Class)class {
return [_target class];
}
- (BOOL)isKindOfClass:(Class)aClass {
return [_target isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
return [_target isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [_target conformsToProtocol:aProtocol];
}
- (BOOL)isProxy {
return YES;
}
- (NSString *)description {
return [_target description];
}
- (NSString *)debugDescription {
return [_target debugDescription];
}
@end

使用的時候,將原來的替換為:

1
2
3
4
5
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:1
                                                       target:[YYWeakProxy proxyWithTarget:self ]
                                                     selector:@selector(animationTimerDidFired:)
                                                     userInfo:nil
                                                      repeats:YES];

5.block方式來解決循環(huán)引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@interface NSTimer (XXBlocksSupport)
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                     block:(void(^)())block
                                   repeats:(BOOL)repeats;
@end
@implementation NSTimer (XXBlocksSupport)
+ (NSTimer *)xx_scheduledTimerWithTimeInterval:(NSTimeInterval)interval
                                     block:(void(^)())block
                                   repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
                                      target:self
                                    selector:@selector(xx_blockInvoke:)
                                    userInfo:[block copy]
                                     repeats:repeats];
}
+ (void)xx_blockInvoke:(NSTimer *)timer {
void (^block)() = timer.userinfo;
if(block) {
    block();
}
}
@end

注意:以上NSTimer的target是NSTimer類對象,類對象本身是個單利,此處雖然也是循環(huán)引用,但是由于類對象不需要回收,所以沒有問題。但是這種方式要注意block的間接循環(huán)引用,當(dāng)然了,解決block的間接循環(huán)引用很簡單,定義一個weak變量,在block中使用weak變量即可。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多