導(dǎo)語(yǔ)
不會(huì)使用block的iOS程序員,不是一個(gè)合格的程序員
學(xué)會(huì)了block也拜,你再也不想用繁瑣的代理
block沒有你想象中的那么難,不要害怕趾痘,不要畏懼慢哈,勇敢嘗試
筆者入行iOS時(shí)已經(jīng)是ARC的天下,所以這里只說ARC環(huán)境下的使用
什么是block
block其實(shí)就是一個(gè)代碼塊永票,把你想要執(zhí)行的代碼封裝在這個(gè)代碼塊里卵贱,等到需要的時(shí)候再去調(diào)用。那block是OC對(duì)象嗎侣集?答案是肯定的
筆者以英語(yǔ)3.9級(jí)的水平給大家翻譯下键俱,“block是一個(gè)OC對(duì)象,這意味著它能被添加到集合世分,比如NSArray方妖、NSDictionary”
block的定義
-
block屬性或變量
格式:返回值類型(^block名稱)(參數(shù)列表)/*定義屬性,block屬性可以用strong修飾罚攀,也可以用copy修飾 有小伙伴留言說蘋果官方建議用copy党觅,筆者查了下文檔, 確實(shí)是這樣的,不過筆者未測(cè)試出copy與strong的區(qū)別斋泄,大家喜歡啥就用啥吧*/ @property (nonatomic, strong) void(^myBlock)();//無參無返回值 @property (nonatomic, strong) void(^myBlock1)(NSString *);//帶參數(shù) @property (nonatomic, strong) NSString *(^myBlock2)(NSString *);//帶參數(shù)與返回值 //定義變量 void(^myBlock)() = nil;//無參無返回值 void(^myBlock1)(NSString *) = nil;//帶參數(shù) NSString *(^myBlock2)(NSString *) = nil;//帶參數(shù)與返回值
-
block被當(dāng)做方法的參數(shù)
格式:(block類型)參數(shù)名稱- (void)test:(void(^)())testBlock//無慘無返回值 - (void)test1:(void(^)(NSString *))testBlock//帶參數(shù) - (void)test2:(NSString *(^)(NSString *))testBlock//帶參數(shù)與返回值
-
使用typedef定義block
typedef void(^myBlock)(); //以后就可以使用myBlock定義無參無返回值的block typedef void(^myBlock1)(NSString *); //使用myBlock1定義參數(shù)類型為NSString的block typedef NSString *(^myBlock2)(NSString *); //使用myBlock2定義參數(shù)類型為NSString杯瞻,返回值也為NSString的block //定義屬性 @property (nonatomic, strong) myBlock testBlock; //定義變量 myBlock testBlock = nil; //當(dāng)做參數(shù) - (void)test:(myBlock)testBlock;
block的賦值
格式:block = ^返回值類型(參數(shù)列表){}
-
沒有參數(shù)沒有返回值
myBlock testBlock = ^void(){ NSLog(@"test"); }; //沒有返回值炫掐,void可以省略 myBlock testBlock1 = ^(){ NSLog(@"test1"); }魁莉; //沒有參數(shù),小括號(hào)也可以省略 myBlock testBlock2 = ^{ NSLog(@"test2"); };
-
有參數(shù)沒有返回值
myBlock1 testBlock = ^void(NSString *str) { NSLog(str); } //省略void myBlock1 testBlock = ^(NSString *str) { NSLog(str); }
-
有參數(shù)有返回值
myBlock2 testBlock = ^NSString *(NSString *str) { NSLog(str) return @"hi"; } //有返回值時(shí)也可以省略返回值類型 myBlock2 testBlock2 = ^(NSString *str) { NSLog(str) return @"hi"; }
實(shí)戰(zhàn)
接下來旗唁,我們就結(jié)合一個(gè)實(shí)例程序畦浓,來看看block在實(shí)際開發(fā)中的簡(jiǎn)單使用
本案例涉及到兩個(gè)控制器與一個(gè)Person類
- 聯(lián)系人列表控制器:使用tableView展示聯(lián)系人列表,稱為A控制器
- 新建聯(lián)系人控制器:創(chuàng)建新的聯(lián)系人對(duì)象检疫,稱為B控制器
- Person:聯(lián)系人讶请,有兩個(gè)屬性,name與phoneNumber
任務(wù)需求:點(diǎn)擊A控制器右上角“新建”按鈕跳到B控制器屎媳,B控制器添加聯(lián)系人后夺溢,點(diǎn)擊“保存”按鈕返回A控制器,并將新添加的聯(lián)系人展示到列表中
問題來了烛谊,如何將B控制器中的數(shù)據(jù)傳遞給A控制器呢风响?
那還不簡(jiǎn)單,A控制器直接把聯(lián)系人數(shù)組傳遞給B控制器丹禀,B控制器新建聯(lián)系人后添加到數(shù)組中状勤,然后返回A控制器,在A控制器的viewWillAppear方法中刷新表格就OK了双泪。
方法可行持搜,但是不得不說,相當(dāng)low攒读,B控制器是用來添加聯(lián)系人的朵诫,至于聯(lián)系人數(shù)組什么情況,無需關(guān)心薄扁,所以剪返,不要把數(shù)組傳遞給B控制器
B控制器要做的僅僅只是,新建聯(lián)系人邓梅,然后把聯(lián)系人對(duì)象傳遞給A控制器脱盲,至于A控制器拿到聯(lián)系人后會(huì)做什么,那是A的事情日缨,與B無關(guān)
看到這里钱反,很多人可能已經(jīng)想到了代理,沒錯(cuò)匣距,代理也可以實(shí)現(xiàn)面哥,但...是...,B控制器定義協(xié)議毅待,聲明代理方法尚卫,A控制器設(shè)置代理,遵守協(xié)議尸红,然后實(shí)現(xiàn)代理方法吱涉,B控制器在合適的地方調(diào)用代理方法刹泄,臥槽,好麻煩有木有怎爵,筆者都不想寫代碼了特石,還是回家種田去吧
好了不廢話了,進(jìn)入正題
使用block傳遞數(shù)據(jù)
-
在B控制器的.h文件中定義一個(gè)沒有返回值鳖链,參數(shù)類型為Person的block屬性
@property (nonatomic, strong) void(^saveBlock)(Person *);
-
在B控制器“保存”按鈕的點(diǎn)擊方法中調(diào)用block
- (IBAction)save:(id)sender { //使用事先定義好的類方法創(chuàng)建Person對(duì)象 Person *person = [Person personWithName:_nameText.text phoneNumber:_phoneNumberText.text]; /**調(diào)用block之前最好先判斷block是否為空姆蘸,不為空才調(diào)用,否則程序崩潰*/ //裝逼寫法 //!self.saveBlock? : self.saveBlock(person); //一般寫法 if (self.saveBlock) { self.saveBlock(person); } [self.navigationController popViewControllerAnimated:YES]; }
-
在A控制器中撒轮,給B控制器的block屬性進(jìn)行賦值
//“新建”按鈕點(diǎn)擊執(zhí)行的方法 - (void)newContact { AddContactViewController *addVC = [[AddContactViewController alloc] init]; addVC.saveBlock = ^(Person *person){ //這里就可以拿到B控制器傳遞過來的person對(duì)象乞旦,添加到數(shù)組然后刷新表格 [self.contactList addObject:person]; [self.tableView reloadData]; }; [self.navigationController pushViewController:addVC animated:YES]; }
三步就搞定贼穆,很簡(jiǎn)單是不是题山,所以說,block并沒有你們想想的那么復(fù)雜故痊,自從筆者學(xué)會(huì)了block顶瞳,就再也沒用過代理,除了系統(tǒng)的愕秫。
block常見雷區(qū)---循環(huán)引用
使用block有一個(gè)特別要注意的地方慨菱,循環(huán)引用,何為循環(huán)引用戴甩?你引用我符喝,我引用你,誰(shuí)也不釋放誰(shuí)甜孤,對(duì)象無法銷毀协饲,占用內(nèi)存
我們來看一個(gè)循環(huán)引用的一個(gè)例子
注意看控制臺(tái)輸出,當(dāng)點(diǎn)擊“取消”時(shí)缴川,B控制器被銷毀茉稠,dealloc方法被調(diào)用
把注釋掉的代碼打開,再運(yùn)行
點(diǎn)擊“取消”按鈕把夸,B被移除而线,但是dealloc方法沒有調(diào)用,所以說恋日,B控制器并沒有銷毀膀篮,why?
block對(duì)象賦值給了B控制器的屬性岂膳,因此B會(huì)對(duì)block有一個(gè)強(qiáng)引用誓竿,而block中又用到了self(B控制器對(duì)象),block會(huì)對(duì)使用到的外部變量進(jìn)行捕獲闷营,所以烤黍,block對(duì)B控制器也有一個(gè)強(qiáng)引用知市,最終造成循環(huán)引用,誰(shuí)也無法釋放
循環(huán)引用解決方法
循環(huán)引用如何解決速蕊?很簡(jiǎn)單嫂丙,一行代碼搞定
使用weakSelf(名稱隨便取的)替代self,block將不再對(duì)self進(jìn)行強(qiáng)引用
圖中__weak也可使用__unsafe_unretained规哲,區(qū)別就是__weak修飾的指針跟啤,當(dāng)對(duì)象銷毀后,指針會(huì)被自動(dòng)置為nil唉锌,而__unsafe_unretained修飾的指針隅肥,當(dāng)對(duì)象銷毀后會(huì)變成野指針,為了安全袄简,推薦使用__weak