Block

一、block定義

  1. 概念
    block是將函數(shù)及其上下文封裝起來的對象。
    block也是一個指針,保存的是一段代碼塊在內(nèi)存中的空間 (棧內(nèi)存)
//blcok定義:后者return_type可省,參數(shù)為void參數(shù)可省略
return_type (^blockName)(var_type) = ^return_type (var_type varName) {
    //  statements
};
blockName(var);

//利用typedef簡化Block的聲明:
typedef return_type (^BlockTypeName)(var_type);
  1. block的使用場景
  • 把block保存到對象中,恰當(dāng)時機(jī)的時候才去調(diào)用
- (void)block1
{
    Person *p = [[Person alloc] init];
    void(^block)() = ^() {
        NSLog(@"執(zhí)行對象中block");
    };
    p.operation = ^(){
        NSLog(@"執(zhí)行對象中block");
    };
    p.operation = block;
    _p = p;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    _p.operation();
}

// block:ARC使用strong,非ARC使用copy
// block類型:void(^)()
@property (nonatomic, strong) void(^operation)();
  • 把block當(dāng)做方法的參數(shù)使用,外界不調(diào)用,都是方法內(nèi)部去調(diào)用,Block實現(xiàn)交給外界決定.
- (void)block2
{
    Person *p = [[Person alloc] init];
    // 傳入block給參數(shù)的Block賦值
    [p eat:^{
        NSLog(@"吃東西");
    }];
}
//Person中eat方法
- (void)eat:(void (^)())block
{
    block();
}
  • 把block當(dāng)做方法的返回值,目的就是為了代替方法.block交給內(nèi)部實現(xiàn),外界不需要知道Block怎么實現(xiàn),只管調(diào)用
- (void (^)(int))run
{
    return ^(int meter){
        NSLog(@"跑了%d米",meter);
    };
}
- (void)block3
{
    Person *p = [[Person alloc] init];
    p.run(2);
   // void(^run)() = p.run;
   //run();
}

二、block捕獲變量

1. block使用外部變量,會將外界變量拷貝一份到堆內(nèi)存(在給block塊分配內(nèi)存空間的時候),調(diào)用block之前修改變量,不影響block內(nèi)的該變量取值。該block是不允許更改該變量的
- (void)test01
{
    int a = 10;
    void(^block)(int x) = ^(int x){
        NSLog(@"== %d", a);//10, a的地址和外面a不同
       //variable is not assignable (missing __block type specifier )不允許修改a的值
    };
    a = 20;
    block(a);
}
2. __block的作用
  • 基本類型
    局部變量 : 在聲明前加__block,可以直接讀寫該變量,是地址傳遞,會影響外界值。會否拷貝該值分為ARC、MRC二種情況
    靜態(tài)變量、全局變量:不會對原來的值進(jìn)行copy,直接讀寫,地址不變
- (void)test01
{
    //int sum = 5;  //( variable is not assignable (missing __block type specifier ))
    __block int sum = 5;
    NSLog(@"1---%p --- %d", &sum, sum);
    sum = 10;
    void (^sumBlock) (int m, int n) = ^(int m, int n) {
        sum = m + n;
        NSLog(@"2---%p --- %d", &sum, sum);
    };
    sum = 15;
    sumBlock(1, 2);
    NSLog(@"3---%p --- %d", &sum, sum);

    __block NSString *name = @"小明";
    NSLog(@"1---%@---%p", name, &name);
    void (^stringBlock) (void) = ^(void) {
        name = @"小麗";
        NSLog(@"2---%@---%p", name, &name);
    };
    name = @"ls";
    stringBlock();
    NSLog(@"3---%@---%p", name, &name);
}

//ARC 
 //1---0x7ffee12688a8 --- 5
// 2---0x60000118ced8 --- 3
// 3---0x60000118ced8 --- 3
// 1---小明---0x7ffee1268848
// 2---小麗---0x600001f8da68
// 3---小麗---0x600001f8da68

//MRC
//1---0x7ffee0d888a8 --- 5
//2---0x7ffee0d888a8 --- 3
//3---0x7ffee0d888a8 --- 3
//1---小明---0x7ffee0d88848
//2---小麗---0x7ffee0d88848
//3---小麗---0x7ffee0d88848

如果想在block內(nèi)修改某局部變量需加__block. MRC 環(huán)境下block在使用過程中不會對原來值進(jìn)行copy,可以直接修改該變量 ,ARC環(huán)境下會對原值進(jìn)行copy,內(nèi)存地址發(fā)生變化。
block可以直接修改全局和靜態(tài)變量 ,不會copy該變量的值

  • 指針類型的變量
- (void)test02
{
    Person *people = nil;
    people = [[Person alloc] init];
    people.name = @"zhangsan";
    
    NSLog(@"1---%@---%p--%@", people, &people, people.name);
    
    void (^peopleBlock) (void) = ^(void) {
        NSLog(@"2---%@---%p--%@", people, &people, people.name);
        people.name = @"wangwu";
        /*
         people = [[Person alloc] init];
         people.name = @"zhaoliu";
         */
    };
    people.name = @"lisi";
    peopleBlock();
    NSLog(@"3---%@---%p--%@", people, &people, people.name);
}

ARC、MRC不使用__block
//1---<Person: 0x6000021ab3b0>---0x7ffeef9c28a8--zhangsan
//2---<Person: 0x6000021ab3b0>---0x600002de3500--lisi
//3---<Person: 0x6000021ab3b0>---0x7ffeef9c28a8--wangwu

MRC使用__block
//1---<Person: 0x6000027ec760>---0x7ffeeec238a8--zhangsan
//2---<Person: 0x6000027ec760>---0x7ffeeec238a8--lisi
//3---<Person: 0x6000027ec760>---0x7ffeeec238a8--wangwu

ARC使用__block
//1---<Person: 0x6000034d8390>---0x7ffee03568a8--zhangsan
//2---<Person: 0x6000034d8390>---0x600003881018--lisi
//3---<Person: 0x6000034d8390>---0x600003881018--wangwu

不加__block:
MRC 和 ARC block內(nèi)都是對(原來指針的copy),也就是有兩個不同的指針,指向同一個對象。在block內(nèi)可以更改對象的屬性值,但是不可以更改對象
使用__block:
MRC環(huán)境block中不會對原來的指針進(jìn)行copy,所以可以更改屬性,也可以更改對象本身 。
ARC環(huán)境則是新增指針地址并賦值給原指針(對原對象的copy?內(nèi)存地址也發(fā)生變化?)。
指針類型全局和靜態(tài)變量block內(nèi)可以直接修改,不會copy值和指針。

三、關(guān)鍵字__weak, __strong,copy

  • __weak
- (void)test03
{
    Person *p = [[Person alloc]init];
    p.name = @"myObject";
    
    NSLog(@"1---%@---%p--%@", p, &p,p.name);
    __weak  Person *weakObj = p;
    NSLog(@"2---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    
    void(^testBlock)(void) = ^(){
        NSLog(@"3---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    };
    
    testBlock();
    p = nil; // 這邊值nil 用來判斷block是否復(fù)制了對象
    testBlock();
}

不使用__weak(另MRC 是沒有__weak關(guān)鍵字的)
//1---<Person: 0x600000c20d50>---0x7ffee5e148a8--myObject
//2---<Person: 0x600000c20d50>---0x7ffee5e148a0--myObject
//3---<Person: 0x600000c20d50>---0x60000002e240--myObject
//3---<Person: 0x600000c20d50>---0x60000002e240--myObject

使用__weak
//1---<Person: 0x6000037f8d90>---0x7ffeee15d8a8--myObject
//2---<Person: 0x6000037f8d90>---0x7ffeee15d8a0--myObject
//3---<Person: 0x6000037f8d90>---0x600003becb30--myObject
//3---(null)---0x600003becb30--(null)

不使用 __weak, p = nil 后block塊內(nèi)打印出的對象仍不為空,說明block中新增了強指針引用
使用 __weak p = nil 后person對象為nil ,說明block內(nèi)新增了弱指針引用,對象釋放后 weakObj 也不在持有, 并會被置nil 防止野指針報錯。

//Capturing 'self' strongly in this block is likely to lead to a retain cycle
- (void)test04
{
     __weak typeof(self)weakSelf = self;
    self.block = ^(NSString *name){
          NSLog(@"arr:%@", weakSelf);
//          [self weakTest];
        //  p.name = @"haha";
    };
    self.block(@"123");
}

block在copy時都會對block內(nèi)部用到的對象進(jìn)行強引用(ARC)或者retainCount增1(非ARC)。在ARC與非ARC環(huán)境下對block使用不當(dāng)都會引起循環(huán)引用問題 。
一般表現(xiàn)為,某個類將block作為自己的屬性變量,然后該類在block的方法體里面又使用了該類本身,
簡單說就是self.block = ^(Type var){
[self dosomething];
或者 self.otherVar = XXX;
或者 _otherVar = ...
};
block的這種循環(huán)引用會被編譯器捕捉到并及時提醒
解決方法 __weak typeof(self)weakSelf = self;

  • __strong
    __weak 可能產(chǎn)生的問題: weakSelf 指針是沒有對象持有權(quán)的,那么外部對象被提前釋放了怎么辦?block內(nèi)部的執(zhí)行豈不是會出錯 ?這個問題又當(dāng)如何解決呢? 神奇的 strong 關(guān)鍵字來了, 先看代碼
- (void)test04
{
    Person* p = [[Person alloc]init];
    p.name = @"myObject";
    NSLog(@"1---%@---%p--%@", p, &p,p.name);
    
    __weak Person *weakObj = p;
    NSLog(@"2---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    
    void(^testBlock)(void) = ^(){
        __strong Person *strongObj = weakObj;
        NSLog(@"3---%@---%p--%@", strongObj, &strongObj,strongObj.name);
        NSLog(@"w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    };
    testBlock();
    p = nil;
    testBlock();
}

//1---<Person: 0x600001002fb0>---0x7ffeee9d28a8--myObject
//2---<Person: 0x600001002fb0>---0x7ffeee9d28a0--myObject
//3---<Person: 0x600001002fb0>---0x7ffeee9d27c8--myObject
//w---<Person: 0x600001002fb0>---0x600001c48260--myObject
//3---(null)---0x7ffeee9d27c8--(null)
//w---(null)---0x600001c48260--(null)

發(fā)現(xiàn) __strong 修飾的對象仍被置nil 了 怎么回事呢 ?? 接著看.....

- (void)test05
{
    Person* p = [[Person alloc]init];
    p.name = @"myObject";
    NSLog(@"0p---%@---%p--%@", p, &p, p.name);
    __weak Person *weakObj = p;
     NSLog(@"0w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        __strong Person *strongObj = weakObj;
        
        NSLog(@"0s---%@---%p--%@", strongObj, &strongObj,strongObj.name); //先打印這行
        
        sleep(3); // 睡眠三秒確保 p 被置 nil 后執(zhí)行接下來的代碼
        
        NSLog(@"2w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
        NSLog(@"2s---%@---%p--%@", strongObj, &strongObj,strongObj.name);
        
    });
    
    sleep(1); //睡眠1秒讓異步線程block塊執(zhí)行
    
    p = nil;  //執(zhí)行過程中將 p 對象置 nil
    NSLog(@"1p---%@---%p--%@", p, &p, p.name);
    NSLog(@"1w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
    
    sleep(4); //異步線程結(jié)束后 再打印出 person 對象
    
    NSLog(@"3w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
}

//0p---<Person: 0x6000031ac320>---0x7ffee1aa68a8--myObject
//0w---<Person: 0x6000031ac320>---0x7ffee1aa68a0--myObject
//0s---<Person: 0x6000031ac320>---0x70000f8fce08--myObject
//1p---(null)---0x7ffee1aa68a8--(null)
//1w---<Person: 0x6000031ac320>---0x7ffee1aa68a0--myObject
//2w---<Person: 0x6000031ac320>---0x600003de98e0--myObject
//2s---<Person: 0x6000031ac320>---0x70000f8fce08--myObject
//3w---(null)---0x7ffee1aa68a0--(null)

p = nil 后由__strong 修飾的對象仍然存在。在block執(zhí)行過程中,如果對象用 __strong 修飾 block內(nèi)部依然會繼續(xù)強引用它 。__weak修飾的只要有強指針指向,會一直存在。上面的例子是因為下面的代碼是后續(xù)執(zhí)行的,所以打印出結(jié)果為 nil 。
block 內(nèi)部的 __strong 會在執(zhí)行期間進(jìn)行強引用操作,保證在 block 內(nèi)部 strongObj 始終是可用的。既避免了循環(huán)引用的問題,又可以在 block 內(nèi)部持有該變量

我們平時在使用時,常常先判斷 strongObj 是否為空,然后再執(zhí)行后續(xù)代碼,如下方式

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       __strong Person *strongObj = weakObj;
       if (strongObj) { 
            //
       }
});
  • copy:block變量定義時為什么用copy關(guān)鍵字
    默認(rèn)情況下,block是存檔在棧中,可能被隨時回收,故需要copy操作。這也就是我們在定義block的時候用得時copy (arc 下也可以用strong), 而不是weak等

block默認(rèn)存儲于棧區(qū),訪問外界對象,不會對對象retain。copy會轉(zhuǎn)移至堆,訪問外界對象,會對對象retain,使用__block修飾,則不會對對象retain。
使用copy修飾block是將block轉(zhuǎn)移至堆,而不是copy一份,使用copy保住外界對象,避免使用時對象已釋放。copy操作后需要在對象的dealloc下對block進(jìn)行block_release.

四、三種類型block

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容