block

203 阅读6分钟

1. block的本质

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

以上代码编译后代码:

        int a = 10;

        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);


struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_a762a2_mi_0,a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_a762a2_mi_1,a);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_a762a2_mi_2,a);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

简化后:去掉强引用


        int a = 10;

        void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));

        block->FuncPtr(block);

可见block就是一个指针地址(带*),本质上是__main_block_impl_0结构体,传减去一个方法__main_block_func_0(block的函数体)__main_block_desc_0_DATA(描述信息包括block的长度等)的地址。

__main_block_func_0会赋值给__block_implFuncPtr

最后通过block拿到FuncPtr (因为结构体的地址和结构体的第一个元素的地址是一样的,所以可以通过强转拿到),调用的时候把这个block传进去,拿到它捕获的变量等。

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

block变量捕获

        static int a = 10;

        NSLog(@"外面——————————%p",&a);

        void (^block)(void) = ^{
            
            NSLog(@"blcok a = %d",a);
            
            a = 30;
            
            NSLog(@"block-----%p",&a);

        };
        
        a = 20;
        
        block();
        
        NSLog(@"外面 a = %d",a);

打印结果:

2020-06-23 11:54:58.613340+0800 block[92464:2645876] 外面——————————0x100001208
2020-06-23 11:54:58.613949+0800 block[92464:2645876] blcok a = 20
2020-06-23 11:54:58.614046+0800 block[92464:2645876] block-----0x100001208
2020-06-23 11:54:58.614151+0800 block[92464:2645876] 外面 a = 30

可以看出,static修饰的时候是 指针传递。

额外:

void(^block)(void) = ^{
    _name;
}

访问_name.其实是捕获了self.因为函数有两个隐式参数:self和_cmd. 再通过self->_name访问_name.

3. block的类型

  • block有三种类型,可以通过调用class方法或者isa指针查看具体类型,最终都继承自NSBlock(继承NSObject).
  1. NSGlobalBlock (_NSConcreteGlobalBlock) (继承__NSGlobalBlock)
  2. NSStackBlock (_NSConcreteStackBlock ) (继承__NSStackBlock)
  3. NSMallocBlock (_NSConcreteMallocBlock) (继承__NSMallocBlock)

在MRC环境下:

  • 小知识:类对象是放在数据段的。

4. block的copy

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

  • block作为函数返回值时
  • 将block赋值给__strong指针时 (ARC环境默认就是强引用)
  • block作为Cocoa API中方法名含有usingBlock的方法参数时
  • block作为GCD API的方法参数时
        MyPerson *person = [[MyPerson alloc]init];
        person.age = 10;
        
        
        MyBlock block = ^{
            NSLog(@"%d",person.age);
        };

ARC默认是强引用,所以这个block在推上。

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

ARC block属性建议写法:

  1. @property (strong, nonatomic) void (^block)(void);
  2. @property (copy, nonatomic) void (^block)(void);

4.对象类型的auto变量

  • 当block内部访问了对象类型的auto变量时
    如果block是在栈上,将不会对auto变量产生强引用

  • 如果block被拷贝到堆上

  1. 会调用block内部的copy函数
  2. copy函数内部会调用_Block_object_assign函数
  3. _Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  • 如果block从堆上移除
  1. 会调用block内部的dispose函数
  2. dispose函数内部会调用_Block_object_dispose函数 _Block_object_dispose函数会自动释放引用的auto变量(release)

源码:

因为__weak和__strong,是运行时产生的。所以在使用clang转换OC为C++代码时,可能会遇到以下问题
cannot create __weak reference in file using manual reference

解决方案:支持ARC、指定运行时系统版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

代码:

        MyBlock block;
        {
        
            MyPerson *person = [[MyPerson alloc]init];
            person.age = 10;
            
            __weak MyPerson *weakPerson = person;
            
            block = ^{
                NSLog(@"%d",weakPerson.age);
            };
        
        }
        
        
        NSLog(@"---------------------");

源码:

  struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MyPerson *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  MyPerson *__weak weakPerson = __cself->weakPerson; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_d22b95_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)weakPerson, sel_registerName("age")));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->weakPerson, (void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->weakPerson, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

注意:这里是对person捕获,而不是对age.

代码:

        MyBlock block;
        {
        
            MyPerson *person = [[MyPerson alloc]init];
            person.age = 10;
            
            
            block = ^{
                NSLog(@"%d",person.age);
            };
        
        }
        
        
        NSLog(@"---------------------");

源码:

  struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  MyPerson *__strong person;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, MyPerson *__strong _person, int flags=0) : person(_person) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  MyPerson *__strong person = __cself->person; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_db4cbb_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

测试:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    MJPerson *p = [[MJPerson alloc] init];
    
    __weak MJPerson *weakP = p;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"1-------%@", p);
        
        NSLog(@"+++++++++++++");
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"2-------%@", weakP);
        });
    });
    
    NSLog(@"touchesBegan:withEvent:");
}

打印结果:

2020-06-23 22:43:57.526211+0800 Interview03-测试[49740:3955822] 
touchesBegan:withEvent:
2020-06-23 22:43:58.525566+0800 Interview03-测试[49740:3955822] 1-------<MJPerson: 0x600002cfc340>
2020-06-23 22:43:58.525788+0800 Interview03-测试[49740:3955822] +++++++++++++
2020-06-23 22:43:58.526006+0800 Interview03-测试[49740:3955822] MJPerson - dealloc
2020-06-23 22:44:00.528077+0800 Interview03-测试[49740:3955822] 2-------(null)

可以看出MyPerson,1秒之后就释放了。因为第一个block对他强引用,这个block运行完之后就释放了,对MyPerson执行了release。

5. __block

改变外部变量的值:

        int age = 10;
        
        MJBlock block1 = ^{
//            age = 20;
            NSLog(@"age is %d", age);
        };
        
        
        static int a = 15;
        
        MJBlock block2 = ^{
            a = 30;
            NSLog(@"a is %d", a);
        };
        
        
        NSObject *o = [[NSObject alloc]init];
        
        MJBlock block3 = ^{
//            o = [[NSObject alloc]init];
            
            NSLog(@"o is %@",o);
        };
        
        block1();
        block2();
        block3();

会发现,注释的两句会报错。因为auto变量(不管是基本类型还是对象),捕获的值传递,在一个函数内是不能访问到另一个函数的变量的。源码中__main_block_func_1是在main函数外定义的。

static修饰的时候,不会报错。因为是指针传递。

static void __main_block_func_1(struct __main_block_impl_1 *__cself) {
  int *a = __cself->a; // bound by copy

            (*a) = 30;
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_1l_pky1p4kn4p9dcgr7cqzz7n080000gn_T_main_00301d_mi_1, (*a));
        }

怎样在里面改auto变量呢?通过__block. 注意:

NSMutableArray *array = [[NSMutableArray alloc]init];
        
        MJBlock block1 = ^{
            [array addObject:@"1"];
            NSLog(@"array count is %ld",array.count);
        };
        
        block1();

这种情况,编译不会报错。因为这是使用它,而不是改变它。