Cocoa Touch(六):App運行機制 NSRunLoop, KVC, KVO, Notification, ARC

 

事件循環NSRunLoop程序員

一、run loop概念web

    NSRunLoop類封裝了線程進入事件循環的過程,一個runloop實例就表示了一個線程的事件循環。更具體的說,在iOS開發框架中,線程每次執行完成程序員自定義的代碼以後,都會檢查當前線程對應的run loop中是否還有其餘事件源,若是還有,那麼線程就不會終止。網絡

    處於事件循環的線程接收的事件源有兩種:input source 和 timer source。線程調用便利函數 [NSTimer scheduledTimerWithTimeInterval: target: selector: userInfo: repeats:] 在建立一個NSTimer實例的同時,以默認模式Default mode在當前線程的run loop中註冊了一個timer source,而且把新建立的timer添加到run loop中,做爲事件的觀察者。app

    不過每一個線程在建立定時器的時候不馬上把它添加到run loop中,只須要調用 [NSTimer timerWithTimeInterval: target: selector: userInfo: repeats:],或者能夠調用 [[NSTimer alloc]  initWithFireDate:interval:target:selector:userInfo:repeats: ],兩種方法等效。而後再使用[NSRunloop currentRunLoop]獲取對應的事件循環對象,再調用 [runloop  addTimer: forMode:] 方法,那麼就會註冊一個定時事件,在這個定時器失效以前,當前線程就回去檢查事件源,不會直接終止,而是會處於等待事件發生的狀態。框架

    以主線程爲例,當線程執行完成程序員定義的代碼以後,就會檢查run loop中的事件,而不會終止。不過須要注意,若是主線程請求了同步信號量,也會阻塞,能夠實現同步網絡請求,防止main thread和web thread之外的線程更新界面,致使crash。函數

二、run loop mode類型oop

    在不一樣run loop mode下運行的線程,運行過程有所不一樣,線程只會檢查當前mode下的事件源。例如:編碼

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1
                                              target:self
                                            selector:@selector(printMessage:)
                                            userInfo:nil
                                             repeats:YES];
}

    這個時候若是咱們在界面上滾動一個scrollview,那麼咱們會發如今中止滾動前,控制檯不會有任何輸出,就好像scrollView在滾動的時候將timer暫停了同樣,這其實就是應爲run loop處於不一樣的mode。atom

    添加一個NSTimer到當前的runloop中的同時,還必需要設定事件源的run loop mode,而當scrollView滾動的時候,當前的MainRunLoop對象是處於UITrackingRunLoopMode的模式下,在這個模式下,並不會處理NSDefaultRunLoopMode的消息(由於線程所處的run loop mode和事件源的run loop mode不匹配),要想在scrollView滾動的同時也接受其它runloop的消息,咱們須要改變二者之間的run loop mode.spa

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

 

三、定時器NSTimer

    上文中其實已經講解了定時器的某些用法,也就是可使用NSTimer的實例在一個NSRunLoop實例中註冊一個定時事件源,而且把這個timer實例註冊爲這個事件的觀察者。

    一個定時器和定時事件是綁定的,使用定時器中的fire方法和invalidate方法來控制一個timer的生命週期,無重複的定時器在fire後當即invalidate,對於不斷重複的timer來講,就須要手動invalidate。或者能夠更改一個定時器觸發的日期,若是觸發日期是distant past,那麼就會當即觸發。如

//關閉定時器 
[myTimer setFireDate:[NSDate distantFuture]];
//開啓定時器 
[myTimer setFireDate:[NSDate distantPast]];

    當一個做爲監聽者的NSTimer實例被觸發的時候,線程將會調用這個NSTimer實例的動做回調方法,有一種方法能夠方便的傳遞多個參數,那就是使用調用類NSInvocation類的實例。

#import <Foundation/Foundation.h>
#import "MyClass.h"

int main (int argc, const char * argv[])
{
@autoreleasepool{
    MyClass *myClass = [[MyClass alloc] init];
    NSString *myString = @"My string";
    
    //普通調用
    NSString *normalInvokeString = [myClass appendMyString:myString];
    NSLog(@"The normal invoke string is: %@", normalInvokeString);
    
    //NSInvocation調用
    SEL mySelector = @selector(appendMyString:);
    NSMethodSignature * sig = [[myClass class] instanceMethodSignatureForSelector: mySelector];
    
    NSInvocation * myInvocation = [NSInvocation invocationWithMethodSignature: sig];
    [myInvocation setTarget: myClass];
    [myInvocation setSelector: mySelector];
    
    [myInvocation setArgument: &myString atIndex: 2];
    
    NSString * result = nil;    
    [myInvocation retainArguments];    
    [myInvocation invoke];
    [myInvocation getReturnValue: &result];
    NSLog(@"The NSInvocation invoke string is: %@", result);
    
    return 0;
}
}

     前兩個參數是隱藏參數self和_cmd,對應target和selector,因此自定義參數從索引2開始。

 

四、日期對象 NSDate, NSDateFormatter

    NSDate的實例表示一個日期,線程能夠藉助於NSDateFormatter的實例實現NSDate對象和NSString對象的相互轉換。

// date方法返回的就是當前時間(now)  
 NSDate *date = [NSDate date];  
// now:  11:12:40  
// date: 11:12:50  
 date = [NSDate dateWithTimeIntervalSinceNow:10];//返回當前時間10秒後的時間  
 // 從1970-1-1 00:00:00開始  
 date = [NSDate dateWithTimeIntervalSince1970:10];//返回1970-1-1 00:00:00時間10秒後的時間  
 // 隨機返回一個比較遙遠的將來時間  
 date = [NSDate distantFuture];  
 // 隨機返回一個比較遙遠的過去時間  
 date = [NSDate distantPast];  
// 返回1970-1-1開始走過的毫秒數  
 NSTimeInterval interval = [date timeIntervalSince1970];  
 // 跟其餘時間進行對比  
 NSDate *date2 = [NSDate date];  
 // 返回比較早的那個時間  
 [date earlierDate:date2];  
 // 返回比較晚的那個時間  
 [date laterDate:date2];  

//獲取兩個時間的時間差  
[date1 timeIntervalSinceDate date2]; 

NSDate *date = [NSDate date];  
 // 2015-04-07 11:14:45  
 NSDateFormatter *formatter = [[NSDateFormatter alloc] init];  
 // HH是24進制,hh是12進制  
 formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";  
 // formatter.locale = [[[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"] autorelease];  
 NSString *string = [formatter stringFromDate:date];  
 NSDate *date2 = [formatter dateFromString:@"2016-03-09 13:14:56"];  

 

關於KVC和KVO

一、KVC 鍵值編碼

    在什麼場景下須要KVC?最簡單的一種應用場景,若是一個控件的屬性被聲明爲@property(nonatomic,readonly)只讀,那麼就只能經過KVC去修改這個屬性,好比當咱們須要用自定義tabBar替換UITabBarController中的原始tabBar的時候。

二、KVO 鍵值監聽

    Cocoa開發框架實現了通知機制,程序員無需編寫太多代碼就能夠建立觀察者,能夠實現數據改變後對每一個觀察者的通知,應用場景能夠是數據模型被一個控制器改變後,通知其它控制器。

- (void)setFinished:(BOOL)finished2
{
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished2;
    [self didChangeValueForKey:@"isFinished"];
}

- (void)setExecuting:(BOOL)executing2
{
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing2;
    [self didChangeValueForKey:@"isExecuting"];
}

     要實現KVO,通常的步驟爲:

(1)假設PersonObject但願可以覺察到BankObject對象的accountBalance屬性的任何變化。

(2)那麼 PersonObject必須發送一個「addObserver:forKeyPath:options:context:」消息,註冊成爲 BankObject的accountBalance屬性的觀察者。「addObserver:forKeyPath:options:context:」方法在指定對象實例之間創建了一個鏈接。

(3)爲了可以響應消息,觀察者必須實現 「observeValueForKeyPath:ofObject:change:context:」方法。這個方法實現如何響應變化的消息。在這個方法裏面咱們能夠跟本身的狀況,去實現應對被觀察對象屬性變更的相應邏輯。

(4)若是遵循KVO規則的話,當被觀察的屬性改變時調用willChangeValueForKey和didChangeValueForKey,那麼方法 「observeValueForKeyPath:ofObject:change:context:」會自動被調用。

 

關於ARC

    衆所周知iOS中的採用引用計數來實現垃圾回收,有兩種狀況不是ARC可以解決的:循環強引用和經過C代碼分配堆內存,這就須要程序員的注意。

如今先忙別的,過幾天再寫。此外過幾天再寫與block相關的ARC問題,好比下面

一、循環引用

    最多見的循環引用錯誤出如今block中:當block中捕獲了self指針的時候,只要block存在,那麼self表示的對象就永遠不會被release。

@property(nonatomic, readwrite, copy) completionBlock completionBlock;

__weak typeof(self) weakSelf = self;self.completionBlock = ^ { if (weakSelf.success) { weakSelf.success(weakSelf.responseData); }};

相關文章
相關標籤/搜索