Block相关问题

1. block的内部实现,结构体是什么样的

block的本质

  1. block本质上也是一个oc对象,他内部也有一个isa指针。
  2. block是封装了函数调用以及函数调用环境的OC对象。
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int age = 10;
        void(^block)(int ,int) = ^(int a, int b){
            NSLog(@"this is block,a = %d,b = %d",a,b);
            NSLog(@"this is block,age = %d",age);
        };
        block(3,5);
    }
    return 0;
}

使用命令行将代码转化为c++查看其内部结构,与OC代码进行比较
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m

// 定义block变量代码
void(*block)(int ,int) = ((void (*)(int, int))&__main_block_impl_0(
(void *)__main_block_func_0, 
&__main_block_desc_0_DATA, age)
);

block定义中调用了__main_block_impl_0函数,并且将__main_block_impl_0函数的地址赋值给了block

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int age;
  //同名构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;   //block代码块函数地址
    Desc = desc;   //block对象占用内存大小
  }
};

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

//block中的代码被封装成函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
  int age = __cself->age; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5__mh11h5tx6w17z4xt87qqj45h0000gn_T_main_34785e_mi_0,a,b);
            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5__mh11h5tx6w17z4xt87qqj45h0000gn_T_main_34785e_mi_1,age);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;  //存储着__main_block_impl_0的占用空间大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

__main_block_impl_0结构体内可以发现__main_block_impl_0构造函数中传入了四个参数。(void *)__main_block_func_0&__main_block_desc_0_DATAageflags。其中flage有默认值,也就说flage参数在调用的时候可以省略不传。而最后的 age(_age)则表示传入的_age参数会自动赋值给age成员,相当于age = _age。

block块中的代码封装成__main_block_func_0函数,并将__main_block_func_0函数的地址传入了__main_block_impl_0的构造函数中保存在结构体内.

//调用block的代码
 ((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 3, 5);

(__block_impl *)block将block强制转化为__block_impl类型的,因为__block_impl是__main_block_impl_0结构体的第一个成员,相当于将__block_impl结构体的成员直接拿出来放在__main_block_impl_0中,那么也就说明__block_impl的内存地址就是__main_block_impl_0结构体的内存地址开头。所以可以转化成功。并找到FunPtr成员。

FunPtr中存储着通过代码块封装的函数地址,那么调用此函数,也就是会执行代码块中的代码。并且回头查看__main_block_func_0函数,可以发现第一个参数就是__main_block_impl_0类型的指针。也就是说将block传入__main_block_func_0函数中,便于重中取出block捕获的值

可以看看这篇文章

2. block是类吗,有哪些类型

从block的结构体中可知,block同样也有一个isa指针,所以block也是一个类,它的类型包括:

  • _NSConcreteGlobalBlock
  • _NSConcreteStackBlock
  • _NSConcreteMallocBlock


    image.png
3. __block的底层原理

__block修饰符

  • __block可以用于解决block内部无法修改auto变量值的问题
  • __block不能修饰全局变量、静态变量(static)
  • 编译器会将__block变量包装成一个结构体,结构体有个__forwarding指针指向这个结构体,通过__forwarding修改其值。
  • 引用 __block 变量的block在堆上时,__block变量也会copy到堆上,栈上__block变量的__forwarding指向堆上的变量,堆上的__forwarding指向自己,修改的其实是堆上的__block变量
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};
4. 一个int变量被 __block 修饰与否的区别?block的变量截获
  1. 没有被__block修饰的int,block体中对这个变量的引用是值拷贝,在block中是不能被修改的。
  2. 通过__block修饰的int,block体中对这个变量的引用是指针拷贝,它会生成一个结构体,复制这个变量的指针引用,从而达到可以修改变量的作用。

关于block的变量截获:

image.png

block会将block体内引用外部变量的变量进行拷贝,将其拷贝到block的数据结构中,从而可以在block体内访问或修改外部变量。

外部变量未被__block修饰时,block数据结构中捕获的是外部变量的值,通过__block修饰时,则捕获的是对外部变量的指针引用。

注意:block内部访问全局变量时,全局变量不会被捕获到block数据结构中。

5. block在修改NSMutableArray,需不需要添加__block
  • 如果修改的是NSMutableArray的存储内容的话,是不需要添加__block修饰的。
  • 如果修改的是NSMutableArray对象的本身,那必须添加__block修饰。
6. __block 在 ARC 和 MRC 下含义一样吗?

MRC 环境下,block 截获外部用 __block 修饰的变量,不会增加对象的引用计数
ARC环境下,block 截获外部用 __block 修饰的变量,增加对象的引用计数

MRC 环境下,可以通过 __block 来打破循环引用
ARC 环境下,则需要用__weak、 __unsafe_unretained 来打破循环引用

7. 怎么进行内存管理的

block按照内存分布,分三种类型:

  • 全局内存中的block
  • 栈内存中的block
  • 堆内存中的block。

在MRC和ARC下block的分布情况不一样

MRC下:
当block内部引用全局变量或者不引用任何外部变量时,该block是在全局内存中的。
当block内部引用了外部的非全局变量的时候,该block是在栈内存中的。
当栈中的block进行copy操作时,会将block拷贝到堆内存中。

通过__block修饰的变量,不会对其应用计数+1,不会造成循环引用。

ARC下:
当block内部引用全局变量或者不引用任何外部变量时,该block是在全局内存中的。
当block内部引用了外部的非全局变量的时候,该block是在堆内存中的。
也就是说,ARC下只存在全局block堆block

通过__block修饰的变量,在block内部依然会对其引用计数+1,可能会造成循环引用。
通过用__weak、__unsafe_unretained 修饰的变量,在block内部不会对其引用计数+1,不会造成循环引用。

8. block解决循环引用- ARC

方法一:用__weak、__unsafe_unretained 解决

__weak thpeof(self) weakSelf = self;
self.block = ^{
    printf("%p", weakSelf);
};

__unsafe_unretained id weakSelf = self;
self.block = ^{
    NSLog(@"%p", weakSelf);
}

方法二:用__block解决(必须要调用block)

__block id weakSelf = self;
self.block = ^{
    printf("%p", weakSelf);
    weakSelf = nil;
};
self.block();
9. 解决循环引用时为什么要用__strong、__weak修饰

__weak修饰的变量,不会出现引用计数+1,也就不会造成block强持有外部变量,这样也就不会出现循环引用的问题了。

但是,我们的block内部执行的代码中,有可能是一个异步操作,或者延迟操作,此时引用的外部变量可能会变成nil,导致意想不到的问题,而我们在block内部通过__strong修饰这个变量时,block会在执行过程中强持有这个变量,此时这个变量也就不会出现nil的情况,当block执行完成后,这个变量也就会随之释放了。

10. block发生copy时机

一般情况在ARC环境中,编译器将创建在栈中的block会自动拷贝到堆内存中,而block作为方法或函数参数传递时,编译器不会做copy操作。

在ARC环境下,编译器会根据情况自动将栈上的block复制到堆上,比如以下情况:
(1)block作为函数返回值时
(2)将block赋值给__strong指针时
(3)block作为 Cocoa API 中,方法名里含有usingBlock的方法参数时
(4)block作为 GCD API 的方法参数时

11. Block访问对象类型的auto变量时,在ARC和MRC下有什么区别

ARC下,栈区创建的block会自动copy到堆区;
MRC下,就不会自动拷贝了,需要我们手动调用copy函数。

  1. 一旦block中捕获的变量为对象类型, block结构体中的__main_block_desc_0会出现两个函数copydispose. 因为block内部会访问这个对象, block需要拥有这个对象,就需要对被捕获的对象进行强引用, 因此Block内部也对内存进行管理操作.因此一旦block捕捉到了变量的类型是对象类型, 就会生成copy和dispose来对内部引用的对象进行内存管理.
    (1)如果block是在栈上,将不会对auto变量产生强引用
    (2)如果block被拷贝到堆上
  2. 如果block被拷贝到堆上, copy函数会调用_Block_object_assign, 该函数会根据auto变量的修饰符(__strong,__weak,unsafe_unretained)来做出相应的操作, 行成强引用或者弱引用.
  3. 如果block从堆中移除,dispose函数会调用_Block_object_dispose函数,自动释放引用的auto变量。

因此,在ARC下,由于block被自动copy到了堆区,从而对外部的对象进行强引用,如果这个对象同样强引用这个block,就会形成循环引用

MRC下,由于访问的外部变量是auto修饰的,所以这个block属于栈区的,如果不对block手动进行copy操作,在运行完block的定义代码段后,block就会被释放,而由于没有进行copy操作,所以这个变量也不会经过Block_object_assign处理,也就不会对变量强引用。

简单说就是:
ARC(堆block)下会对这个对象强引用,MRC(栈block)下不会。

12. 被__block修饰的对象类型

1、当__block变量在栈上时,不会对指向的对象产生强引用
2、当__block变量被copy到堆时

(1)会调用__block变量内部的copy函数
(2)copy函数内部会调用_Block_object_assign函数
(3)_Block_object_assign函数会根据所指向对象的修饰符(__strong、__weak、__unsafe_unretained)作出相应操作,形成强引用(retain)或者弱引用(注意:这里仅限于ARC时会retain,MRC时不会retain

3、如果__block变量从堆上移除
(1)会调用__block变量内部的dispose函数
(2)dispose函数内部会调用_Block_object_dispose函数
(3)_Block_object_dispose函数会自动释放指向的对象(release)

你可能感兴趣的:(Block相关问题)