IOS block 教程

本章學習目標: 1. 瞭解何謂block。  2. 瞭解block的使用方法。 注:變數=變量 app

   Block 是iOS在4.0以後新增的程式語法,嚴格來講block的概念並不算是基礎程式設計的範圍,對初學者來講也不是很容易瞭解,可是在iOS SDK 4.0以後,block幾乎出如今全部新版的API之中,換句話說,若是不瞭解block這個概念就沒法使用SDK 4.0版本之後的新功能,所以雖然block自己的語法有點難度,但爲了使用iOS的新功能咱們仍是得硬着頭皮去了解這個新的程式概念。 dom

    1.初探Block async

    在這一小節咱們先用一些簡單範例來導入block的概念。 函數

           1.1 宣告和使用Block: 學習

            咱們使用「^」運算子來宣告一個block變數並且在block的定義最後面要加上「;」來表示一個完整的述句(也就是將整個block定義視爲前面章節所介紹的簡單述句,由於整個定義必須是一個完整的句子,因此必須在最後面加上分號),下面是一個block的範例: spa

int multiplier = 7;
int (^myBlock)(int) = ^(int num)
{
   return num * multiplier;
};

        咱們宣告一個「myBlock」變數,用「^」符號來表示這是一個block。這是block的完整定義,這個定義將會指定給「myBlock」變數。表示「myBlock」是一個回傳值爲整數(int)的block。它有一個參數,型態也是整數。這個參數的名字叫作「num」。這是block的內容。 設計

        值得注意的地方是block可使用和自己定義範圍相同的變數,能夠想像在上面的例子中multiplier 和myBlock 都是某一個函數內定義的兩個變數也就是這個變數都在某個函數兩個大括號「{」和「 }」中間的區塊,由於它們的有效範圍是相同的,所以在block中就能夠直接使用 multiplier 這個變數,此外當把block定義成一個變數的時,咱們能夠直接像使用通常函數般的方式使用它: code

int multiplier = 7 ;
int (^myBlock)( int ) = ^( int num)
{
   return num * multiplier;
};
printf ( "%d" , myBlock( 3 ));
//結果會打印出21
         1.2   直接使用Block


        在不少狀況下,咱們並不須要將block宣告成變數,反之咱們能夠直接在須要使用block的地方直接用內嵌的方式將block的內容寫出來,在下面的例子中qsort_b函數,這是一個相似傳統的qsort_t函數,可是直接使用block作爲它的參數: orm

char *myCharacters[3] = { "TomJohn" , "George" , "Charles Condomine"};
qsort_b (myCharacters, 3,
         sizeof ( char *),
         ^( const void *l, const void *r)//block部分
           {
               char *left = *( char **)l;
               char *right = *( char **)r;
               return strncmp (left, right, 1 );
           }
);
          1.3  __block 變量


        通常來講,在block內只能讀取在同一個做用域的變數並且沒有辦法修改在block外定義的任何變數,此時若咱們想要這些變數可以在block中被修改,就必須在前面掛上__block的修飾詞,以上面第一個例子中的 multiplier 來講,這個變數在 block 中是惟讀的,因此 multiplier = 7 指定完後,在 block 中的 multiplier 就只能是 7 不能修改,若咱們在 block 中修改 multiplier,在編輯時就會產生錯誤,所以若想要在 block 中修改 multiplier ,就必須在 multiplier 前面加上 __block 的修飾詞,請參考下面的範例: 索引

__block int multiplier = 7;
int (^myBlock)(int) = ^(int num)
{
   if (num > 5 )
   {
       multiplier = 7 ;
   }
   else
   {
       multiplier = 10 ;
   } 
   return num * multiplier;                 
};
    2. Block概要


        Block 提供咱們一種可以將函數程式碼內嵌在通常述句中的方法,在其餘語言中也有相似的概念稱作「closure」,可是爲了配合Objective-C的貫例,咱們一概將這種用法稱爲「block」

             2.1 Block 的功能

                Block 是一種具備匿名功能的內嵌函數,它的特性以下: 如通常的函數般能擁有帶有型態的參數。擁有回傳值。能夠擷取被定義的詞法做用域(lexical scope)狀態。能夠選擇性地修改詞法做用域的狀態。

                注:詞法做用域(lexical scope)能夠想像成是某個函數兩個大括號中間的區塊,這個區塊在程式執行時,系統會將這個區塊放入堆疊記憶體中,在這個區塊中的宣告的變數就像是咱們常聽到的區域變數,當咱們說block能夠擷取同一詞法做用域的狀態時能夠想像block變數和其餘區域變數是同一個層級的區域變數(位於同一層的堆疊裏),而block的內容能夠讀取到和他同一層級的其餘區域變數。

               咱們能夠拷貝一個block,也能夠將它丟到其餘的執行緒中使用,基本上雖然block在iOS程式開發中可使用在C/C++開發的程式片斷,也能夠在Objective-C中使用,不過在系統的定義上,block永遠會被視爲是一個Objective-C的物件。

            2.2 Block 的使用時機

                Block 通常是用來表示、簡化一小段的程式碼,它特別適合用來創建一些同步執行的程式片斷、封裝一些小型的工做或是用來作爲某一個工做完成時的回傳呼叫(callback) 。在新的iOS API中block被大量用來取代傳統的delegate和callback,而新的API會大量使用block主要是基於如下兩個緣由:

                    能夠直接在程式碼中撰寫等會要接着執行的程式,直接將程式碼變成函數的參數傳入函數中,這是新API最常使用block的地方。

                    能夠存取區域變數,在傳統的callback實做時,若想要存取區域變數得將變數封裝成結構才能使用,而block則是能夠很方便地直接存取區域變數。

      3宣告和創建Block

         3.1 宣告Block的參考

                Block 變數儲存的是一個block的參考,咱們使用相似宣告指標的方式來宣告,不一樣的是這時block變數指到的地方是一個函數,而指標使用的是「*」,block則是使用「^」來宣告,下面是一些合法的block宣告:

/* 回傳void ,參數也是void 的block*/
void(^blockReturningVoidWithVoidArgument)( void );

/* 回傳整數,兩個參數分別是整數和字元型態的block*/
int(^blockReturningIntWithIntAndCharArguments)(int,char);

/*回傳void,含有10個block的陣列,每一個block都有一個型態爲整數的參數*/
void(^arrayOfTenBlocksReturningVoidWinIntArgument[10])(int);

             3.2 創建一個Block

             咱們使用「^」來開始一個block,並在最後使用「;」來表示結束,下面的範例示範了一個block變量,而後再定義一個block把它指定給block變量:

/* 宣告block 變量*/
int (^oneFrom)( int );
/* 定義block 的內容並指定給上面宣告的變量*/
oneFrom = ^(int anInt)
{
    return anInt = - 1 ; 
};

             3.3 全局的Block

能夠在檔案中宣告一個全域的block,請參考如下範例:

int GlobalInt = 0;
int(^getGlobalInt)(void) = ^(void){
   return GlobalInt; 
};


4Block和變量

    4.1 變量的型態

        咱們能夠在block中遇到日常在函數中會遇到的變量類型:

             全域(global)變量或是靜態的區域變量(static local)。

             全域的函數。

             區域變量和由封閉領域(enclosing scope)傳入的參數。

             除了上述以外block額外支援了另外兩種變量:在函數內可使用__block 變量,這些變量在block中是可被修改的。匯入常數(const imports)。此外,在方法的實做裏,block可使用Objective-C的實體變量(instance variable)。

       下列的規則能夠套用到在block中變量的使用:

            能夠存取全域變量和在同一領域(enclosing lexical scope)中的靜態變量。

            能夠存取傳入block的參數(使用方式和傳入函數的參數相同)。

            在同一領域的區域變數在block中將視爲常數(const)。

            能夠存取在同一領域中以__block 爲修飾詞的變數。

            在block中宣告的區域變數,使用方式和日常函數使用區域變數的方式相同。

            下面的例子介紹了區域變數(上述第三點)的使用方式:


int x = 123;
void (^printXandY)(int) = ^(int y)
{
   printf("%d %d", x,y);
}

printXAndY( 456 );//將會印出123 456

就如上面第三點所提到的,在上例中的int x = 123的變量x,在傳入block後將視同常數,所以若咱們在block中試着去修改x的值時就會產生錯誤.若在block中想要修改上面的變數x,必須將x宣告加上修飾詞__block,請參考接下來這一小節的介紹。    

    4.2 __block 型態變量

咱們能夠藉由將一個由外部匯入block的變數放上修飾詞__block來讓這個變數由惟讀變成能夠讀和寫,不過有一個限制就是傳入的變數 在記憶體中必須是一個佔有固定長度記憶體的變數,__block修飾詞沒法使用於像是變更長度的陣列這類不定長度的變數,請參考下面的範例: 

//加上__block 修飾詞,因此能夠在block 中被修改。
__block int x = 123;
void (^printXandY)(int) = ^(int y)
{
   x = x+y;
   printf("%d %d",x,y);
}
printXAndY( 456 ); // 將會印出579 456
下面咱們使用一個範例來介紹各種型的變數和block之間的互動: 


extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
{
   NSInteger localCounter = 42 ;
   __block char localCharacter;
   void(^aBlock)(void) = ^(void)
   {
      ++ CounterGlobal; //能夠存取 
      ++ CounterStatic; //能夠存取
      CounterGlobal = localCounter; //localCounter在block 創建時就不可變了
      localCharacter = 'a' ; //設定外面定義的localCharacter 變數
   }
   ++localCounter; //不會影響的block 中的值
   localCharacter = 'b';
   aBlock(); //執行block 的內容
   //執行完後,localCharachter 會變成'a'
}
      4 .3 物件和Block變量


Block 支援在Objective-C、C++物件和其餘block中看成變數來使用,不過由於在大部分的狀況咱們都是使用Objective-C的撰寫程式,因 此在這一小節咱們僅針對Objective-C的狀況進行介紹,至於其餘兩種狀況就留給有興趣的讀者再自行深刻研究了。

    4.3.1Objective-C物件

在擁有參考計數(reference-counted)的環境中,若咱們在block中參考到Objective-C的物件,在通常的狀況下它將會自動增長物件的參考計數,不過若以__block爲修飾詞的物件,參考計數則是不受影響。

     若是咱們在Objective-C的方法中使用block時,如下幾個和記憶體管理的事是須要額外注意的:

        若直接存取實體變量(instance variable),self的參考計數將被加1。

        若透過變數存取實體變數的值,則只變數的參考計數將被加1。

        如下程式碼說明上面兩種狀況,在這個假設instanceVariable是實體變量:

dispath_async(queue,^{

   //由於直接存取實體變數instanceVariable ,因此self 的retain count會加1
   doSomethingWithObject (instanceVariable); 
});

id localVaribale = instanceVariable;
dispatch_async(queue,^{
   //localVariable是存取值,因此這時只有localVariable 的retain count 加1
   //self 的return count並不會增長。
   doSomethingWithObject (localVaribale);
});
5. 使用Block


    5.1 呼叫一個Block

當block宣告成一個變量時,咱們能夠像使用通常函數的方式來使用它,請參考下面兩個範例:

int(^oneFrom)(int) = ^(int anInt){
   return anInt-1;
};
printf("1 from 10 is %d", oneFrom(10));
//結果會顯示:1 from 10 is 9


float(^distanceTraveled)(float, float, float) = ^(float startingSpeed, float acceleration, float time)
{
   float distance = (startingSpeed * time) + ( 0.5 * acceleration * time * time);
   return distance;
};
float howFar = distanceTraveled( 0.0 , 9.8 , 1.0 );
//howFar會變成4.9
在通常常見的狀況中,如果將block當作是參數傳入函數,咱們一般會使用「內嵌」的方式來使用block。


    5.2 將Block看成函數的參數

咱們能夠像使用通常函數使用參數的方式,將block以函數參數的型式傳入函數中,在這種狀況下,大多數咱們使用block的方式將不會傾向宣告block而是直接之內嵌的方式來將block傳入,這也是目前新版SDK中主流的作法,咱們將補充前面章節的例子來講明:

char *myCharacters[3]={"TomJohn","George","Charles Condomine"};
qsort_b(myCharacters, 3, sizeof(char *),^(const void *l,const void *r){
   char *left = *(char **)l;
   char *right = *( char **)r;
   return strncmp (left, right, 1 );
}//這裏是block 的終點。 
);
//最後的結果爲:{"Charles Condomine", "George", "TomJohn"}
在上面的例子中,block自己就是函數參數的一部分,在下一個例子中dispatch_apply函數中使用block,dispatch_apply的定義以下:


void dispatch_apply(size_t iterations,dispatch_queue_t queue,void(^block)(size_t));
這個函數將一個block提交到發送佇列(dispatch queue)中來執行多重的呼叫,只有當佇列中的工做都執行完成後纔會回傳,這個函數擁有三個變數,而最後一個參數就是block ,請參考下面的範例:


size_t count = 10;
dispatch_queue_t queue = dispatch_get_global_queue ( DISPATCH_QUEUE_PRIORITY_DEFAULT , 0 );
dispatch_apply(count, queue, ^(size_t i))
{
   printf ( "%u\n" , i);
}


        5.3 將Block看成方法的參數

在SDK中提供了許多使用block的方法,咱們能夠像傳遞通常參數的方式來傳遞block,下面這個範例示範如何在一個陣列的前5筆資料中取出咱們想要的資料的索引值:

//全部的資料
NSArray *array = [ NSArray arrayWithObjects : @"A" , @"B" , @"C" , @"A" , @"B" , @"Z" , @"G" , @"are" , @" Q" ,nil ];
//咱們只要這個集合內的資料
NSSet *filterSet = [ NSSet setWithObjects : @"A" , @"B" , @"Z" , @"Q" , nil ];
BOOL (^test)(id obj,NSUInteger idx,BOOL *stop);
test = ^ ( id obj, NSUInteger idx, BOOL *stop)
{
   //只對前5筆資料作檢查
   if (idx < 5 ) {
      if ([filterSet containsObject : obj]) {
         return YES ;   
      }
   }
   return NO ;
}
NSIndexSet *indexes = [array indexesOfObjectsPassingTest :test];
NSLog ( @"indexes: %@" , indexes); 
//結果:indexes: <NSIndexSet: 0x6101ff0>[number of indexes: 4 (in 2 ranges), indexes: (0-1 3-4)]
//前5筆資料中,有4筆符合條件,它們的索引值分別是0-1, 3-4
         5 .4  該避免的使用方式

在下面的例子中,block是for迴圈的區域變數所以在使用上必須避免將區域的block指定給外面宣告的block:

//這是錯誤的範例,請勿在程式中使用這些語法!!
void dontDoThis() {
   void (^blockArray[3])(void); // 3 個block 的陣列
   for (int i = 0; i < 3; ++i) {
      blockArray[i] = ^{ printf("hello, %d\n", i); };
      // 注意: 這個block 定義僅在for 迴圈有效。
   }
}

void dontDoThisEither() {
   void (^block)(void);
   int i = random():
   if (i > 1000) {
      block = ^{ printf("got i at: %d\n", i); };
   }
}
相關文章
相關標籤/搜索