1. 程式人生 > >【iOS開發】---- block 教程

【iOS開發】---- block 教程

      本文來自臺灣的某開發人員的部落格,被牆,感覺講的比較易懂,所以引過來。文字簡體化了,原來是繁體,變數=變數,這個注意一下。


本章學習目標:

1. 瞭解何謂block。

2. 瞭解block的使用方法。

Block 是iOS在4.0之後新增的程式語法,嚴格來說block的概念並不算是基礎程式設計的範圍,對初學者來說也不是很容易瞭解,但是在iOS SDK 4.0之後,block幾乎出現在所有新版的API之中,換句話說,如果不瞭解block這個概念就無法使用SDK 4.0版本以後的新功能,因此雖然block本身的語法有點難度,但為了使用iOS的新功能我們還是得硬著頭皮去了解這個新的程式概念。

在這一章的目標以瞭解如何使用block為主而不深入探討block底層的運作方式,至於有些初學者較少遇的辭彙如「詞法作用域(lexical scope)」等,本章將不再多做解釋,待有興趣的讀者去請教Google大神吧。

---------------------------我是萬惡的分割線----------------------------------

X.1 初探Block

在這一小節我們先用一些簡單範例來匯入block的概念。

X.1.1 宣告和使用Block

我們使用「^」運運算元來宣告一個block變數,而且在block的定義最後面要加上「;」來表示一個完整的

述句(也就是將整個blo​​ck定義視為前面章節所介紹的簡單述句,因為整個定義必須是一個完整的句子,

所以必須在最後面加上分號),下面是一個block的範例:

  1. 1: int multiplier = 7 ;  
  2. 2: int (^myBlock)( int ) = ^( int num)  
  3. 3: {  
  4. 4:     return num * multiplier;  
  5. 5: };  

我們使用下圖來解釋這個範例(請將文字框的字翻譯如下):

我們宣告一個「myBlock」變數,用「^」符號來表示這是一個block。

這是block的完整定義,這個定義將會指定給「myBlock」變數。

表示「myBlock」是一個回傳值為整數(int)的block。

它有一個引數,型態也是整數。

這個引數的名字叫做「num」。

這是block的內容。

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

  1. 1: int multiplier = 7 ;  
  2. 2: int (^myBlock)( int ) = ^( int num)  
  3. 3: {  
  4. 4:     return num * multiplier;  
  5. 5: };  
  6. 6: printf ( "%d" , myBlock( 3 ));  
  7. 7: //結果會打印出21  

X.1.2 直接使用Block

在很多情況下,我們並不需要將block宣告成變數,反之我們可以直接在需要使用block的地方直接用內嵌的方式將block的內容寫出來,在下面的例子中qsort_b函式,這是一個類似傳統的qsort_t函式,但是直接使用block做為它的引數:

  1. 1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };  
  2. 2: qsort_b (myCharacters, 3 ,  
  3. 3:          sizeof ( char *),  
  4. 4:          ^( const void *l, const void *r)//block部分  
  5. 5:             {  
  6. 6:                 char *left = *( char **)l;  
  7. 7:                 char *right = *( char **)r;  
  8. 8:                 return strncmp (left, right, 1 );  
  9. 9:             }                            //end  
  10. 0: );  

X.1.3 __block 變數

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

  1.  1: __block int multiplier = 7 ;  
  2.  2: int (^myBlock)( int ) = ^( int num)  
  3.  3:                         {  
  4.  4:                             if (num > 5 )  
  5.  5:                             {  
  6.  6:                                   multiplier = 7 ;  
  7.  7:                             }  
  8.  8:                             else  
  9.  9:                             {  
  10. 10:                                   multiplier = 10 ;  
  11. 11:                             }  
  12. 12:                             return num * multiplier;  
  13. 13:                         };  
X.2 Block 概要

        Block 提供我們一種能夠將函式程式碼內嵌在一般述句中的方法,在其他語言中也有類似的概念稱做「closure」,但是為了配合Objective-C的貫例,我們一律將這種用法稱為「block」

X.2.1 Block 的功能

Block 是一種具有匿名功能的內嵌函式,它的特性如下:

如一般的函式般能擁有帶有型態的引數。

擁有回傳值。

可以擷取被定義的詞法作用域(lexical scope)狀態。

可以選擇性地修改詞法作用域的狀態。

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

我們可以拷貝一個block,也可以將它丟到其他的執行緒中使用,基本上雖然b​​lock在iOS程式開發中可以使用在C/C++開發的程式片段,也可以在Objective-C中使用,不過在系統的定義上,block永遠會被視為是一個Objective-C的物件。

X.2.2 Block 的使用時機

Block 一般是用來表示、簡化一小段的程式碼,它特別適合用來建立一些同步執行的程式片段、封裝一些小型的工作或是用來做為某一個工作完成時的回傳呼叫(callback) 。在新的iOS API中block被大量用來取代傳統的delegate和callback,而新的API會大量使用block主要是基於以下兩個原因:可以直接在程式碼中撰寫等會要接著執行的程式,直接將程式碼變成函式的引數傳入函式中,這新API最常使用block的地方。可以存取區域變數,在傳統的callback實作時,若想要存取區域變數得將變數封裝成結構才能使用,而block則是可以很方便地直接存取區域變數。

X.3 宣告和建立Block

X.3.1 宣告Block的參考(Reference)

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

  1. 1: /* 回傳void ,引數也是void 的block*/  
  2.  2: void (^blockReturningVoidWithVoidArgument)( void );  
  3.  3: /* 回傳整數,兩個引數分別是整數和字元型態的block*/  
  4.  4: int   (^blockReturningIntWithIntAndCharArguments)( int , char );  
  5.  5: /* 回傳void ,含有10 個block 的陣列,每個block 都有一個型態為整數的引數*/  
  6.  6: void (^arrayOfTenBlocksReturningVoidWinIntArgument[ 10 ])( int );  
  7.  7: X.3.2 建立一個Block   
  8.  8:    
  9.  9: 我們使用「^」來開始一個block,並在最後使用「;」來表示結束,下面的範例示範了一個block變數,然後再定義一個block把它指定給block變數:   
  10. 10:    
  11. 11: int (^oneFrom)( int ); /* 宣告block 變數*/  
  12. 12:     /* 定義block 的內容並指定給上面宣告的變數*/  
  13. 13:     oneFrom = ^(int anInt)  
  14. 14:                 {  
  15. 15:                     return anInt = - 1 ;   
  16. 16:                 };  


X.3.3 全域的Block

我在可以在檔案中宣告一個全域的block,請參考以下範例:

  1. 1: int GlobalInt = 0 ;  
  2. 2: int (^getGlobalInt)( void ) = ^ ( void ) { return GlobalInt ;};  
X.4 Block 和變數

接下來的這一小節我們將會介紹block和變數之間的互動。

X.4.1 變數的型態

我們可以在block中遇到平常在函式中會遇到的變數型別:

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

l 全域的函式。

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

除了上述之外block額外支援了另外兩種變數:

在函式內可以使用__block 變數,這些變數在block中是可被修改​​的。

匯入常數(const imports)。

此外,在方法的實作裡,block可以使用Objective-C的實體變數(instance variable)。

下列的規則可以套用到在block中變數的使用:

可以存取全域變數和在同一領域(enclosing lexical scope)中的靜態變數。

可以存取傳入block的引數(使用方式和傳入函式的引數相同)。

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

可以存取在同一領域中以__block 為修飾詞的變數。

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

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

  1.  1: int x = 123 ;  
  2.  2: void (^printXAndY)( int ) = ^( int y)  
  3.  3:     {  
  4.  4: printf ( "%d %d\n" , x, y);   
  5.  5:     };  
  6.  6: // 將會印出123 456  
  7.  7:     printXAndY( 456 );  
  8.  8: 就如上面第三點所提到的,在上例中的int x = 123的變數x,在傳入block後將視同常數,因此若我們在block中試著去修改x的值時就會產生錯誤,下面的例子將會無法通過編譯:   
  9.  9:    
  10. 10: int x = 123 ;  
  11. 11: void (^printXAndY)( int ) = ^( int y)  
  12. 12: {  
  13. 13:     // 下面這一行是錯的,因為x 在這是一個常數不能被修改。  
  14. 14:     x = x + y;  
  15. 15:     printf ( "%d %d\n" , x, y);   
  16. 16:     };  

若在block中想要修改上面的變數x,必須將x宣告加上修飾詞__block,請參考接下來這一小節的介紹。

X.4.2 __block 型態變數

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

  1.  1: // 加上__block 修飾詞,所以可以在block 中被修改。  
  2.  2: __block int x = 123 ;  
  3.  3: void (^printXAndY)( int ) = ^( int y)  
  4.  4:     {  
  5.  5:         x = x + y;   
  6.  6: printf ( "%d %d\n" , x, y);   
  7.  7:     };  
  8.  8: // 將會印出579 456  
  9.  9:     printXAndY( 456 );  
  10. 10: //x 將會變成 579;  
  11. 11: 下面我們使用一個範例來介紹各型別的變數和block之間的互動:   
  12. 12:    
  13. 13: extern NSInteger CounterGlobal;  
  14. 14: static NSInteger CounterStatic;  
  15. 15: {  
  16. 16: NSInteger localCounter = 42 ;  
  17. 17: __block char localCharacter;  
  18. 18: void (^aBlock)( void ) = ^( void )  
  19. 19:     {  
  20. 20:         ++ CounterGlobal ; //可以存取。  
  21. 21:         ++ CounterStatic ; //可以存取。   
  22. 22: CounterGlobal = localCounter; //localCounter在block 建立時就不可變了。  
  23. 23:         localCharacter = 'a' ; //設定外面定義的localCharacter 變數。  
  24. 24:     };  
  25. 25:     ++localCounter; //不會影響的block 中的值。  
  26. 26:     localCharacter = 'b' ;  
  27. 27:     aBlock(); //執行block 的內容。  
  28. 28: //執行完後,localCharachter 會變成'a'  
  29. 29: }  

X.4.3 物件和Block變數

Block 支援在Objective-C、C++物件和其他block中當作變數來使用,不過因為在大部分的情況我們

都是使用Objective-C的撰寫程式,因此在這一小節我們僅針對Objective-C的情況進行介紹,至於

其他兩種情況就留給有興趣的讀者再自行深入研究了。

x.4.3.1 Objective-C 物件

在擁有參考計數(reference-counted)的環境中,若我們在block中參考到Objective-C的物件,

在一般的情況下它將會自動增加物件的參考計數,不過若以__block為修飾詞的物件,參考計數則

是不受影響。

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

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

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

以下程式碼說明上面兩種情況,在這個假設instanceVariable是實體變數:

  1. 1: dispatch_async (queue, ^{  
  2. 2: // 因為直接存取實體變數instanceVariable ,所以self 的retain count 會加1  
  3. 3: doSomethingWithObject (instanceVariable);  
  4. 4:     });  
  5. 5: id localVaribale = instanceVariable;  
  6. 6: dispatch_async (queue, ^{  
  7. 7: //localVariable 是存取值,所以這時只有localVariable 的retain count 加1  
  8. 8: //self 的 return count  並不會增加。  
  9. 9: doSomethingWithObject (localVaribale);  
  10. 0:     });  
X.5 使用Block

這一小節我們將會對block的使用方式做一些初步的介紹

X.5.1 呼叫一個Block

當block宣告成一個變數時,我們可以像使用一般函式的方式來使用它,請參考下面兩個範例:

  1.  1: int (^oneFrom)( int ) = ^( int anInt) {  
  2.  2: return anInt - 1 ;  
  3.  3:     };  
  4.  4: printf ( "1 from 10 is %d" , oneFrom( 10 ));  
  5.  5: //結果會顯示:1 from 10 is 9  
  6.  6: float (^distanceTraveled)( float , float , float ) = ^( float startingSpeed, float acceleration, float time)  
  7.  7:     {  
  8.  8: float distance = (startingSpeed ​​* time) + ( 0.5 * acceleration * time * time);  
  9.  9: return distance;  
  10. 10:     };  
  11. 11: float howFar = distanceTraveled( 0.0 , 9.8 , 1.0 );  
  12. 12: //howFar會變成4.9  

在一般常見的情況中,若是將block當做是引數傳入函式,我們通常會使用「內嵌」的方式來使用block。

X.5.2 將Block當作函式的引數

我們可以像使用一般函式使用引數的方式,將block以函式引數的型式傳入函式中,在這種情況下,大多數我們使用block的方式將不會傾向宣告block而是直接以內嵌的方式來將block傳入,這也是目前新版SDK中主流的做法,我們將補充前面章節的例子來說明:

  1. 1: char *myCharacters[ 3 ] = { "TomJohn" , "George" , "Charles Condomine" };  
  2. 2: qsort_b (myCharacters, 3 , sizeof ( char *),  
  3. 3:             ^( const void *l, const void *r)  
  4. 4:             {  
  5. 5: char *left = *( char **)l;  
  6. 6: char *right = *( char **)r;  
  7. 7: return strncmp (left, right, 1 );  
  8. 8:             } // 這裡是block 的終點。  
  9. 9:             );  
  10. 0: // 最後的結果為:{"Charles Condomine", "George", "TomJohn"}  

在上面的例子中,block本身就是函式引數的一部分,在下一個例子中dispatch_apply函式中使用block,dispatch_apply的定義如下:

  1. 相關推薦

    iOS開發---- block 教程

          本文來自臺灣的某開發人員的部落格,被牆,感覺講的比較易懂,所以引過來。文字簡體化了,原來是繁體,變數=變數,這個注意一下。 本章學習目標: 1. 瞭解何謂block。 2. 瞭解block的使用方法。 Block 是iOS在4.0之後新增的程式語

    iOS開發Gitlab教程 (一)

    一 、gitLab 建立工程 Project name : 工程名字起一個,最好和專案相關; Namespace : 你自己的gitLab工作空間,預設就好; Description : 描述,隨便寫。 Visibi

    iOS開發iOS移動端架構

    引言:一個app的初始階段,必然是先滿足各種業務需求。然後,經過多次版本迭代之後,先前的由於急於滿足需求而導致的雜亂程式碼則會充斥整個專案。而此時,專案有了一定的規模,有了一定數量的開發人員,那麼為了達到快速迭代版本的需求,則是需要有一個強大的架構來支撐。

    iOS開發判斷app啟動的方式(launchOptions)

    iOS app啟動的方式有哪些: 自己啟動(使用者手動點選啟動)urlscheme啟動(關於urlScheme的詳解點選開啟連結)本地通知啟動  (自己寫的本地通知啟動,藍芽模組的啟動,地理圍欄的啟動)遠端通知啟動    (後臺伺服器的推送通知)在appdelegate.m

    iOS開發SEL和Selector 原理小結

    一 、Selector(選擇器)簡介 選擇器是用來選擇一個方法來為一個物件 執行的名稱,或是在編譯原始碼時替換該名稱的 唯一識別符號的名稱。一個選擇器本身不做任何事情。它簡單地識別了一種方法。唯一使選擇器的方法名稱不同於普通字串,編譯器確保選擇器是獨特的。

    iOS開發一些常見的警告解決方案(更新中。。。)

    Unknown pattern color for the Background Color attribute 1.背景色屬性為未知模式的顏色 解決:預設xib裡面控制元件的背景色為Default。如果出現警告,可能是你定義的顏色Xcode啟動

    iOS開發UIWebView載入html時不顯示網路圖片解決辦法

    NSString *html = @"<html><body><p>怎麼顯示網路圖片 <img src=\"http://p0.ifengimg.com/pmop/2017/1218/F9636BB16CC72EC34B5FCB78

    APP內開啟另一個APP(URL Scheme與openURL)iOS開發

    目標 平常我們做iOS開發,會經常遇到開啟其他的APP的功能。本篇文章講的就是開啟別人的APP的一些知識。我們的目標是: 開啟別人的APP讓別人開啟我們的APPiOS9的適配問題使用URL Schemes傳遞資料 準備工作 建立一個名為OpenApp的工作空間,用來存放

    iOS開發陣列的去重(無序和有序)

    一、 無序的去重 1.利用NSDictionary的AllKeys(AllValues)方法 可以將NSArray中的元素存入一個字典,然後利用AllKeys或者AllValues取得字典的所有鍵或值,這些鍵或值都是去重的。 程式碼:

    iOS開發---- tableView背景隨表滾動

            最近專案中用到了“圖隨表動”,如下圖所示:         這是一個訂單,需要add shipping insurance以上的部分隨著表格的滾動而滾動,這裡表格是group型別的,所以背景加在cell上是達不到效果的,在code4App上找到了一個例子(

    iOS開發---- 語音識別

            最近iOS專案中需要用到語音識別技術(也被稱為自動語音識別,英語:Automatic Speech Recognition, ASR)。去google搜尋了一下,發現語音識別做的不錯的

    IOS 開發Object

    .一. 類定義類定義需要實現兩部分 : -- 介面部分 : 定義類的成員變數和方法, 方法是抽象的, 在標頭檔案中定義;-- 實現部分 : 引入介面部分的標頭檔案, 實現抽象方法;1. 介面部分定義 (

    iOS開發UIView(包括子類)的幾個初始化方法

    -(id)initWithFrame:(CGRect)frame UIView的指定初始化方法; 總是傳送給UIView去初始化, 除非是從一個nib檔案中載入的; -(id

    iOS開發NSThread

     atomic    原子屬性,是預設屬性     * 是在多執行緒開發時,保證多個執行緒在"寫入"的時候,能夠保證只有一條執行緒執行寫入操作!     * 是一個單(執行緒)寫多(執行緒)讀的多執行緒技術     * 原子屬性,解決不了賣票問題,因為賣票的讀寫都需要鎖定

    iOS開發launch Images啟動圖片設定(UILaunchImageFile)之002

    一、實現效果: 通過直接給圖片起預設的名字,讓app啟動頁自動載入啟動圖片。 二、注意點: ·圖片命名一定要按蘋果官方的指定規則命名,圖片的畫素也要符合規則; ·如果橫豎屏圖片都需要,記得在Xcode中勾選上專案支援橫豎屏。 三、官方描述:

    iOS 開發將自己的框架打包成 Framework 的方法

    建立 Framework 工程1. 建立打包工程開啟 Xcode 新建一個工程,選擇 Framework & Libray 選項中的 Cocoa Touch Framework ,這裡說一下我的 Xcode 版本是 8.3.2 。建立工程因為要打包所需的檔案,所以這裡我自己建立了一個測試類,在實際的打

    iOS開發 常遇到的Crash和Bug處理

    七:pngcrush caught libpng error,Not a PNG file Could not find file While reading /XXX/XXX/XXX/img1.png pngcrush caught libpng error:   Not a PNG filCou

    iOS開發---- 手把手教你github託管程式碼

           在csdn上還有一篇介紹如何使用github託管程式碼的: 兩分鐘學會在GitHub託管程式碼。我照著這個教程嘗試了一遍,發現程式碼並沒有託管上去,只是建立了一個存放程式碼的倉庫(re

    iOS 開發Status Bar 狀態列設定彙總

    狀態列 個人覺得 iOS 的 Status Bar 狀態列也是一個比較坑的地方,所以還是寫一個總結,有遇到這方面問題的朋友可以看一下。 Status Bar 狀態列的隱藏 1. 通過設定 Info.plist 檔案實現狀態列的全域性隱藏 在 Info.pl

    iOS開發---- 快速將大圖儲存到本地

           碰到一個問題:如何快速的批量儲存iphone相簿中的圖片(原始的大圖,解析度高)到本地? 儲存圖片到本地,首先得拿到這個圖片:alAsset.defaultRepresentation.fullResolutionImage.但是圖片太大了,大批量的儲存取這個