Block


Block注意點:


  • 區分聲明和定義
    • 聲明沒有等號
    • 定義有等號

何時用block,何時用delegate


  • 異步和回調用block
  • 比如AFNetwork,MJRefesh
  • 接口很多,用delegate進行解耦。其實用block也行,不過這時候有點麻煩,因為接口很多嘛。
  • 比如UITableViewDelegate
  • 能用代理一定能用block,能用通知一定能用block,所以block完全可以替換代理和通知,即block是萬能的。block雖然簡潔,但不好理解,只要用的多了就好理解了。block其實就是個函數

block和delegate相同點


  • 具體實現都是在代理類中、初始化Block的類中處理的。具體算法都是在算法類中實現的。
  • 比如我想讓A控制器顯示block的代碼塊中的內容,那么我必須把初始化block的代碼塊寫在A控制器中。具體如何執行block中的代碼,由我自己決定,我把block()這句代碼C方法中,那么程序只要執行C方法,就會立刻執行block,A控制就會顯示block代碼塊中的內容

Block核心理解


  • 1.block中的代碼何時執行?只有理解這個,才理解了block
    • 只有執行了類似block();這段代碼,才會真正調用block,才會執行block中保存的代碼。
  • 2.block中保存的代碼在哪里?
  • 只要見到{代碼塊;};或者(參數){代碼塊;}; 這兩行種形式。保存的代碼塊就存在{}里面
  • 1+2的結合你就完全理解了何時執行block,執行哪里的block。

block中的實參和形參


  • 實參: 調用block方法時,方法中的參數就是實參,例如block(10,20)。
  • 形參: 執行block中的代碼塊時,^后面括號中的內容就是形參,例如 ^(int a,int b){代碼塊}
  • 和C語言的形參、實參差不多。

block的應用場景


  • 動畫
  • 多線程
  • 網絡請求回調
  • 集合遍歷
  • 第三方框架多數都使用的是block

block是地址傳遞的理解


  • 通過block();的形式調用block,本質是調用block中保存的代碼。類似c語言函數,例如 eat(),就會調用eat函數;
  • block是指向結構體的指針,也就是說block存的是地址,block是一種特殊的數據類型。注意數據類型的理解。
  • block是地址傳遞不是值傳遞,所以block中{}的內容,內容中的變量如果是 全局變量/用__block修飾/static修飾,滿足三種情況之一,那么外界修改變量的值,就會修改block中對應的變量,因為這三種情況是地址傳值。內容中的變量如果是用普通的局部變量修飾(例如在方法內容僅僅用int修飾),那么外界修改變量對block中的內容無影響,因為是值傳遞。
  • 總結:地址傳遞可以修改以block中{}的內容,值傳遞不可以修改block中{}的內容
情況1:num是全局變量
void test1();

int main(int argc, const char * argv[])
{
        test1();
    return 0;
}

int num = 10;// 全局變量(在方法外部定義的num)
void test1()
{
    void (^block)() = ^{// 定義并初始化block
        // block內部能夠一直引用全局變量
        NSLog(@"----num=%d", num);  // 20
    };
    num = 20;
    block();// 調用Block
}

情況2:用__block修飾age
void test2()
{
    __block int age = 10;
    void (^block)() = ^{// 定義并初始化block
        // block內部能夠一直引用被__block修飾的變量
        NSLog(@"----age=%d", age);// 20
    };
    
    age = 20;
    
    block();// 調用Block
}

情況3:用static修飾的局部變量height
void test3()
{
    static int height = 10;
    void (^block)() = ^{ // 定義并初始化block
        // block內部能夠一直引用被static修飾的變量
        NSLog(@"----height=%d", age);// 20
    };
    
    height = 20;
    
    block();// 調用Block
}

情況4:用int修飾普通的局部變量weight
void test2()
{
    int weight = 10;
    void (^block)() = ^{// 定義并初始化block
        // 普通的局部變量,block內部只會引用它初始的值(block定義那一刻),不能跟蹤它的改變
        NSLog(@"----weight=%d", weight);// 10
    };
    
    age = 20;
    
    block();// 調用Block
}

block相關的內存管理( Block_copy(block變量名) 、 __block )


  • 1.block是存儲在堆中還是棧中?
    • 默認情況下block存儲在棧中,block中訪問了外界的對象p, 不會對對象進行retain操作
    • 如果對block進行一個copy操作,即Block_copy(block變量名), block會轉移到堆中
      • block存儲在堆中, block中訪問了外界的對象p, 那么會對外界的對象進行一次retain(計數器加1)
      • 例:對block進行一個copy操作,block會轉移到堆中,此時,block中訪問了外界的對象p,會retain
Person *p = [[Person alloc] init];// +1
void (^myBlock)() = ^{// 定義并初始化block
        NSLog(@"block retainCount = %lu", [p retainCount]);// 計數器會+1
    };
  Block_copy(myBlock);// 對block進行一次copy操作,那么block就會轉移到堆中
  myBlock();// 調用block
  [p release]; // 2-release一次= 1,所以會造成內存泄露
  • 2.如果在block中訪問了外界的對象p, 一定要給對象加上__block, 只要加上了__block, 哪怕block在堆中, 也不會對外界的對象進行retain。例如:
__block Person *p = [[Person alloc] init];// +1
void (^myBlock)() = ^{// 定義并初始化block
        NSLog(@"block retainCount = %lu", [p retainCount]);// p讓__block修飾了,所以計數器不會+1
    };
  Block_copy(myBlock);// 對block進行一次copy操作,那么block就會轉移到堆中
  myBlock();// 調用block
  [p release]; // 1-release一次= 0,所以不會造成內存泄露


oc代碼如何轉成c/c++的運行時代碼


步驟1:創建一個命令行項目
步驟2:將代碼寫在main.m文件中
步驟3:打開終端
步驟4:cd 到main.m的上一級目錄
步驟5:clang -rewrite-objc main.m
步驟6:open main.cpp

block聲明


// 聲明:無返回值(^block變量名)(形參)
    void(^block)();
    
    void:block中保存的代碼沒有返回值
    (^block):block是一個變量,這個變量里保存著一段代碼 
    ():block保存的代碼沒有形參

block定義的同時并初始化block


  • 等號左邊是block的定義。等號右邊是初始化block,等號左邊+等號右邊一起出現的形式就是block定義的同事并初始化block。當然也可以分開寫,即先定義block,再初始化block
  • 方式一:無返回值((^block變量名)(形參)= ^(形參)
    // 定義并初始化block
    void(^block1)() = ^(){    //不會執行代碼塊中的內容,只有執行block1才會調用block1保存的代碼塊
       // 代碼塊
        NSLog(@"調用block1");
    };
    
    // 調用Block1,這個Block1就是block變量名,然后去執行下Block1保存代碼,就是等號右側的代碼^(){代碼塊}
    block1();

  • 方式二:無返回值+block2的定義中沒有參數(形參),那么等號右側的()可以省略
    //類型:void(^)()
    
    // 定義并初始化block
    void(^block2)() = ^{//不會執行代碼塊中的內容,只有執行block2才會調用block2保存的代碼塊
        // 代碼塊
    };
    // 調用Block2,這個Block2就是block變量名,然后去執行下Block2保存代碼,就是等號右側的代碼^{代碼塊}
    block2();
    

  • 方式三:有返回值+block3的定義中沒有參數(形參)
    // 類型:int(^)()
    
    // 定義并初始化block
    int(^block3)() = ^{//不會執行代碼塊中的內容,只有執行block2才會調用block2保存的代碼塊
        // 代碼塊
        return 1;
    };
    // 調用Block3,這個Block3就是block變量名,然后去執行下Block3保存代碼,就是等號右側的代碼^int{代碼塊}
    block3();

  • 方式四:有返回值+block4的定義中有兩個參數(形參)
    // 類型:int(^)(int,double)
    
    // 定義并初始化block
    int(^block4)(int,double) = ^(int age ,double height){//不會執行代碼塊中的內容,只有執行block2才會調用block2保存的代碼塊
        // 代碼塊
        return 2;
    };
    // 調用Block4,這個Block4就是block變量名,然后去執行下Block4保存代碼,就是等號右側的代碼^(int age ,double height){// 代碼塊 };
    block4();


block作用:


  • 作用:保存一段代碼
  • 類型:int(^)()

Block和typedef具體用法


  • Block方式一
// 定義block+初始化block
int main(int argc, const char * argv[]) {

  int (^sumBlock)(int , int );// 定義block
     sumBlock = ^(int value1, int value2){// 初始化block
        return value1 + value2;// 代碼塊
    };
    NSLog(@"sum = %i", sumBlock(20, 10));// 30  調用block
    
    int (^minusBlock)(int , int); // 定義block
     minusBlock = ^(int value1, int value2){ // 初始化block
        return value1 - value2;// 代碼塊
    };
    NSLog(@"minus = %i", minusBlock(20, 10));//10 調用block
    return 0;

 }

  • Block方式二
// 定義block的同時并初始化block
類比   int a; // 定義a
       a = 10;// 把a初始化為10
       int a = 10;// 定義a的同時,并把a初始化為10

 int main(int argc, const char * argv[]) {
 
   int (^sumBlock)(int , int ) = ^(int value1, int value2){// 定義block的同時并初始化block
        return value1 + value2;// 代碼塊
    };
    NSLog(@"sum = %i", sumBlock(20, 10));// 30 調用block
    
    int (^minusBlock)(int , int) = ^(int value1, int value2){// 定義block的同時并初始化block
        return value1 - value2;// 代碼塊
    };
    NSLog(@"minus = %i", minusBlock(20, 10));// 10 調用block
    return 0;
}
  • block+typedef的方式
    • 注意: 利用typedef給block起別名,block變量的名稱就是別名,也就是類型的名稱,以后可以用calculteBlock這個類型去定義并初始化一個block
typedef int (^calculteBlock)(int , int);

int main(int argc, const char * argv[]) {

// 由于用了typedef,所以此句代碼的含義:用calculteBlock類型定義sumBlock,并初始化sumBlock
calculteBlock sumBlock = ^(int value1, int value2){
    return value1 + value2;// 代碼塊
};
NSLog(@"sum = %i", sumBlock(20, 10));// 30 調用block          

// 由于用了typedef,所以此句代碼的含義:用calculteBlock類型定義sumBlock,并初始化sumBlock    
calculteBlock minusBlock = ^(int value1, int value2){
return value1 - value2;// 代碼塊
};
NSLog(@"minus = %i", minusBlock(20, 10));// 10 調用block
return 0;
}


block實戰演練


#import <Foundation/Foundation.h>

// 如果外界沒有調用goToWork方法,那么goToWork的參數僅僅是定義一個block
// 如果外界調用了goToWork方法,那么goToWork的參數的含義是:定義并初始化block。
// 因為外界調用goToWork方法,必定傳進來一個"初始化block"的代碼
// 我們只需在goToWork方法中調用block();就可以執行block中的代碼,從而輸出block中的內容
void goToWork(void (^workBlock)())
{
    // 參數為void (^workBlock)(),那么外界將初始化的代碼塊作為形參賦值給這個參數時,本質就會變成如下代碼
    /*
     void (^workBlock)() = ^{// 定義并初始化block。 所以下面的workBlock();就會執行block中的內容
     NSLog(@"寫代碼嘍");
     });
     */
    // 總結:定義block 作為goToWork方法的參數時,外界把代碼塊傳遞給這個參數時,那么就變成了定義并初始化block,
    NSLog(@"起床");
    NSLog(@"駕車去上班");
    
    // 不一樣
    workBlock(); // 執行block,就是執行block中的代碼
    
    NSLog(@"駕車回家");
    NSLog(@"吃晚飯");
    NSLog(@"睡覺");
}

int main(int argc, const char * argv[]) {
    
    goToWork(^{
        NSLog(@"認識新同事");
    });
    return 0;
}


創建block的快捷方式:

  // 快捷方式:inline
    <#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
        <#statements#>
    };
  • 若parameterTypes有類型,那么parameters必須是參數類型+值的形式,否則提示錯誤
  
  //例如:
    void(^block4)(int) = ^(int a) {//正確
        
    };
    
     void(^block4)(int) = ^( a ) {//錯誤   a前面必須加上返回值類型
        
    };
    
    void(^block4)() = ^(  ) {//正確   沒有參數類型,那么參數也不需要參數
        
    };
    

循環引用的問題[詳細看JS_OC_JavaScriptCore這個demo]
  • 對象未變為弱指針,控制臺沒有打印-[XXX dealloc],所以控制器沒有被釋放,造成了循環引用

    對象未變為弱指針.gif

  • 對象變為了弱指針,控制臺打印-[XXX dealloc],所以控制器被釋放,解決了循環引用

    對象變為了弱指針.gif

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容