iOS之輕鬆上手block

原文出處: codingZero   歡迎分享原創到伯樂頭條程序員

導語

不會使用block的iOS程序員,不是一個合格的程序員
學會了block,你不再想用繁瑣的代理
block沒有你想象中的那麼難,不要懼怕,不要畏懼,勇敢嘗試
筆者入行iOS時已是ARC的天下,因此這裏只說ARC環境下的使用編程

什麼是block

block其實就是一個代碼塊,把你想要執行的代碼封裝在這個代碼塊裏,等到須要的時候再去調用。那block是OC對象嗎?答案是確定的數組

 

來自官方文檔安全

筆者以英語3.9級的水平給你們翻譯下,「block是一個OC對象,這意味着它能被添加到集合,好比NSArray、NSDictionary」架構

block的定義

  1. block屬性或變量
    格式:返回值類型(^block名稱)(參數列表) 
    函數

    1測試

    2網站

    3atom

    4spa

    5

    6

    7

    8

    9

    10

    /*定義屬性,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;//帶參數與返回值

  2. block被當作方法的參數
    格式:(block類型)參數名稱 

    1

    2

    3

    - (void)test:(void(^)())testBlock//無慘無返回值

    - (void)test1:(void(^)(NSString *))testBlock//帶參數

    - (void)test2:(NSString *(^)(NSString *))testBlock//帶參數與返回值

  3. 使用typedef定義block

    1

    2

    3

    4

    5

    6

    7

    8

    9

    typedef void(^myBlock)(); //之後就可使用myBlock定義無參無返回值的block

    typedef void(^myBlock1)(NSString *); //使用myBlock1定義參數類型爲NSString的block

    typedef NSString *(^myBlock2)(NSString *); //使用myBlock2定義參數類型爲NSString,返回值也爲NSString的block

    //定義屬性

    @property (nonatomic, strong) myBlock testBlock;

    //定義變量

    myBlock testBlock = nil;

    //當作參數

    - (void)test:(myBlock)testBlock;

block的賦值

格式:block = ^返回值類型(參數列表){}

  1. 沒有參數沒有返回值

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    myBlock testBlock = ^void(){

         NSLog(@"test");

    }

    //沒有返回值,void能夠省略

    myBlock testBlock1 = ^(){

         NSLog(@"test1");

    }

    //沒有參數,小括號也能夠省略

    myBlock testBlock2 = ^{

         NSLog(@"test2");

    }

  2. 有參數沒有返回值

    1

    2

    3

    4

    5

    6

    7

    myBlock1 testBlock = ^void(NSString *str) {

          NSLog(str);

    }

    //省略void

    myBlock1 testBlock = ^(NSString *str) {

          NSLog(str);

    }

  3. 有參數有返回值

    1

    2

    3

    4

    5

    6

    7

    8

    9

    myBlock2 testBlock = ^NSString *(NSString *str) {

         NSLog(str)

         return @"hi";

    }

    //有返回值時也能夠省略返回值類型

    myBlock2 testBlock2 = ^(NSString *str) {

         NSLog(str)

         return @"hi";

    }

    實戰

    接下來,咱們就結合一個實例程序,來看看block在實際開發中的簡單使用

     

    本案例涉及到兩個控制器與一個Person類

    1. 聯繫人列表控制器:使用tableView展現聯繫人列表,稱爲A控制器

    2. 新建聯繫人控制器:建立新的聯繫人對象,稱爲B控制器

    3. 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傳遞數據

  1. 在B控制器的.h文件中定義一個沒有返回值,參數類型爲Person的block屬性

    1

    @property (nonatomic, strong) void(^saveBlock)(Person *);

  2. 在B控制器「保存」按鈕的點擊方法中調用block

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    - (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];

    }

  3. 在A控制器中,給B控制器的block屬性進行賦值

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

       //「新建」按鈕點擊執行的方法

       - (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中打印,沒有被指針引用

 

圖1

b.用到成員屬性變量(圖2),name爲成員屬性

 

圖2

2. 堆block

特色:沒有強指針引用即銷燬,生命週期由程序員手動管理
棧block若是有強指針引用或copy修飾的成員屬性引用就會被拷貝到堆中,變成堆block

a.強指針引用(圖3),block被testBlock引用,testBlock就是一個block類型的強指針(ARC環境下默認就是強指針)

 

圖3

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

 

圖4

3. 全局block

特色:命長,有多長?很長很長,人在塔在(應用程序在它就在)
沒有用到外界變量,或者只用到全局變量、靜態(static)變量的block就是全局block

對於全局block,有沒有指針引用都不影響,block類型的成員屬性不管是用assign、weak、strong仍是copy修飾都無所謂,不過開發中不多用到全局block,因此不要用weak或assign

a.沒有用到外界變量(圖5),下圖中block沒有用到外界變量,因此就算用weak修飾也是全局block(舉個例子而已,開發中不要用weak,用了也別說是筆者教的)

 

圖5

b.只用到全局變量、靜態(static)變量(圖6),str爲全局變量,str1爲靜態變量,只用到其中一個也是全局block

 

圖6

分類總結
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內部修改了外界指針變量的引用,指向了新的字符串

 

講到這裏,筆者對block的理解已所有分享給你們,並隨時歡迎各位讀者的補充與糾正

問啊-定製化IT教育平臺,牛人一對一服務,有問必答,開發編程社交頭條 官方網站:www.wenaaa.com


QQ羣290551701 彙集不少互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!

相關文章
相關標籤/搜索