《Objective-C基礎教程》第14章 代碼塊和併發性

第14章 代碼塊和併發性

14.1 代碼塊

代碼塊對象(通常稱爲代碼塊)是對C語言中函數的擴展。除了函數中的代碼,代碼塊還包含變量綁定。代碼塊有時也稱爲閉包(closure)。

代碼塊包含兩種類型的綁定:自動型和託管型。自動綁定(automatic binding)使用的是棧中的內存,而託管綁定(managed binding)是通過堆創建的。

14.1.1 代碼塊和函數指針

代碼塊借鑑了函數指針的語法。與函數指針相似,代碼塊具有以下特徵:

  • 返回類型可以手動聲明,也可以由編譯器推導;
  • 具有指定類型的參數列表
  • 擁有名稱
1. 代碼塊的定義和實現
int (^square_block)( int number ) = ^(int number) {  
     return (number * number);  
};  
int result = square_block(6);  
NSLog(“Result = %d “,result);

說明:

等號前面的內容:int (^square_block)( int number ),是代碼塊的定義。

等號後面的內容:是代碼塊的實現內容。

一般我們可以用如下關係來表示它們:

<returntype> ( ^ blockname) ( list of arguments ) = ^( arguments ) {  body; };

返回類型可以省略。

2. 使用代碼塊

可以像函數一樣使用代碼塊。例如:

int result = square_block(6);

使用代碼塊的時候通常不需要創建一個代碼塊變量,而是在代碼中內聯代碼塊的內容。通常需要將代碼塊作爲參數的方法或函數。

NSArray *array = [NSArray arrayWithObjects:@“a”,@“b”,@“c”,@“d”,nil];  
NSArray *sortedArray = [array sortedArrayUsingComparator:^(NSString *object1, NSString *object2){  
     return [object1 compare: object2];  
}];   
3. 使用typedef關鍵字

像上面那樣那麼長的變量定義語句,在輸入這些代碼的時候很容易引起錯誤。我們可以用typedef關鍵字。

typedef double (^ MyBlockName)(double a, double b);  

這行代碼定義了一個名爲MyBlockName的代碼塊變量類型,它包含兩個雙浮點類型的參數,並且返回一個雙浮點類型的數值。

有了typedef,就可以像下面這樣使用這個代碼塊變量。

MyBlockName *myBlock = ^(double a, double b){  
     return a * b;  
};  
NSLog(@“%f, %f”, myBlock (2, 4 ) ,  myBlock (3, 4) );  
4. 代碼塊和變量

代碼塊被聲明後會捕捉創建點時的狀態。

4.1 本地變量

typedef double (^ MyBlock)(void);  
double a = 10, b = 20;  
MyBlock myBlock = ^(void){  
     return a * b;  
};  
a = 30;  
b = 20;  
NSLog(@“%f”,myBlock()); 

這段代碼最後輸出地的是100,而不是600。因爲變量是本地變量,代碼塊會在定義的時候複製並保存它們的狀態。

全局變量、參數變量與函數中表現相同。

4.2 _block變量

本地變量會被代碼塊作爲常量獲取到。如果你想要修改他們的值,必須將他們聲明爲可修改的,否則像下面這個實例,編譯時會出現錯誤。

double c = 3;  
MyBlock myBlock = ^(double a, double b){  
     c = a * b;  
};  

編譯器會報這個錯誤:

Variable is not assignable (missing __block type specifier)

想要修復這個編譯錯誤,需要將變量c標記爲__block。

__block double c = 3;  
MyBlock myBlock = ^(double a, double b){  
     c = a * b;  
};  

有些變量是無法聲明爲__block類型的。

包括:

1)長度可變的數組

2)包含可變長度數組的結構體

14.2 併發性

GCD技術

蘋果公司爲了減輕在多核上變成的負擔,引入了Grand Central Dispatch,我們稱之爲GCD。

  • GCD技術減少了不少線程管理的麻煩,如果要使用GCD,你需要提交代碼塊或者函數作爲線程來運行。
  • GCD是一個系統級別(system-level)的技術,因此你可以在任意級別的代碼中使用它。
  • GCD決定需要多少線程來安排他們運行的進度。
  • 因爲GCD是運行在系統級別的,所以可以平衡應用程序所有內容的加載,這樣可以提高計算機或設備的執行效率。

14.2.1 同步

Objective-C提供了一個語言級別的(language-level)關鍵字@synchronized。這個關鍵字擁有一個參數,通常這個對象是可以修改的。

@synchronized(theObject)  
{  
          //Critical section  
}

它可以確保不同的線程會連續地訪問臨界區的代碼。

nonatomic屬性

如果你定義了一個屬性,並且沒有指定關鍵字nonatomic作爲屬性的特性,編譯器會生成強制彼此互斥的getter和setter方法,但是這樣設置代碼和變量,會產生一些消耗,比直接訪問慢一些。爲了提高性能,可以添加nonatomic特性。

1. 選擇性能

NSObject提供方法以供一些代碼只在後臺執行。這些方法中都有performSelector:,最簡單的就是performSelectorInBackground:WithObject:,它能在後臺執行一個方法。它通過創建一個線程來運行方法。定義這些方法時必須遵從以下限制:

1)這些方法運行在各自的線程裏,因此你必須爲這些Cocoa對象創建一個自動釋放池,而主自動釋放池是與主線程相關的。

2)這些方法不能有返回值,並且要麼沒有參數,要麼只有一個參數對象。換句話說,你只能使用以下代碼格式中的一種:

-(void)myMethod;

-(void)myMethod:(id)myObject;

-(void)myBackgroundMethod:(id)myObject  
{  
          @autoreleasepool  
          {  
                    NSLog(@“My Background Method %@”,myObject);  
          }  
}

在後臺執行你的方法

[self performSelectorInBackground:@selector(myBackgroundMethod) withObject:argumentObjectl];  

當方法執行結束之後,Objective-C運行時會特地清理並棄掉線程。需要注意:方法執行結束後並不會通知你,這是比較簡單的代碼。如果想要做一些更復雜的事情,需要學習調度隊列。

14.2.2 調度隊列

GCD可以使用調度隊列(dispatch queue),只需寫下你的代碼,把它指派爲一個隊列,系統就會執行它了。可以同步或異步執行任意代碼。

有三種類型的隊列:

1)連續隊列:每個連續隊列都會根據指派的順序執行任務。可以按自己的想法創建任意數量的隊列,他們會並行操作任務。

2)併發隊列:每個併發隊列都能併發執行一個或多個任務。任務會根據指派到隊列的順序開始執行。你無法創建併發隊列,只能從系統提供的三個隊列內選擇一個來使用。

3)主隊列:它是應用程序中有效的主隊列,執行的是應用程序的主線程任務。

連續隊列

當有一連串任務需要按照一定順序執行的時候,可以使用連續隊列。任務執行順序爲先進先出(FIFO):只要任務是異步提交的,隊列會確保任務根據預定順序執行。這些隊列都是不會發生死鎖的。

使用:

dispatch_queue_t my_serial_queue;
my_serial_queue = dispatch_queue_create(“com.appress.MySerialQueue1”,NULL);

第一個參數是隊列的名稱,第二個參數負責提供隊列的特性(現在用不到,所以必須爲NULL)。當隊列創建好以後,就可以給他指派任務。

併發隊列

併發調度隊列適合那些可以並行執行的任務。併發隊列也遵從先進先出(FIFO)的規範,且任務可以在前一個任務結束前就開始執行。每一次運行同一個程序,併發任務的數量可能是不一樣的,因爲它會根據其它運行的任務在不同時間變化。

說明:如果需要確保每次運行的任務數量都是一樣的,可以通過線程API來手動管理線程。

三種併發隊列:

(1)高優先級(high):優先級選項是DISPATCH_QUEUE_PRIORITY_HIGH

(2)默認優先級(default):優先級選項是DISPATCH_QUEUE_PRIORITY_DEFAULT

(3)低優先級(low):優先級選項是DISPATCH_QUEUE_PRIORITY_LOW

如果想要引用他們,可以調用dispatch_get_global_queue方法。

dispatch_queue_t myQueue;  
myQueue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  

說明:第一個參數是優先級選項,對應不同的優先級。第二個參數暫時都用0。因爲它們都是全局的,所以無需爲他們管理內存。不需要保留這些隊列的引用,在需要的時候使用函數來訪問就行了。

主隊列

使用dispatch_get_main_queue可以訪問與應用程序主線程相關的連續隊列。

dispatch_queue_t main_queue = dispatch_get_current_queue(void);

因爲這個隊列與主線程相關,所以必須小心安排這個隊列中的任務順序,否則它們可能會阻塞主應用程序運行。通常要以同步的方式使用這個隊列,提交過個任務並在它們操作完畢後執行一些動作。

獲取當前隊列

可以通過dispatch_get_current_queue()來找出當前運行的隊列代碼塊。如果在代碼塊對象之外調用了這個函數,則它將返回主隊列。

dispatch_queue_t myQueue = dispatch_get_current_queue();
調度程序

(1) 通過代碼塊添加任務

代碼塊必須是dispatch_block_t這樣的類型,要定義爲沒有參數和返回值才行。

typedef void(^dispatch_block_t)(void);

下面的示例添加異步代碼塊。這個函數擁有兩個參數,分別是隊列和代碼塊。

dispatch_async(_serial_queue, ^{
     NSLog(@“Serial Task 1”);
});

如果是同步添加,使用dispatch_sync函數。

(2) 通過函數添加任務

函數的標準原型必須要像下面這樣:

void fucntion_name(void argument)

示例:

void myDispatchFunction(void *argument)
{
     NSLog(@“Serial Task %@”,(__bridge NSNumber *)argument);
     NSMutableDictionary *context = (__bridge NSMutableDictionary *)
        dispatch_get_context(dispatch_get_current_queue());
     NSNumber *value = [context objectForKey:@“value”];
     NSLog(@“value = %@“,value);  
}

向隊列添加這個函數

調用函數擁有三個參數:隊列、需要傳遞的任意上下文以及函數。如果沒有信息要發送給函數,也可以只傳遞一個NULL值。

dispatch_async_f(_serial_queue, (__bridge void *) [NSNumber numberWithInt:3],
    (dispatch_function_t)myDispatchFunction);

如果想以同步的方式添加到隊列中,請調用dispatch_sync_f函數。

暫停隊列

如果出於某個原因要暫停隊列,請調用dispatch_susend()函數並傳遞隊列名稱。

dispatch_suspend(_serial_queue);
重新啓用隊列

隊列暫停之後,可以調用dispatch_resume()函數來重新啓用。

dispatch_resume(_serial_queue);

14.2.3 操作隊列

Objective-C提供一些被稱爲操作(operation)的API,使隊列在Objective-C層級上使用起來更加簡單。

如果想要使用操作,首先需要創建一個操作對象,然後將其指派給操作隊列,並讓隊列執行它。一共有三種創建隊列的方式。

(1)NSInvocationOperation

​ 如果已經有一個可以完成工作的類,並且想要在隊列上執行它,可以嘗試使用這種方法。

(2)NSBlockOperation

​ 類似於包含了需要執行代碼塊的dispatch_async函數。

(3)自定義操作

​ 如果需要更靈活的操作類型,可以創建自己的自定義類型。必須通過NSOperation子類來定義你的操作。

創建調用操作(invocation operation)
@implementation MyCustomClass
-(NSOperation *)operationWithData:(id)data
{
     return [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myWorkerMethod:) object:data];
}

//This is the method that does the actual work
-(void)myWorkerMethod:(id)data
{
     NSLog(@“My Worker Method %@“,data);
}
@end

一旦向隊列中添加了操作,任務即將執行時便會調用類裏面的myWorkerMethod:方法。

創建代碼塊操作 (block operation)

如果你有一個需要執行的代碼塊,那麼可以創建這個操作並讓隊列執行它。

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWihBlock:^{  
     //Do my work  
}];  

一旦創建了第一個代碼塊,你便可以通過addExecutionBlock:方法繼續添加更多的代碼塊。根據隊列的類型(連續的還是併發的),代碼塊會分別以連續或者併發的方式進行。

[blockOperation addExecutionBlock:^{
     //do some more work
}];
向隊列中添加操作

一旦創建了操作,你就需要向隊列中添加代碼塊。NSOperationQueue一般會併發執行。它具有相關性,因此如果某操作是基於其他操作的,它們會相應地執行。

如果要確保你的操作是連續執行的,可以設置最大併發操作數是1,這樣任務就會按照先入先出的規範執行。在向隊列添加操作之前,需要某個方法來引用到那個隊列。可以創建一個新隊列或使用之前已經定義過的隊列(比如當前運行的隊列)。

NSOperationQueue *currentQueue = [NSOperationQueue currentQueue];

或主隊列:

NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; 

以下就是創建隊列的代碼:

NSOperationQueue *_operationQueue = [[NSOperationQueue alloc] init];  

添加操作

[_operationQueue addOperation:blockOperation];  

也可以添加需要執行的代碼塊來替代操作對象

[_operationQueue addOperationWithBlock:^{
 NSLog(“My Block”);
}];

一旦隊列中添加了操作,它就會被安排進度並執行。

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