1. 程式人生 > 實用技巧 >IOS:block知識點個人筆記總結

IOS:block知識點個人筆記總結

自己也是初學者,從其它人的部落格,以及教學視訊總結來的,如果有錯誤,請大家指正。希望能夠幫上螢幕的你。

覺得不錯的點個贊吧,轉載請註明。

==========================================================================================================

首先來看C語言中定義一個函式,然後去呼叫的例子

void fun(int count);

fun(10);

如果使用

void (*funPointer)(int)=&fun;

(*funPointer)(10);

或者

void (*funPointer)(int);

funPointer=fun;

funPointer(10);

先用指標指向這個函式,然後再傳值,這樣看起來就像是沒經過函式名稱。

=========================================================================================================

關於block,block其實是一種特殊的資料型別,用來儲存一段程式碼。應用場景比較廣泛,例如集合遍歷,網路回撥,多執行緒等等

block和函式一樣,可以有形參有返回值,有形參沒返回值,沒形參有返回值,沒形參沒返回值。

block的語法

與一般函式類似,分為宣告和具體程式碼的實現。

宣告格式: 返回值 (^xxblock)(形參型別);//block名稱一般都以block結尾,多個形參時之間用逗號隔開,形參可以只寫引數型別

實現格式: xxblock=^(返回值型別)(引數列表){……};//沒有引數時,實現^後面的()可以去掉,宣告的後面的()不能去掉,返回值型別一般不寫。

呼叫 : xxblock();

上面是宣告和實現分開寫,常用的寫法格式是定義的時候初始化:返回值 (^xxblock)(形參型別)=^(形參型別){……};

下面給出一些具體的例子

 1
//=====無引數無返回============ 2 //先聲明後實現 3 void (^funBlock)(); 4 funBlock=^{ 5 NSLog(@"無參無返回"); 6 }; 7 funBlock(); 8 //宣告時初始化 9 void (^fun2Block)()=^{ 10 NSLog(@"無參無返回"); 11 }; 12 fun2Block(); 13 14 //======無引數有返回========== 15 int (^fun3Block)(); 16 fun3Block=^{ 17 return 1; 18 }; 19 NSLog(@"無引數有返回值,返回結果:%d",fun3Block()); 20 int (^fun4Block)()=^{return 1;}; 21 NSLog(@"無引數有返回值,返回結果:%d",fun4Block()); 22 //=====有引數無返回============ 23 void (^fun5Block)(int,int); 24 fun5Block=^(int n1,int n2){ 25 NSLog(@"n1+n2結果是%d",n1+n2); 26 }; 27 fun5Block(10,15); 28 void (^fun6Block)(int,int)=^(int n1,int n2) 29 { 30 NSLog(@"n1+n2結果是%d",n1+n2); 31 }; 32 fun6Block(10,10); 33 //======有引數有返回============ 34 int (^fun7Block)(int,int); 35 fun7Block=^(int n1,int n2){ 36 return n1+n2; 37 }; 38 NSLog(@"呼叫fun7block結果是%d",fun7Block(20,20)); 39 int (^fun8Block)(int ,int )=^(int n1,int n2){ 40 return n1+n2; 41 }; 42 NSLog(@"呼叫fun8block結果是%d",fun8Block(200,20));

關鍵字typedef

關於typedef,從名字上來說是型別定義,其實它是為了幫助我們重新定義一種型別,解決有相同程式碼時的重複與累贅,先寫兩個用指標指向函式的例子

 1 int  test1(int n1 ,int n2)
 2  {
 3      return  n1+n2;
 4 };
 5 int  test2(int n1,int n2)
 6 {
 7     return  2*(n1+n2);
 8 }
 9 int main(int argc, const char * argv[]) {
10     int (*test1Pointer)(int,int);
11     test1Pointer=test1;
12     NSLog(@"==test1Pointer===%d", test1Pointer(10,10));
13     
14     int (*test2Pointer)(int,int);
15     test2Pointer=test2;
16     NSLog(@"===test2Pointer==%d",test2Pointer(10,10));
17     return 0;
18 }

我們會發現其中有些重複的程式碼,似乎是可以避免的,於是我們就用到了typedef。

 1 int  test1(int n1 ,int n2)
 2  {
 3      return  n1+n2;
 4 };
 5 int  test2(int n1,int n2)
 6 {
 7     return  2*(n1+n2);
 8 }
 9 /*
10  用typedef宣告一個有返回值,有兩個引數的指標型別
11  用這個型別宣告兩個指標,分別指向test1,test2,
12  這樣我們就不用去寫兩個指標的聲明瞭。
13   */
14 typedef int (*newtest)(int,int);
15 int main(int argc, const char * argv[]) {
16 //    int (*test1Pointer)(int,int);
17 //    test1Pointer=test1;
18     newtest test1Pointer=test1;
19     NSLog(@"==test1Pointer===%d", test1Pointer(10,10));
20     
21 //    int (*test2Pointer)(int,int);
22 //    test2Pointer=test2;
23     newtest test2Pointer=test2;
24     NSLog(@"===test2Pointer==%d",test2Pointer(10,10));
25     return 0;
26 }

我們將typedef運用到block中,下面給出兩個例子。

 1 typedef int (^newtestBlock)(int,int);//宣告一個有返回值,有形參的block型別,在main方法中,用這個block型別宣告兩個block變數
 2 int main(int argc, const char * argv[]) {
 3 //    int (^fun7Block)(int,int);
 4      newtestBlock  fun7Block=^(int n1,int n2){
 5            return n1+n2;
 6        };
 7     NSLog(@"呼叫fun7block結果是%d",fun7Block(20,20));
 8 //    int (^fun8Block)(int,int);
 9      newtestBlock fun8Block=^(int n1,int n2)
10     {
11         return 2*(n1+n2);
12     };
13     NSLog(@"呼叫fun8block結果是%d",fun8Block(20,20));
14  
15     return 0;
16 }

需要注意的是,我們定義了block型別,再去宣告兩個block變數,那麼這兩個block變數的返回值型別和引數型別都是定義好了的。我們在寫例子演示的時候是先寫幾個block,再進行抽取式的行為,因此需要看清返回值型別,和引數型別。

block的一些用法

1:例如block作為屬性在A類中的.h檔案宣告,呼叫是在A類的.m檔案中,但是block的實現是在B類的.m檔案中。

這個是最近碰到的一個使用的例子,需求是獲取當前點的顏色,用到的是網上的一個框架,框架中獲取當前顏色的方法,然後呼叫自己類的block,傳入引數color物件,但是block的實現是在另一個類中,由於block的實現中,有些傳送藍芽命令以及跟view上的控制元件有關的程式碼,所以如果把block的實現寫在A類中,其實是不太合適的。

2:在A類中作為屬性宣告,實現也在本類。B類中呼叫。

3:block作為方法的引數使用

A類中寫一個將block作為引數的方法,B類中先定義初始化一個block(),然後呼叫A類的方法,將B類的block傳進去。或者在B類中直接呼叫A類的方法, 然後寫一個block的實現。A類收到後,再做對應的操作。

4:作為方法的返回值使用

A類中寫一個方法,方法的返回值是block物件,方法內部返回的也是一個block物件,然後B類中呼叫這個方法,例如下面這個例子

 1 //.h檔案及.m檔案
 2 @interface useA : NSObject
 3 -(void(^)(int))run;
 4 @end
 5 @implementation useA
 6 -(void(^)(int))run
 7 {
 8     return ^(int a){NSLog(@"傳入的值是%d",a);};
 9 }
10 @end
11 //main
12   useA *usea=[[useA alloc]init];
13   usea.run(5);

block的注意事項

1:block中可以訪問外部變數

1 int main(int argc, const char * argv[]) {
2     int a=10;
3     void (^testBlock)()=^{NSLog(@"==========%d",a);};
4     testBlock();
5     return 0;
6 }

2:block中可以建立外部同名的變數,訪問的時候就近原則。

int main(int argc, const char * argv[]) {
      int a=10;
      void (^testBlock)()=^{
        int a=20;
        NSLog(@"==========%d",a);};
    testBlock();
    return 0;
}

3:block中預設不能修改外部變數的值。block如果訪問了外部變數,會將外部變數的值拷貝一份到堆記憶體(可通過列印地址驗證)。

接著如果在block後修改變數的值,block立馬的值拷貝的還是初始的,並不會因為外部之後修改了而發生變化

 1 int main(int argc, const char * argv[]) {
 2          int a=10;
 3         NSLog(@"======%i",&a);
 4          void (^testBlock)()=^{
 5              NSLog(@"a====%d",a);
 6         NSLog(@"======%i",&a);};
 7     a=20;
 8     testBlock();
 9     return 0;
10 }

4:如果想在block中修改外部變數的值,必須在外界變數的前面加上__block修飾,這種方式會改變外部的值。

將該檔案轉成c++檔案,會發現沒有__block的時候,對變數操作是值傳遞,加了後是引用傳遞,因此會修改值。

1 //沒有使用block,僅在block中訪問了外部的變數
2 int main(int argc, const char * argv[]) {
3          int a=10;
4          void (^testBlock)()=^{
5              NSLog(@"======%i",a);};
6     testBlock();
7     return 0;
8 }

在終端中切換到當前的檔案所在的路徑,例如我是在main檔案寫的,那麼切換到這個路徑即可,然後使用cc-rewrite-objcmain.m,將檔案轉為c++檔案,在資料夾中發現多了個檔案,開啟,找到main方法所在的地方,可以看到對a的操作是值傳遞

1 int main(int argc, const char * argv[]) {
2          int a=10;
3  
4          void (*testBlock)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
5  
6     ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
7     return 0;
8 }


用同樣的方式,在用了__block的情況下,開啟main.cpp檔案,可以看見裡面的有關main方法的程式碼

對a的操作是引用傳遞

 1 int main(int argc, const char * argv[]) {
 2          __block int a=10;
 3  
 4          void (^testBlock)()=^{
 5              a=20;
 6              NSLog(@"======%i",a);};
 7     
 8     testBlock();
 9     return 0;
10 }
int main(int argc, const char * argv[]) {
         __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
 
         void (*testBlock)()=((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
 
    ((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
    return 0;
}

5:block預設情況下,儲存在棧中,如果對block進行copy操作,blok會轉移到堆中。

如果block在棧中,block中訪問了外部物件,那麼不會對物件進行retain操作

如果在堆中,block訪問了外部物件,會進行retain操作,計數器加一。進行這個實驗需要先把自動管理計數改為NO

int main(int argc, const char * argv[]) {
    useA *a=[[useA alloc]init];
    
    NSLog(@"========%lu", [a retainCount]);
         void (^testBlock)()=^{
             
             NSLog(@"======%@",a);
    NSLog(@"block=====%lu",[a retainCount]);
         };
    testBlock();
    return 0;
}

下面我們來進行copy操作,再看看結果

int main(int argc, const char * argv[]) {
    useA *a=[[useA alloc]init];
    
    NSLog(@"========%lu", [a retainCount]);
         void (^testBlock)()=^{
             
             NSLog(@"======%@",a);
    NSLog(@"block=====%lu",[a retainCount]);
         };
    Block_copy(testBlock);
    testBlock();
    return 0;
}