Block

2020-04-02  本文已影响0人  caiyajie

一、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的使用场景
- (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)();
- (void)block2
{
    Person *p = [[Person alloc] init];
    // 传入block给参数的Block赋值
    [p eat:^{
        NSLog(@"吃东西");
    }];
}
//Person中eat方法
- (void)eat:(void (^)())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的作用
- (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

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

- (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) { 
            //
       }
});

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

四、三种类型block

上一篇下一篇

猜你喜欢

热点阅读