程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> object-c編程tips-timer

object-c編程tips-timer

編輯:關於C語言

object-c編程tips-timer


object-c定時器

object-c定時器會自動retain當前的使用者,如果不注意調用invalidate,則很容易引起循環引用導致內存洩露。下面的思路提供了一套還算可行的解決方案。


舉例:

經常在viewController中有可能有自動刷新界面的需求。 獲取數據失敗後,每隔10秒自動刷新重新獲取數據,這個時候使用NSTimer是一個很方便的事情。一般情況下直接創建一個NSTimer的repeat對象,然後實現對應的timerFireMethod方法。 當用戶主動點擊返回按鈕時候,此界面應該被釋放,但是由於NSTimer retain了當前的viewController,導致界面內存洩露。 你可能會說在dealloc中調用invalidate,但是必須明白dealloc根本就不會調用,當然viewDidDisappear也一樣不會被調用。


前一段時間看了effective object-c,學習了一種很好的思想,現分享出來。

給NSTimer添加一個類別,使用block的方式傳遞timerFireMethod,代碼如下:

@implementation NSTimer(LPBLocks)

+(NSTimer*) lpScheduleTimerWithTimerInternal:(NSTimeInterval)interval
                                       block:(void(^)())block
                                     repeats:(BOOL)repeats
{
    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(lpTimerBlockInvoke:) userInfo:[block copy] repeats:repeats];
}
+(void)lpTimerBlockInvoke:(NSTimer*)timer
{
    void(^block)() = timer.userInfo;

    if(block){
        block();
    }
}
@end

這個scheduledTimer方法也會retain target,但是由於這是一個類方法,它保留的是類對象,因此也就不會有什麼問題。 它傳入要執行的block, 然後在回調函數中通過userInfo得到block,並執行。


改進:

這個已經是一個很大的改進了,我們可以在代碼中放心的傳入block代碼。不過仔細思考一下,如果在block中引入了viewController的成員,而且timer又作為成員變量存在於viewController中。

例如如下的代碼:

@interface LPNextViewController ()
{
    NSTimer*  refreshTimer;
}

這樣viewController和refreshTimer又陷入了循環引用的邏輯圈裡。當然可以在block中使用weak_self的方式避免循環引用,但是寫起代碼來總是有些不順手,而且還必須要外部使用者顯式的進行。

於是很容易想到,應該封裝到一個專門的LPTimer類中。它負責持有NSTimer,同時NSTimer的block使用LPTimer的weak版本。

@interface LPTimer ()
{
    NSTimer* _pollTimer;

    //timer selector
    __weak id _weak_target;
    SEL _selector;
    id  _userInfo;
}
@end

-(void)scheduleTimerWithTimerInternal:(NSTimeInterval)interval
                                 target:(id)target
                               selector:(SEL)aSelector
                               userInfo:(id)userInfo
                                repeats:(BOOL)repeats
{
    __weak id weak_self = self;

    _weak_target = target;
    _selector = aSelector;
    _userInfo = userInfo;

    //借用第一個版本的block思想
    //使用了第二層間接,調用_weak_target的aSelector方法。
    //這樣可以把stopTimer給封裝進去,外部不需要管理timer的stop。
    _pollTimer = [NSTimer lpScheduleTimerWithTimerInternal:1 block:^{
        [weak_self doTimer];
    } repeats:repeats];
}

上面的代碼LPTimer持有NSTimer對象,而NSTimer執行的block使用的是weak_self。 它在timer觸發的時候調用自身的doTimer方法。在doTimer中負責將方法傳遞給外部的使用者。

-(void)doTimer
{
    if ([_weak_target respondsToSelector:_selector]) {
        [_weak_target performSelector:_selector withObject:self];
    }
    else{
        DLog(@"WARNNING: unknown selector");
    }
}


_weak_target是外部的使用者。 外部的使用者可以將LPTimer看成是一個普通的對象就行,持有它也不會有什麼問題。 LPTimer保留一個弱引用指向外部的使用者。在時間到timer觸發的時候,會先調到NStimer的block中,然後傳遞到LPTimer的doTimer中,然後調用到_weak_target的selector中。


必須注意釋放NStimer對象,在LPTimer釋放的時候調用NSTimer的invalidate方法。

-(void)stopTimer
{
    DLog(@"");
    [_pollTimer invalidate];
}
-(void)dealloc
{
    [self stopTimer];
    DLog(@"");
}


其實,使用者都是使用的LPTimer類,那麼應該讓LPTimer表現的和NSTimer的行為一模一樣, 使用組合方式的適配器模式就可以輕松搞定。

總結:

基本的思想就是NSTimer會retain一個對象,現在讓它retain類對象。 當時候到來進行觸發的時候,由NSTimer類對象觸發到Block中,繼而觸發到外部的LPTimer普通對象中。在普通對象中我們就可以自由的進行處理了。使用weak_target使LPTimer弱引用外部使用者,斷開外部使用者與LPTimer的關聯。 使用weak_self斷開LPTimer與NStimer的循環關聯。 個人認為還算不錯的思想, 有需要的歡迎討論。





  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved