導語
不會使用block的iOS程式設計師,不是一個合格的程式設計師
學會了block,你再也不想用繁瑣的代理
block沒有你想象中的那麼難,不要害怕,不要畏懼,勇敢嘗試
筆者入行iOS時已經是ARC的天下,所以這裡只說ARC環境下的使用
什麼是block
block其實就是一個程式碼塊,把你想要執行的程式碼封裝在這個程式碼塊裡,等到需要的時候再去呼叫。那block是OC物件嗎?答案是肯定的

筆者以英語3.9級的水平給大家翻譯下,“block是一個OC物件,這意味著它能被新增到集合,比如NSArray、NSDictionary”
block的定義
- block屬性或變數
格式:返回值型別(^block名稱)(引數列表)
12345678910/*定義屬性,block屬性可以用strong修飾,也可以用copy修飾有小夥伴留言說蘋果官方建議用copy,筆者查了下文件,確實是這樣的,不過筆者未測試出copy與strong的區別,大家喜歡啥就用啥吧*/@property (nonatomic, strong) void(^myBlock)();//無參無返回值@property (nonatomic, strong) void(^myBlock1)(NSString *);//帶引數@property (nonatomic, strong) NSString *(^myBlock2)(NSString *);//帶引數與返回值//定義變數void(^myBlock)() = nil;//無參無返回值void(^myBlock1)(NSString *) = nil;//帶引數NSString *(^myBlock2)(NSString *) = nil;//帶引數與返回值 - block被當做方法的引數
格式:(block型別)引數名稱
123- (void)test:(void(^)())testBlock//無慘無返回值- (void)test1:(void(^)(NSString *))testBlock//帶引數- (void)test2:(NSString *(^)(NSString *))testBlock//帶引數與返回值 - 使用typedef定義block
123456789typedef void(^myBlock)(); //以後就可以使用myBlock定義無參無返回值的blocktypedef void(^myBlock1)(NSString *); //使用myBlock1定義引數型別為NSString的blocktypedef NSString *(^myBlock2)(NSString *); //使用myBlock2定義引數型別為NSString,返回值也為NSString的block//定義屬性@property (nonatomic, strong) myBlock testBlock;//定義變數myBlock testBlock = nil;//當做引數- (void)test:(myBlock)testBlock;
block的賦值
格式:block = ^返回值型別(引數列表){}
- 沒有引數沒有返回值
1234567891011myBlock testBlock = ^void(){NSLog(@"test");};//沒有返回值,void可以省略myBlock testBlock1 = ^(){NSLog(@"test1");};//沒有引數,小括號也可以省略myBlock testBlock2 = ^{NSLog(@"test2");}; - 有引數沒有返回值
1234567myBlock1 testBlock = ^void(NSString *str) {NSLog(str);}//省略voidmyBlock1 testBlock = ^(NSString *str) {NSLog(str);} - 有引數有返回值
123456789myBlock2 testBlock = ^NSString *(NSString *str) {NSLog(str)return @"hi";}//有返回值時也可以省略返回值型別myBlock2 testBlock2 = ^(NSString *str) {NSLog(str)return @"hi";}實戰
接下來,我們就結合一個例項程式,來看看block在實際開發中的簡單使用
本案例涉及到兩個控制器與一個Person類
- 聯絡人列表控制器:使用tableView展示聯絡人列表,稱為A控制器
- 新建聯絡人控制器:建立新的聯絡人物件,稱為B控制器
- Person:聯絡人,有兩個屬性,name與phoneNumber
任務需求:點選A控制器右上角“新建”按鈕跳到B控制器,B控制器新增聯絡人後,點選“儲存”按鈕返回A控制器,並將新新增的聯絡人展示到列表中
問題來了,如何將B控制器中的資料傳遞給A控制器呢?
那還不簡單,A控制器直接把聯絡人陣列傳遞給B控制器,B控制器新建聯絡人後新增到陣列中,然後返回A控制器,在A控制器的viewWillAppear方法中重新整理表格就OK了。
方法可行,但是不得不說,相當low,B控制器是用來新增聯絡人的,至於聯絡人陣列什麼情況,無需關心,所以,不要把陣列傳遞給B控制器
B控制器要做的僅僅只是,新建聯絡人,然後把聯絡人物件傳遞給A控制器,至於A控制器拿到聯絡人後會做什麼,那是A的事情,與B無關
看到這裡,很多人可能已經想到了代理,沒錯,代理也可以實現,但…是…,B控制器定義協議,宣告代理方法,A控制器設定代理,遵守協議,然後實現代理方法,B控制器在合適的地方呼叫代理方法,臥槽,好麻煩有木有,筆者都不想寫程式碼了,還是回家種田去吧
好了不廢話了,進入正題
使用block傳遞資料
- 在B控制器的.h檔案中定義一個沒有返回值,引數型別為Person的block屬性
1@property (nonatomic, strong) void(^saveBlock)(Person *); - 在B控制器“儲存”按鈕的點選方法中呼叫block
123456789101112- (IBAction)save:(id)sender {//使用事先定義好的類方法建立Person物件Person *person = [Person personWithName:_nameText.text phoneNumber:_phoneNumberText.text];/**呼叫block之前最好先判斷block是否為空,不為空才呼叫,否則程式崩潰*///裝逼寫法//!self.saveBlock? : self.saveBlock(person);//一般寫法if (self.saveBlock) {self.seveBlock(person);}[self.navigationController popViewControllerAnimated:YES];} - 在A控制器中,給B控制器的block屬性進行賦值
12345678910//“新建”按鈕點選執行的方法- (void)newContact {AddContactViewController *addVC = [[AddContactViewController alloc] init];addVC.saveBlock = ^(Person *person){//這裡就可以拿到B控制器傳遞過來的person物件,新增到陣列然後重新整理表格[self.contactList addObject:person];[self.tableView reloadData];};[self.navigationController pushViewController:addVC animated:YES];}
三步就搞定,很簡單是不是,所以說,block並沒有你們想想的那麼複雜,自從筆者學會了block,就再也沒用過代理,除了系統的。
block常見雷區—迴圈引用
使用block有一個特別要注意的地方,迴圈引用,何為迴圈引用?你引用我,我引用你,誰也不釋放誰,物件無法銷燬,佔用記憶體
我們來看一個迴圈引用的一個例子

注意看控制檯輸出,當點選“取消”時,B控制器被銷燬,dealloc方法被呼叫
把註釋掉的程式碼開啟,再執行

點選“取消”按鈕,B被移除,但是dealloc方法沒有呼叫,所以說,B控制器並沒有銷燬,why?
block物件賦值給了B控制器的屬性,因此B會對block有一個強引用,而block中又用到了self(B控制器物件),block會對使用到的外部變數進行捕獲,所以,block對B控制器也有一個強引用,最終造成迴圈引用,誰也無法釋放
迴圈引用解決方法
迴圈引用如何解決?很簡單,一行程式碼搞定

使用weakSelf(名稱隨便取的)替代self,block將不再對self進行強引用
圖中__weak也可使用__unsafe_unretained,區別就是__weak修飾的指標,當物件銷燬後,指標會被自動置為nil,而__unsafe_unretained修飾的指標,當物件銷燬後會變成野指標,為了安全,推薦使用__weak
block的分類
block可分為三種
- NSStackBlock:棧block
- NSMallocBlock:堆block
- NSGlobalBlock:全域性block
1. 棧block
特點:生命週期由系統控制,函式返回即銷燬
用到區域性變數、成員屬性變數,且沒有強指標引用的block都是棧block
a.用到區域性變數(圖1),i為區域性變數,block直接在NSLog中列印,沒有被指標引用

b.用到成員屬性變數(圖2),name為成員屬性

2. 堆block
特點:沒有強指標引用即銷燬,生命週期由程式設計師手動管理
棧block如果有強指標引用或copy修飾的成員屬性引用就會被拷貝到堆中,變成堆block
a.強指標引用(圖3),block被testBlock引用,testBlock就是一個block型別的強指標(ARC環境下預設就是強指標)

b.copy修飾的成員屬性引用(圖4)

3. 全域性block
特點:命長,有多長?很長很長,人在塔在(應用程式在它就在)
沒有用到外界變數,或者只用到全域性變數、靜態(static)變數的block就是全域性block
對於全域性block,有沒有指標引用都不影響,block型別的成員屬性無論是用assign、weak、strong還是copy修飾都無所謂,不過開發中很少用到全域性block,所以不要用weak或assign
a.沒有用到外界變數(圖5),下圖中block沒有用到外界變數,所以就算用weak修飾也是全域性block(舉個例子而已,開發中不要用weak,用了也別說是筆者教的)

b.只用到全域性變數、靜態(static)變數(圖6),str為全域性變數,str1為靜態變數,只用到其中一個也是全域性block

分類總結
1.沒有用到外界變數或只用到全域性變數、靜態變數的block為全域性block,生命週期從建立到應用程式結束
2.用到區域性變數、成員屬性變數的block為棧block,生命週期系統控制,函式返回即銷燬
3.有強指標引用或copy修飾的成員屬性引用的block會被複制一份到堆中成為堆block,沒有強指標引用即銷燬,生命週期由程式設計師控制
block對外界變數的捕獲
a.基本資料型別—區域性變數
block會拷貝該變數的值當做常量使用,外界修改變數的值不會影響block內部,且block內部不能對其修改
block內部修改外界變數i的值直接報錯,如果想要修改,可以在int i = 0前面加上關鍵字__block,此時i等效於全域性變數或靜態變數

外界變數i從0變成了1,block內部列印依然是0

b.基本資料型別—靜態變數、全域性變數、成員屬性變數
block直接訪問變數地址,在block內部可以修改變數的值,並且外部變數被修改後,block內部也會跟著變
圖中_k為成員屬性變數,初始值i = 10,j = 20,k = 0,block內部只對i、j、k進行一次自增操作,列印結果卻是i = 12,j = 22,k = 2,所以外部的自增操作也影響了內部,即訪問的是同一個記憶體地址

c.指標型別—區域性變數
block會複製一份指標並強引用指標所指物件,且內部不能修改指標的指向
圖中被註釋掉的程式碼試圖修改指標指向,所以會報錯(如果想要修改,在前面加上__block),但是可以修改所指物件的值,如str從“abc”變成了“abcdef”

d.指標型別—全域性變數、靜態變數、成員變數屬性
block不會複製指標,但是會強引用該物件,內部可修改指標指向,block會強引用成員屬性變數所屬的物件,這也是為什麼block屬性內部用到self.xxx會引起迴圈引用的原因
圖中str2為成員屬性,由於NSString是不可變的,所以從列印結果可以看出,在block內部修改了外界指標變數的引用,指向了新的字串
