Block

作者: caiyajie | 来源:发表于2020-04-02 22:32 被阅读0次

    一、block定义

    1. 概念
      block是将函数及其上下文封装起来的对象。
      block也是一个指针,保存的是一段代码块在内存中的空间 (栈内存)
    //blcok定义:后者return_type可省,参数为void参数可省略
    return_type (^blockName)(var_type) = ^return_type (var_type varName) {
        //  statements
    };
    blockName(var);
    
    //利用typedef简化Block的声明:
    typedef return_type (^BlockTypeName)(var_type);
    
    1. block的使用场景
    • 把block保存到对象中,恰当时机的时候才去调用
    - (void)block1
    {
        Person *p = [[Person alloc] init];
        void(^block)() = ^() {
            NSLog(@"执行对象中block");
        };
        p.operation = ^(){
            NSLog(@"执行对象中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当做方法的参数使用,外界不调用,都是方法内部去调用,Block实现交给外界决定.
    - (void)block2
    {
        Person *p = [[Person alloc] init];
        // 传入block给参数的Block赋值
        [p eat:^{
            NSLog(@"吃东西");
        }];
    }
    //Person中eat方法
    - (void)eat:(void (^)())block
    {
        block();
    }
    
    • 把block当做方法的返回值,目的就是为了代替方法.block交给内部实现,外界不需要知道Block怎么实现,只管调用
    - (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使用外部变量,会将外界变量拷贝一份到堆内存(在给block块分配内存空间的时候),调用block之前修改变量,不影响block内的该变量取值。该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二种情况
      静态变量、全局变量:不会对原来的值进行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内修改某局部变量需加__block. MRC 环境下block在使用过程中不会对原来值进行copy,可以直接修改该变量 ,ARC环境下会对原值进行copy,内存地址发生变化。
    block可以直接修改全局和静态变量 ,不会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内都是对(原来指针的copy),也就是有两个不同的指针,指向同一个对象。在block内可以更改对象的属性值,但是不可以更改对象
    使用__block:
    MRC环境block中不会对原来的指针进行copy,所以可以更改属性,也可以更改对象本身 。
    ARC环境则是新增指针地址并赋值给原指针(对原对象的copy?内存地址也发生变化?)。
    指针类型全局和静态变量block内可以直接修改,不会copy值和指针。

    三、关键字__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是否复制了对象
        testBlock();
    }
    
    不使用__weak(另MRC 是没有__weak关键字的)
    //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块内打印出的对象仍不为空,说明block中新增了强指针引用
    使用 __weak p = nil 后person对象为nil ,说明block内新增了弱指针引用,对象释放后 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内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题 。
    一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,
    简单说就是self.block = ^(Type var){
    [self dosomething];
    或者 self.otherVar = XXX;
    或者 _otherVar = ...
    };
    block的这种循环引用会被编译器捕捉到并及时提醒
    解决方法 __weak typeof(self)weakSelf = self;

    • __strong
      __weak 可能产生的问题: weakSelf 指针是没有对象持有权的,那么外部对象被提前释放了怎么办?block内部的执行岂不是会出错 ?这个问题又当如何解决呢? 神奇的 strong 关键字来了, 先看代码
    - (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)
    

    发现 __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 后执行接下来的代码
            
            NSLog(@"2w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
            NSLog(@"2s---%@---%p--%@", strongObj, &strongObj,strongObj.name);
            
        });
        
        sleep(1); //睡眠1秒让异步线程block块执行
        
        p = nil;  //执行过程中将 p 对象置 nil
        NSLog(@"1p---%@---%p--%@", p, &p, p.name);
        NSLog(@"1w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
        
        sleep(4); //异步线程结束后 再打印出 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执行过程中,如果对象用 __strong 修饰 block内部依然会继续强引用它 。__weak修饰的只要有强指针指向,会一直存在。上面的例子是因为下面的代码是后续执行的,所以打印出结果为 nil 。
    block 内部的 __strong 会在执行期间进行强引用操作,保证在 block 内部 strongObj 始终是可用的。既避免了循环引用的问题,又可以在 block 内部持有该变量

    我们平时在使用时,常常先判断 strongObj 是否为空,然后再执行后续代码,如下方式

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           __strong Person *strongObj = weakObj;
           if (strongObj) { 
                //
           }
    });
    
    • copy:block变量定义时为什么用copy关键字
      默认情况下,block是存档在栈中,可能被随时回收,故需要copy操作。这也就是我们在定义block的时候用得时copy (arc 下也可以用strong), 而不是weak等

    block默认存储于栈区,访问外界对象,不会对对象retain。copy会转移至堆,访问外界对象,会对对象retain,使用__block修饰,则不会对对象retain。
    使用copy修饰block是将block转移至堆,而不是copy一份,使用copy保住外界对象,避免使用时对象已释放。copy操作后需要在对象的dealloc下对block进行block_release.

    四、三种类型block

    相关文章

      网友评论

          本文标题:Block

          本文链接:https://www.haomeiwen.com/subject/wqyuaftx.html