Block 介绍及简单使用

多谢大佬的分享:
//www.greatytc.com/p/51d04b7639f1
https://juejin.im/post/5e15a2356fb9a047f5177a80

1、Block 简介

Block 是将函数及其执行上下文封装起来的结构体对象。该对象封装了函数调用的地址、函数的参数、返回值、捕获的外部变量等。

Block 结构

调用 block 时,实际上就是通过函数指针 FuncPtr 找到封装的函数并将 block 的地址作为参数传给这个函数进行执行。把 block 传给函数是因为函数执行中需要用到的某些数据是存在 block 的结构体中的(比如捕获的外部变量)。如果定义的是带参数的 block,调用 block 时是将 block 地址和 block 的参数一起传给封装好的函数。
主要参数分析
isa:isa 指针,可见它就是一个对象。
FuncPtr:是一个函数指针,也就是底层将 block 中要执行的代码封装成了一个函数,然后用这个指针指向那个函数。
Block_size:block 占用的内存大小。
age:捕获的外部变量 age。block 会捕获外部变量并将其存储在 block 的底层结构体中。

2、Block 变量捕获

1、基本类型变量捕获
int c = 1000; // 全局变量
static int d = 10000; // 静态全局变量

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

        int a = 10; // 局部变量
        static int b = 100; // 静态局部变量
        void (^block)(void) = ^{
             NSLog(@"a = %d",a);
             NSLog(@"b = %d",b);
             NSLog(@"c = %d",c);
             NSLog(@"d = %d",d);
         };
         a = 20;
         b = 200;
         c = 2000;
         d = 20000;
         block();
    }
    return 0;
}

// ***************打印结果***************
2020-01-07 15:08:37.541840+0800 CommandLine[70672:7611766] a = 10
2020-01-07 15:08:37.542168+0800 CommandLine[70672:7611766] b = 200
2020-01-07 15:08:37.542201+0800 CommandLine[70672:7611766] c = 2000
2020-01-07 15:08:37.542222+0800 CommandLine[70672:7611766] d = 20000

//内部源码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  int *b;
};

分析:
1. 全局变量(包括普通全局变量和静态全局变量): Block 不会捕获变量。因为全局变量在任何地方都可以访问。外部更改全局变量的时,Block 内部使用的也是最新值。
2. 静态局部变量:地址捕获。无论 Block 内部还是 Block 外部都可以更改静态局部变量的值。
3. 普通局部变量:值捕获。是在 Block 内部产生了一个新的变量来保存捕获的值,故外部更改不会影响到内部的值。

注:
1、普通局部变量值捕获的原因?
因为普通局部变量在其作用域结束后就会被回收,如果 Block 是在外部调用,则在使用 Block 时会找不到普通局部变量。
2、为什么要捕获静态局部变量?
静态局部变量的作用域只存在其所在的大括号内,出了大括号就不能访问。
2、对象型的局部变量捕获

栈区的 Block:不论捕获的对象是强指针还是弱指针,Block 内部都不会对该对象产生强引用。
堆区的 Block:如下

//强引用下:
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [[Person alloc] init];
        person.age = 20;

        void (^block)(void) = ^{
            NSLog(@"age--- %ld",person.age);
         };
        block();

    }
    return 0;
}
// 底层结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__strong person;
};


//弱引用下:
int main(int argc, const char * argv[]) {
    @autoreleasepool {

        Person *person = [[Person alloc] init];
        person.age = 20;

        __weak Person *weakPerson = person;
        void (^block)(void) = ^{
            NSLog(@"age--- %ld",weakPerson.age);
         };
        block();

    }
    return 0;
}

// 底层block
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  Person *__weak weakPerson;
};

结果:
强引用下的对象,在 Block 中捕获的时候产生了一个 __strong 修饰词。
弱引用下的对象,在 Block 中捕获的时候产生了一个 __weak 修饰词。

分析:
1、当 block 被拷贝到堆上时是调用的 copy 函数,copy 函数内部会调用 _Block_object_assign 函数,该函数就会根据关键字来进行操作。
2、如果关键字是 __strong,那 block 内部就会对这个对象进行一次 retain 操作,引用计数+1,也就是 block 会强引用这个对象。也正是这个原因,导致在使用 block 时很容易造成循环引用。
3、如果关键字是 __weak 或 __unsafe_unretained,那 block 对这个对象是弱引用,不会造成循环引用。所以我们通常在 block 外面定义一个 __weak 或 __unsafe_unretained 修饰的弱指针指向对象,然后在 block 内部使用这个弱指针来解决循环引用的问题。
4、block 从堆上移除时,则会调用 block 内部的 dispose 函数,dispose 函数内部调用 _Block_object_dispose 函数会自动释放强引用的变量。

3、self 局部变量捕获
- (void)blockTest{
    
    // 第一种
    void (^block1)(void) = ^{
        NSLog(@"%p",self);
    };
    
    // 第二种
    void (^block2)(void) = ^{
        self.name = @"Jack";
    };
    
    // 第三种
    void (^block3)(void) = ^{
        NSLog(@"%@",_name);
    };
    
    // 第四种
    void (^block4)(void) = ^{
        [self name];
    };
}
分析:
以上四种都会捕获 self。第一、二、四都是显式 self,第三种其实是 self->_name 的形式,故也会捕获 self。

⚠️⚠️⚠️  self 是局部变量!!!  ⚠️⚠️⚠️
原因:
OC 中一个对象调用方法,其实就是给这个对象发送消息。比如 [self blockTest],其实是 objc_msgSend(self, @selector(blockTest))。转成 objc_msgSend 后有2个参数,一个是 self(函数调用者),另一个参数就是要调用的方法。对于所有的 OC 方法来说,它们都有这2个默认的参数,第一个参数就是 self,所以 self 就是这么通过参数的形式传进来的,它的确是一个局部变量。

问题:
1、普通局部变量为什么不可以和静态局部变量一样,捕获变量的指针?
答:普通局部变量在作用域结束后就会被释放掉,而如果 Block 是在函数外面调用,这时候该局部变量已经不存在,会抛出异常。而静态局部变量的生命周期是整个程序的生命周期,因此不会存在该问题。
2、既然静态局部变量的生命周期是整个程序的周期,那么为什么还要捕获它?
答:静态局部变量虽然生命周期还在,但是静态局部变量的作用域只在它作用域内,超过该作用域是不能访问的到的。Block 里面的代码在底层是被封装在一个函数中的,该函数肯定是在该静态局部变量是作用域外的,所以一定要在 Block 内部捕获静态局部变量的。

3、Block 的类型

- (void)test{
    int age = 10;
    
    void (^block1)(void) = ^{
        NSLog(@"-----");
    };
    NSLog(@"block1的类:%@",[block1 class]);
    
    NSLog(@"block2的类:%@",[^{
        NSLog(@"----%d",age);
    } class]);
    
   NSLog(@"block3的类:%@",[[^{
       NSLog(@"----%d",age);
   } copy] class]);
}

打印结果:
2020-01-08 09:07:46.253895+0800 AppTest[72445:7921459] block1的类:__NSGlobalBlock__
2020-01-08 09:07:46.254027+0800 AppTest[72445:7921459] block2的类:__NSStackBlock__
2020-01-08 09:07:46.254145+0800 AppTest[72445:7921459] block3的类:__NSMallocBlock__

由上可知 Block 有 __NSGlobalBlock__、__NSStackBlock__、__NSMallocBlock__ 三种类型。

注:
block2 和 block3 不像 block1 那样先定义一个 block 然后再打印 block。这是因为在 ARC 模式下,如果一个 NSStackBlock 类型的 block 被一个强指针指向,那系统会自动对这个 block 进行一次 copy 操作将这个 block 变成 NSMallocBlock 类型,这样会影响运行的结果。

1、NSGlobalBlock

若 Block 里面没有访问普通局部变量,则该 Block 是 NSGlobalBlock
特点:
1、该类型的 Block 存放在全局/静态区。
2、该类型的 Block 调用 copy 的时候什么都不会做。
3、该类型的 Block 的继承关系为:NSGlobalBlock --> __NSGlobalBlock --> NSBlock --> NSObject。

- (void)test{
    void (^block)(void) = ^{
        NSLog(@"-----");
    };
    NSLog(@"--- %@",[block class]);
    NSLog(@"--- %@",[[block class] superclass]);
    NSLog(@"--- %@",[[[block class] superclass] superclass]);
    NSLog(@"--- %@",[[[[block class] superclass] superclass] superclass]);
}

结果:
2020-01-08 11:03:34.331652+0800 AppTest[72667:7957820] --- __NSGlobalBlock__
2020-01-08 11:03:34.331777+0800 AppTest[72667:7957820] --- __NSGlobalBlock
2020-01-08 11:03:34.331883+0800 AppTest[72667:7957820] --- NSBlock
2020-01-08 11:03:34.331950+0800 AppTest[72667:7957820] --- NSObject
2、NSStackBlock

如果一个 Block 访问了外部的普通局部变量,则该 Block 是一个 NSStackBlock
特点:
1、存放在栈区。
2、该类型的 Block 使用 copy 的时候 Block 会由栈区赋值到堆区。
3、继承链:NSStackBlock --> __NSStackBlock --> NSBlock --> NSObject。

3、NSMallocBlock

对一个 NSStackBlock 类型的 Block 做 copy 的时候,会将 Block 从栈区复制到堆区即 NSMallocBlock
特点:
1、该类型的 Block 存放在堆区。
2、该类型的 Block copy 时会使引用计数+1。

ARC 时在以下情况下,会将栈区的 Block 复制到堆区。

1. Block 为返回值
typedef void (^MyBlock)(void);

- (MyBlock)createBlock{
    int a = 10;
    return ^{
        NSLog(@"******%d",a);
    };
}

2. Block 赋值给强指针
- (void)test{
    int a = 10; // 局部变量

    void (^myBlock)(void) = ^{
        NSLog(@"a = %d",a);
    };
    block();
}

3. 作为参数
[UIView animateWithDuration:1.0f animations:^{
        
}];
dispatch_async(dispatch_get_main_queue(), ^{
        
});

注:
在 MRC 时修饰 Block 建议使用 copy,可以将 Block 从栈区复制到堆区。
在 ARC 时修饰 Block 使用 copy 或者 strong 都可以,他们都会将 Block 从栈区复制到堆区。

4、Block 的 Copy
Block 的 Copy

1、栈上 block 变量的 copy


原理简介

栈区 block 的 copy 后会在堆区产生一个相同的副本,栈区的 block 在作用域结束之后会被销毁,但是堆区的 copy 副本并不会被销毁,在 MRC 下会产生内存泄露。
2、栈上 __block 变量的 copy


原理简介

--> 栈上 __block 变量未进行 copy 的时候,__forwarding 指针指向栈上自己的变量。
--> 栈上 __block 变量进行 copy 之后,__forwarding 指针指向堆上 copy 的副本的 __block 变量。
--> 堆上副本的 __forwarding 指针指向副本上自己的 __block 变量。

__forwarding 指针作用:无论在任何内存位置,都可以通过 __forwarding 指针顺利的访问同一个 __block 变量。
--> 若没有对 __block 变量进行 copy ,可以通过 __forwarding 指针访问栈上的 __block 变量;
--> 若对 __block 变量进行 copy ,可以通过 __forwarding 指针访问堆上 copy 副本的 __block 变量。

4、__Block

1、__block 源码及作用
- (void)test1{
    __block int age = 10;
    void (^block)(void) = ^{
        age = 20;
    };
    block();
    NSLog(@"%d",age);
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_age_0 *age; // by ref
};

//源码
struct __Block_byref_age_0 {
  void *__isa; // isa指针
__Block_byref_age_0 *__forwarding; // 如果这block是在堆上那么这个指针就是指向它自己,如果这个block是在栈上,那这个指针是指向它拷贝到堆上后的那个block
 int __flags;
 int __size; // 结构体大小
 int age; // 真正捕获到的age
};
__Block 原理及作用
2、__block 修饰变量的内存管理
  1. __block 修饰的类型,底层都是将它包装成一个对象,然后 block 结构体中有个指针指向该对象。
  2. 当 block 在栈上时,block 内部并不会对该对象产生强引用。
  3. 当 block 调用 copy 函数从栈拷贝到堆中时,它同时会将该对象拷贝到堆上并对该对象产生强引用。
  4. 当 block 从堆中移除时,会调用 block 内部的 dispose 函数,dispose 函数内部又会调用 _Block_object_dispose 函数来释放该对象。
3、__block 使用特点

对局部变量(包括基本数据类型和对象类型)进行赋值的时候,需要使用 __block;对静态局部变量、全局变量、静态全局变量进行赋值操作的时候,不需要使用 __block。

4、示例

__block 使用示例一

__block 使用示例二

结果:
示例一中的 array 不需要添加 __block,示例二中的 array 需要添加 __block。
分析:
一般情况下,对被截获变量进行赋值操作的时候需要使用 __block,在使用的时时候不需要添加 __block。

5、Block 简单使用

Block-- 局部.png

全局变量使用


Block 全局01.png

Block 全局02.png

Block 全局03.png

作为函数参数
A 控制器


Block 作为函数参数--调用.png

B 控制器


Block 作为函数参数--定义.png

Block 作为函数参数--赋值.png

说明:进入 B 的时候就对 A 中进行传值

传值


Block 传值--使用.png

Block 传值--定义.png

Block 传值--赋值.png

说明:在 B 返回到 A 的时候才对 A 进行传值

示例

1、block 的原理是怎样的?本质是什么?

block 本质上也是一个 OC 结构体对象,它内部也有个 isa 指针,block 是封装了函数调用以及函数调用环境的 OC 对象。

2、__block 的作用是什么?有什么使用注意点?

__block 可以用于解决 block 内部无法修改 auto 变量值的问题,编译器会将 __block 变量包装成一个对象。
__block 不能修饰全局变量、静态变量(static)。

3、__block 的内存管理

1、当 block 在栈上时,并不会对 __block 变量产生强引用;当 block 被 copy 到堆时,会调用 block 内部的 copy 函数,copy 函数内部会调用 _Block_object_assign 函数,_Block_object_assign 函数会对 __block 变量形成强引用(retain)。
2、当 block 从堆中移除时,会调用 block 内部的 dispose 函数,dispose 函数内部会调用 _Block_object_dispose 函数,_Block_object_dispose 函数会自动释放引用的 __block 变量(release)。

4、block 的属性修饰词为什么是 copy?使用 block 有哪些使用注意?

block 一旦没有进行 copy 操作,就不会在堆上。
使用注意:循环引用问题

5、block 在修改 NSMutableArray,需不需要添加 __block?

不需要,在使用指针的时候不需要使用 __block 修饰,在修改指针的时候才需要添加 __block。

代码
1 下面代码运行结果是什么?

int d = 1000; // 全局变量
static int e = 10000; // 静态全局变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10; // 局部变量
        static int b = 100; // 静态局部变量
        __block int c = 1000;
        void (^block)(void) = ^{
             NSLog(@"a = %d",a);
             NSLog(@"b = %d",b);
             NSLog(@"c = %d",c);
             NSLog(@"d = %d",d);
             NSLog(@"e = %d",e);
         };
         a = 20;
         b = 200;
         c = 2000;
         d = 20000;
         e = 200000;
         block();
    }
    return 0;
}

// ***************打印结果***************
2020-01-08 08:50:54.621532+0800 CommandLine[72269:7909757] a = 10
2020-01-08 08:50:54.621871+0800 CommandLine[72269:7909757] b = 200
2020-01-08 08:50:54.621912+0800 CommandLine[72269:7909757] c = 2000
2020-01-08 08:50:54.621969+0800 CommandLine[72269:7909757] d = 20000
2020-01-08 08:50:54.621994+0800 CommandLine[72269:7909757] e = 200000

解析:
> block 在捕获普通的局部变量时是捕获的 a 的值,后面无论怎么修改 a 的值都不会影响 block 之前捕获到的值.
> block 在捕获静态局部变量时是捕获的 b 的地址,block 里面是通过地址找到 b 并获取它的值.
> __block 将外部变量包装成了一个对象并将 c 存在这个对象中,此时 block 外面 c 的地址也指向这个对象中存储的 c . block 底层有一个指针指向这个对象的从而获取到 c 的值.
> 全局变量在哪里都可以访问,block 并不会捕获全局变量,所以无论哪里更改 d 和 e,block 里面获取到的都是最新的值.
2 下面代码能正常编译吗?不能的话是那些代码不能通过编译呢?

int d = 1000; // 全局变量
static int e = 10000; // 静态全局变量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int a = 10; // 局部变量
        static int b = 100; // 静态局部变量
        __block int c = 1000;
        NSMutableArray *array1 = nil;
        __block NSMutableArray *array2 = nil;
        void (^block)(void) = ^{
            a = 20;
            b = 200;
            c = 2000;
            d = 20000;
            e = 200000;
            array1 = [NSMutableArray array];
            [array1 addObject:@"111"];
            array2 = [NSMutableArray array];
            [array2 addObject:@"222"];
         };
         
         block();
    }
    return 0;
}

解析
> a = 20;无法通过编译.因为 a 是局部变量,其作用域和生命周期仅限于它所在的大括号内部,而block 底层是将块中的代码封装到了一个函数中,在那个函数中修改 a 就相当于在一个函数中去修改另外一个函数中的局部变量.
> array1 = [NSMutableArray array];无法通过编译.array1 是一个指针,这里是想在一个函数中去给另外一个函数中的变量重新赋值.
> [array1 addObject:@"111"];可以通过编译.是因为 block 捕获了array1 的值(也就是数组的地址)存储在 block 里面,通过地址找到数组,然后对数组中的元素进行操作.对于一个对象类型的变量,block 内部只要不是想修改这个变量的值,都不需要用 __block 来修饰这个变量(比如增、删、改集合类型对象里面的元素或者修改一个实例对象的属性等都不需要用 __block 修饰).
3 下面代码运行结果是什么?

- (void)test{
    __block Person *person = [[Person alloc] init];
    person.age = 20;
    __weak Person *weakPerson = person;
    self.block = ^{
        NSLog(@"block-begin:%@ age = %d",[NSThread currentThread],weakPerson.age);
        [NSThread sleepForTimeInterval:1.0f];//休眠时间长
        NSLog(@"block-eng:%@ age = %d",[NSThread currentThread],weakPerson.age);// weakPerson 已经不存在
    };
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        self.block();
    });
    [NSThread sleepForTimeInterval:0.2f];//休眠时间短
    NSLog(@"test-end:%@",[NSThread currentThread]);
}

// ***************打印结果***************
block-begin:<NSThread: 0x600001b1aa40>{number = 6, name = (null)} age = 20
test-end:<NSThread: 0x600001b7ee40>{number = 1, name = main}
block-eng:<NSThread: 0x600001b1aa40>{number = 6, name = (null)} age = 0

解析
> 休眠 0.2s 后 test 函数运行结束, weakPerson 被释放.在 1.0s 时 weakPerson 对象已不存在.
4 下面代码运行结果是什么?

- (void)test{
    __block Person *person = [[Person alloc] init];
    person.age = 20;
    __weak Person *weakPerson = person;
    self.block = ^{
        __strong Person *strongPerson = weakPerson;
        NSLog(@"block-begin:%@ age = %d",[NSThread currentThread],strongPerson.age);
        [NSThread sleepForTimeInterval:1.0f];
        NSLog(@"block-eng:%@ age = %d",[NSThread currentThread],strongPerson.age);
    };
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        self.block();
    });
    [NSThread sleepForTimeInterval:0.2f];
    NSLog(@"test-end:%@",[NSThread currentThread]);
}

// ***************打印结果***************
block-begin:<NSThread: 0x6000010b2a00>{number = 4, name = (null)} age = 20
test-end:<NSThread: 0x6000010a8680>{number = 1, name = main}
block-eng:<NSThread: 0x6000010b2a00>{number = 4, name = (null)} age = 20

解析
> 在 block 内部定义了一个 __strong 修饰的 strongPerson.__strong 的作用就是保证 block 代码块在执行的过程中,它所修饰的对象不会被释放,即便 block 外面已经没有任何强指针指向这个对象了,这个对象也不会立马释放,而是等到 block 执行结束后再释放.所以在实际开发过程中 __weak 和 __strong 最好是一起使用,避免出现 block 运行过程中其弱引用的对象被释放.
> 注意 __strong 只是保证在 block 运行过程中弱引用对象不被释放.
5 下面代码运行结果是什么?

- (void)test{
    __block Person *person = [[Person alloc] init];
    person.age = 20;
    __weak Person *weakPerson = person;
    self.block = ^{
        __strong Person *strongPerson = weakPerson;
        NSLog(@"block-begin:%@ age = %d",[NSThread currentThread],strongPerson.age);
        [NSThread sleepForTimeInterval:1.0f];
        NSLog(@"block-eng:%@ age = %d",[NSThread currentThread],strongPerson.age);
    };
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self test1];
    });
    NSLog(@"test - end");
}
- (void)test1{//此函数会延迟 0.1s 执行
    self.block();
}

// ***************打印结果***************
test - end
block-begin:<NSThread: 0x6000038b9bc0>{number = 5, name = (null)} age = 0
block-eng:<NSThread: 0x6000038b9bc0>{number = 5, name = (null)} age = 0

解析
在并发队列中通过异步函数添加任务执行 test1,是开启一个新线程来执行,而新线程是先睡眠0.1秒再执行 test1,等到开始执行 test1 时,test 已经执行结束,所以在执行 block 之前 person 就已经被释放了,这种情况下 __strong 修饰符是不起作用的.

并发队列换成串行队列
dispatch_async(dispatch_queue_create("testQueue", DISPATCH_QUEUE_SERIAL), ^{
        [NSThread sleepForTimeInterval:0.1f];
        [self test1];
    });

// ***************打印结果***************
test - end
block-begin:<NSThread: 0x6000022ce400>{number = 5, name = (null)} age = 0
block-eng:<NSThread: 0x6000022ce400>{number = 5, name = (null)} age = 0
只要是异步函数就行,异步函数不会阻塞当前线程,所以执行 test1 时 test 已经执行完了.

同步函数
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:0.1f];
        [self test1];
    });
    
// ***************打印结果***************
block-begin:<NSThread: 0x600000cf5040>{number = 1, name = main} age = 20
block-eng:<NSThread: 0x600000cf5040>{number = 1, name = main} age = 20
test - end
同步函数会阻塞当前线程,所以是等 test1 执行结束后,test 才会继续执行后面的代码.
6 下面代码运行结果是什么?

- (void)test{
    __block Person *person = [[Person alloc] init];
    person.age = 20;
    __weak Person *weakPerson = person;
    self.block = ^{
        __strong Person *strongPerson = weakPerson;
        NSLog(@"block-begin:%@ age = %d",[NSThread currentThread],strongPerson.age);
        [NSThread sleepForTimeInterval:1.0f];
        NSLog(@"block-eng:%@ age = %d",[NSThread currentThread],strongPerson.age);
    };
    [self performSelector:@selector(test1) withObject:nil afterDelay:.0f];
    NSLog(@"test - end");
}
- (void)test1{
    self.block();
}

// ***************打印结果***************
test - end
block-begin:<NSThread: 0x600002e1ad00>{number = 1, name = main} age = 0
block-eng:<NSThread: 0x600002e1ad00>{number = 1, name = main} age = 0

解析
performSelector:withObject:afterDelay 这个方法底层实现实际上是将一个定时器添加到了runloop 中,然后等时间到了后就执行 test1 方法.虽然这里最后一个参数传的是0,也就是等待0秒后执行test1,但它并不是立马执行,因为需要先唤醒 runloop,这是要耗一定时间的,所以会先执行后面的方法.所以等到开始执行 test1 时 test 已经执行结束了,person已经释放了.
7 下面代码是否会造成循环引用:

- (void)test{
    self.age = 20;
    self.block = ^{
      NSLog(@"%d",self.age);
    };
}
> 会造成循环引用

- (void)test1{
    self.age = 20;
    self.block = ^{
      NSLog(@"%d",_age);
    };
    self.block();
}
> 会造成循环引用.实际上 _age 完整写法是self->_age.

- (void)test{
    self.block = ^{
        [self setAge:10];
    };
    self.block();
}
> 会造成循环引用.

dispatch_sync(dispatch_get_global_queue(0, 0), ^{
       NSLog(@"%d",self.age);
});
[UIView animateWithDuration:1.0f animations:^{
       NSLog(@"%d",self.age);
}];
> 不会造成循环引用.当 block 是某个函数的参数时,虽然 block 内部对 self 是强引用,但 self 并不持有 block.

- (void)test1{
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        self.block = ^{
            NSLog(@"%d",self.age);
        };
        self.block();
    });
}
> 会造成循环引用.嵌套 block,虽然外层 block 不会循环引用,但里面的 block 会造成循环引用.

typedef void(^MyBlock)(void);
- (void)test{
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        self.age = [self testWithBlock:^{
           NSLog(@"%d",self.age);
        }];
    });
}
- (int)testWithBlock:(MyBlock)myBlock{
    myBlock();
    return 10;
}
> 不会造成循环引用.外层 block 并没有被 self 持有,所以不会造成循环引用.里面的 block 是一个函数的参数,self 并不持有它.

- (void)test1{
    self.block = [self blockWithBlock:^{
       NSLog(@"%d",self.age);
    }];
    self.block();
}
- (MyBlock)blockWithBlock:(MyBlock)myBlock{
    myBlock();  
    return ^{
      NSLog(@"block作为返回值");
    };
}
> 不会造成循环引用.这里有2个不同的 block,self 持有的是一个 block,而强引用 self 的是另外一个 block.

- (void)test1{
    self.block = [self blockWithBlock:^{
       NSLog(@"作为参数的block");
    }];
    self.block();
}
- (MyBlock)blockWithBlock:(MyBlock)myBlock{
    myBlock();
    return ^{
      NSLog(@"block作为返回值--%d",_age);
    };
}
> 会造成循环引用.第二个函数 return 的是一个里面强引用了 self 的 block,且将这个 block 赋值给 self.block.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 229,517评论 6 539
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,087评论 3 423
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 177,521评论 0 382
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,493评论 1 316
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,207评论 6 410
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 55,603评论 1 325
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 43,624评论 3 444
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,813评论 0 289
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,364评论 1 335
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,110评论 3 356
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,305评论 1 371
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,874评论 5 362
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,532评论 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,953评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,209评论 1 291
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,033评论 3 396
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,268评论 2 375

推荐阅读更多精彩内容