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; }