iOS原理(五)----block

2018-12-27  本文已影响0人  会笑的Even

iOS原理(五)----block

block的本质

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

下面是简单的一个block代码:

        typedef  void(^MyBlock)(void);
        MyBlock block = ^{
            NSLog(@"---block---");
        };
        block();

查看其编译生成的c++代码如下:

typedef void(*MyBlock)(void);
        MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

// block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_g6_f7y9s65s36vcgjz8dy2t0yr00000gn_T_main_6a9ca9_mi_0);
}

// block结构体
struct __main_block_impl_0 {
    // block实现
  struct __block_impl impl;
    // block描述信息
  struct __main_block_desc_0* Desc;
    // 构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// block实现结构体
struct __block_impl {
    // isa指针,指向block类型
  void *isa;
  int Flags;
  int Reserved;
    // block执行逻辑的函数指针
  void *FuncPtr;
};

// block描述信息
static struct __main_block_desc_0 {
  size_t reserved;
    // block的大小
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

从上面的源码可以看出:block其实就是一个带isa指针的结构体,结构体有两个成员变量:block实现的struct __block_impl impl和block信息的struct __main_block_desc_0* Desc,其中impl也是一个结构体,其成员变量有isa指针void *isa,int Flags,int Reserved和函数实现的函数指针oid *FuncPtr.Desc也是一个结构体,含有两个成员变量size_t reserved和block大小的size_t Block_size,所有block的结构如下:

Snip20181112_17.png

block对变量的捕获

1.对局部auto变量的捕获

代码如下:

        typedef  void(^MyBlock)(void);
        int age = 10;
        MyBlock block = ^{
            NSLog(@"---block---%d",age);
        };
        block();

c++编译代码如下:

Snip20181112_18.png Snip20181112_19.png Snip20181112_20.png

可以出__main_block_impl_0结果体多了个age成员变量,且以值传递的方式.

2.对局部非auto变量的捕获

代码如下:

        typedef  void(^MyBlock)(void);
        static int age = 10;
        MyBlock block = ^{
            NSLog(@"---block---%d",age);
        };
        block();

c++编译代码如下:

Snip20181112_21.png Snip20181112_22.png Snip20181112_23.png

可以出__main_block_impl_0结果体多了个*age成员变量,且以指针传递的方式.

3.对全局变量的捕获

代码如下:

int age = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        typedef  void(^MyBlock)(void);
       
        MyBlock block = ^{
            NSLog(@"---block---%d",age);
        };
        block();
    }
    return 0;
}

c++编译代码如下:

Snip20181112_25.png

可以出__main_block_impl_0并没有对全局变量age捕获,这是因为既然age是全局变量,也没有必要去捕获它,__main_block_func_0()函数能直接访问全局变量age.

block的类型

block类型分为三种:全局类型的_NSConcreteGlobalBlock,栈类型的_NSConcreteStackBlock和堆类型的_NSConcreteMallocBlock,即他们分别在内存中的数据区,堆区和栈区.

Snip20181112_26.png

1._NSConcreteGlobalBlock

没有访问auto类型变量的block即为_NSConcreteGlobalBlock.代码如下:

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

打印如下:

Snip20181112_27.png

可以出此时的类型是_NSConcreteGlobalBlock,继承于NSBlockNSObject类型.

2._NSConcreteStackBlock

访问了auto类型的block即为_NSConcreteStackBlock.代码如下:

        typedef  void(^MyBlock)(void);
        int age = 10;
        MyBlock block = ^{
            NSLog(@"---block---%d",age);
        };
        block();
        NSLog(@"%@",[block class]);
        NSLog(@"%@",[[block class] superclass]);
        NSLog(@"%@",[[[block class] superclass] superclass]);
        NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);

打印如下:

Snip20181112_29.png

可以出此时的类型是_NSConcreteStackBlock,继承于NSBlockNSObject类型.

3._NSConcreteMallocBlock

_NSConcreteStackBlock类型的block进行copy后类型即为_NSConcreteMallocBlock,代码如下:

        typedef  void(^MyBlock)(void);
        int age = 10;
        MyBlock block = [^{
            NSLog(@"---block---%d",age);
        } copy];
        block();
        NSLog(@"%@",[block class]);
        NSLog(@"%@",[[block class] superclass]);
        NSLog(@"%@",[[[block class] superclass] superclass]);
        NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);

打印如下:

Snip20181112_31.png

可以出此时的类型是_NSConcreteMallocBlock,继承于NSBlockNSObject类型.

block的copy

1.如果对_NSConcreteGlobalBlock类型的进行copy,类型还是_NSConcreteGlobalBlock,代码如下:

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

打印如下:

Snip20181112_32.png

2.如果已经在堆上的_NSConcreteMallocBlock类型的block进行copy,该block的应用计数会加1.

3.如果在栈上的_NSConcreteStackBlock类型的block进行copy,该类型为_NSConcreteMallocBlock.

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

5 MRC下block属性的建议写法:@property (copy, nonatomic) void (^block)(void);

6 ARC下block属性的建议写法:@property (strong, nonatomic) void (^block)(void);@property (copy, nonatomic) void (^block)(void);都可以.

auto类型的对象变量

当block内部访问了外部auto类型的对象变量时:

1.当block在栈上时:将不会对auto变量产生强引用.

2.当block被拷贝到堆上:会调用block内部的copy函数, copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用.

3.当block从堆上移除:会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数, _Block_object_dispose函数会自动释放引用的auto变量(release).

__block修饰符

__block可以用于解决block内部无法修改auto变量值的问题, __block不能修饰全局变量、静态变量(static).代码如下:

        typedef  void(^MyBlock)(void);
        __block int age = 10;
        MyBlock block = ^{
            age = 20;
            NSLog(@"---block---%d", age);
        };
        block();

生成的c++的代码为:

 typedef void(*MyBlock)(void);
        // 生成一个新的对象age(结构体),类型为__Block_byref_age_0
        __attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
        // block定义,就是指向__main_block_impl_0的指针
        MyBlock block = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        
 // block逻辑执行函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_age_0 *age = __cself->age; // bound by ref

            (age->__forwarding->age) = 20;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_g6_f7y9s65s36vcgjz8dy2t0yr00000gn_T_main_eb767f_mi_0, (age->__forwarding->age));
        }
        
 struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    // 指向__Block_byref_age_0的指针
  __Block_byref_age_0 *age; // by ref
    // 构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

新生成包装age的结构体
struct __Block_byref_age_0 {
  void *__isa;
    // 指向自己类型的指针
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
    // 原本的age变量
 int age;
};

// block描述
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

从上面的编译的c++的源码得知:__block修饰的age变量变成了一个对象,类型为struct __Block_byref_age_0结构体.

__block的内存管理

Snip20181112_33.png Snip20181112_34.png

__block的__forwarding指针

已知由__block修饰的变量生成的对象中有__Block_byref_age_0 *__forwarding成员变量,在栈上的block__forwarding指向自己,当被copy之后,栈上的block的__forwarding指向堆上的block,堆上的__forwarding指向自己.

Snip20181112_35.png

__block修饰对象

循环引用问题

Animal定义一个block,实现并调用block:

typedef  void(^MyBlock)(void);

@interface Animal : NSObject

@property (nonatomic,copy) MyBlock block;

@end

@implementation Animal

- (void)dealloc {
    NSLog(@"%s",__func__);
}

@end

Animal *ani = [[Animal alloc] init];
ani.block = ^{
    NSLog(@"%@",ani);
};
ani.block();

运行代码,并没有回收ani对象,ani强引用了block,而block内部强引用了ani,导致对象得不到及时释放.

Snip20181112_36.png

我们可以用__weak(对象释放后为nil),__unsafe_unretained(对象释放后指向原来的内存).

        Animal *ani = [[Animal alloc] init];
        // 或者__unsafe_unretained
        __weak typeof(ani) weakAni = ani;
        ani.block = ^{
            NSLog(@"%@",weakAni);
        };
        ani.block();

还可以用__block解决(必须要调用block).

        Animal *ani = [[Animal alloc] init];
        __block typeof(ani) weakAni = ani;
        ani.block = ^{
            NSLog(@"%@",weakAni);
            weakAni = nil;
        };
        ani.block();
Snip20181112_37.png

前面都是ARC情况下,在MRC情况下,只能用__unsafe_unretained和__block,此时用__block解决循环引用时,不需调用block,也不必将对象置为nil,这是因为在MRC下,__block下生成的对象,对变量时弱引用.

        Animal *ani = [[Animal alloc] init];
        __block typeof(ani) weakAni = ani;
        ani.block = ^{
            NSLog(@"%@",weakAni);
           
        };
        [ani release];
上一篇下一篇

猜你喜欢

热点阅读