美文网首页
Block 介绍及简单使用

Block 介绍及简单使用

作者: Abner_XuanYuan | 来源:发表于2021-05-11 13:58 被阅读0次

多谢两位大佬的分享:(传送入口)
http://www.jianshu.com/p/51d04b7639f1
https://juejin.im/post/5e15a2356fb9a047f5177a80
1 block 本质
block 本质上是一个 OC 对象,它内部也有isa指针,这个对象封装了函数调用地址以及函数调用环境(函数参数、返回值、捕获的外部变量等).

2 block 的底层存储结构 : 定义一个 block 并在 block 里面访问量 block 外面的变量 age.


block 底层存储结构

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

3 Block 的变量捕获机制
Block 外部的变量是可以被捕获的,这样可以在 Block 内部使用捕获的变量.

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

由此可知 Block 对不同类型的变量捕获是不一样的.
3.1 全局变量的捕获 -- 不捕获变量
由于全局变量(包括静态全局变量和普通全局变量)是在全局中都可以被访问到的,所以 Block 是不捕获全局变量的,且在 Block 内部使用的全局变量是最新的更改的值.
3.2 静态局部变量的捕获 -- 捕获变量的地址
Block 对静态局部变量的捕获是以 int *b 的形式存在的,也就是是指针的形式存在的,因此 Block 内部使用的是最新的变量的值,并且 Block 内部对变量进行修改的时候,也会影响到外部改变量的值.
3.3 普通局部变量 -- 值捕获
Block 对普通局部变量的捕获在 Block 结构体中是以 int a 形式存在的,也就是说 Block 对 a 的值的捕获,且在 Block 内部以一个新的变量来存放该值,Block 内部和外部的 a 是两个不同的变量,因此,外部修改此类型的变量时对 Block 内部的此变量是没有影响的.
总结:判断一个变量是否会被 block 捕获关键就在于这个变量是局部变量还是全局变量.
全局变量:不捕获变量,直接使用.
静态局部变量:捕获变量的指针.
普通局部变量:捕获变量的值.

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

小测试
哪个 Block 会捕获 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 是局部变量.
OC 中调用方法,其实就是给这个对象发送消息.比如我调用 [self blockTest] ,它转成 C 语言后就变成了 objc_msgSend(self, @selector(blockTest)).OC 的 blockTest 方法是没有参数的,但是转成 objc_msgSend 后就多出来了2个参数,一个就是 self ,是指向函数调用者的,另一个参数就是要调用的方法.所以对于所有的 OC 方法来说,它们都有这2个默认的参数,第一个参数就是 self ,所以 self 就是这么通过参数的形式传进来的,它的确是一个局部变量.
第三种会被捕获的原因是 _name 其实是 self-> _name.

4 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]);
}

block1的类:__NSGlobalBlock__
block2的类:__NSStackBlock__
block3的类:__NSMallocBlock__

由上可知 block 的类有三种:NSGlobalBlockNSStackBlockNSMallocBlock.
注意:在ARC模式下,如果一个 NSStackBlock 类型的 block 被一个强指针指着,那系统会自动对这个 block 进行一次 copy 操作将这个 block 变成 NSMallocBlock 类型,这样会影响运行的结果.
4.1 NSGlobalBlock
如果一个 block 里面没有访问普通局部变量(也就是说 block 里面没有访问任何外部变量或者访问的是静态局部变量或者访问的是全局变量),那这个 block 就是 NSGlobalBlock . NSGlobalBlock 类型的 block 在内存中是存在数据区的(也叫全局区或静态区,全局变量和静态变量是存在这个区域的).注: NSGlobalBlock 类型的 block 调用 copy 方法的话什么都不会做.
NSGlobalBlock 的继承关系链

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

继承链:__NSGlobalBlock__  :  __NSGlobalBlock : NSBlock : NSObject

4.2 NSStackBlock
如果一个 block 里面访问了普通的局部变量,那它就是一个 NSStackBlock.它在内存中存储在栈区,栈区的特点就是其释放不受开发者控制,都是由系统管理释放操作的,所以在调用 NSStackBlock 类型 block 时要注意,一定要确保它还没被释放.如果对一个 NSStackBlock 类型 block 做 copy 操作,那会将这个 block 从栈复制到堆上.
继承链:NSStackBlock : __NSStackBlock : NSBlock : NSObject
4.3 NSMallocBlock
一个 NSStackBlock 类型 block 做调用 copy,会将这个 block 从栈复制到堆上,堆上的这个 block 类型就是 NSMallocBlock,所以 NSMallocBlock 类型的 block 是存储在堆区.如果对一个 NSMallocBlock 类型 block 做 copy 操作,那这个block的引用计数+1.
继承链:NSMallocBlock : __NSMallocBlock : NSBlock : NSObject

在 ARC 环境下,编译器会在以下情况自动将栈上的 block 复制到堆上.

a. block作为函数返回值时:
typedef void (^MyBlock)(void);
- (MyBlock)createBlock{
    int a = 10;
    return ^{
        NSLog(@"******%d",a);
    };
}

b. 将block赋值给强指针时:
- (void)test{
    int a = 10; // 局部变量

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

c. 当block作为参数传给Cocoa API时:
[UIView animateWithDuration:1.0f animations:^{
        
}];

d. block作为GCD的API的参数时:
dispatch_async(dispatch_get_main_queue(), ^{
        
});

5 Block 对对象型的局部变量的捕获
栈上 Block,不管捕获的对象是强指针还是弱指针,block 内部都不会对这个对象产生强引用.
堆上的 Blcok

// 堆上 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;//注意此处的 ⚠️⚠️⚠️ __strong  ⚠️⚠️⚠️
};

weak 修饰的 Block

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 20;
        __weak Person *weakPerson = person;//注意此处的 ⚠️⚠️⚠️ __weak  ⚠️⚠️⚠️
        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;//注意此处的 ⚠️⚠️⚠️ __weak  ⚠️⚠️⚠️
};

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

6 __block 修饰符

//OC
- (void)test1{
    __block int age = 10;
  //static 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 和外面的 age 是同一个
};

所以 age 用 __block 修饰后它就不再是一个 test1 方法内部的局部变量了,而是被包装成了一个对象.age 就被存储在这个对象中.之所以说是包装成一个对象,是因为 __Block_byref_age_0 这个结构体的第一个成员就是isa指针.
__block 修饰变量的内存管理:
__block 不管是修饰基础数据类型还是对象类型,底层都是将它包装成一个对象(我这里取个名字叫 __blockObj),该对象是一个结构体,该结构体中有个指针指向 __blockObj.既然是一个对象,那 block 内部如何对它进行内存管理呢?
当 block 在栈上时,block 内部并不会对 __blockObj 产生强引用.
当 block 从栈拷贝到堆中时,它同时会将 __blockObj 也拷贝到堆上,并对__blockObj 产生强引用.
当 block 从堆中移除时,会调用 block 内部的 dispose 函数,dispose 函数内部又会调用_Block_object_dispose 函数来释放 __blockObj.

7 简单使用


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 进行传值

7 测试

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.

相关文章

  • Block 介绍及简单使用

    多谢两位大佬的分享:(传送入口)http://www.jianshu.com/p/51d04b7639f1[htt...

  • Block 介绍及使用

    Block :带有自动变量的函数 语法: ^返回值类型(参数列表){函数表达式} 返回值类型可以省略 ^(参数列表...

  • Block介绍及使用

    在实际开发中,使用Block比delegate和notification更简洁。于是在项目中会大量的使用block...

  • block 实用小例子

    本篇是简单介绍iOS 开发中block的使用(附demo): /* 声明一个block (返回类型)(^block...

  • Block的介绍及使用

    前言 这篇随记就是向大家简单介绍下block以及一些简单地用法.我相信对于一些初学者会有一些帮助的. 什么是blo...

  • 老生常谈之Block

    老生常谈之Block 前面有一篇介绍Block的博客,主要介绍了Block的简单使用技巧。这篇博客主要更加深入地了...

  • iOS Block实现原理

    系列文章:iOS Block概念、语法及基本使用iOS Block __block说明符iOS Block存储域及...

  • Block简单介绍

    1:什么是block?block其实就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。...

  • ios block使用

    iOS Block的使用一 .最简单的block使用******使用block的三个步骤:1.定义block变量 ...

  • Block-介绍

    Block简单介绍首先Block是iOS中比较特殊的一种数据类型Block是苹果官方比较推荐使用的数据类型,应用也...

网友评论

      本文标题:Block 介绍及简单使用

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