block常见问题及底层初探

block常见的几个问题:

  • 1、block是什么
    block是一个指向结构体的指针,编译器会将block的内部代码生成对应的函数。

  • 2、block为什么用copy修饰
    正常创建出来的block的内存是放在栈中(如果block内部没有调用外部变量时存放在全局区),程序员无法去管理,什么时候释放内存也不是程序员可以决定的,栈区的特点就是创建的对象随时可能被销毁,一旦被销毁后续再次调用空对象就可能会造成程序崩溃。所以一般情况下,就需要使用copy去修饰,会把block copy到堆上,在ARC中,对block代码块内部的对象强引用,在非ARC中对于引用对象进行一次retain操作。

  • 3、block与循环引用
    在block代码块内部如果使用了当前对象进行调用方法,或者他的的操作,就会对当前对象进行强引用一次,并且如果这个block归当前对象持有,就会导致block和当前对象互相持有引用,而都不能正常释放。(注意:如果block不是当前对象所持有,例如在控制器中使用AFNetWorking请求,AFNetWorking的block并不是当前控制器所持有的,所以这里不用__weak,不会造成循环引用)

  • 4、_strong的使用
    使用_weak来修饰可以避免对控制器或者当前类的循环引用.
    但是有的时候,当前对象self已经销毁了,之后再去执行这个block,里面的weakSelf就是个nil了,可能会出现异常,于是最好是如下写法:

    __weak typeof(self) weakSelf = self;
    self.rightBtnClickHandler = ^() {
        __strong typeof(self) strongSelf = weakSelf;
        [strongSelf defaultRightBtnClick];
        [strongSelf defaultLeftBtnClick];
    };

  在block内部使用__strong对weakSelf进行一次强引用,self的引用计数+1,此时strongSelf相当于是block内部的一个局部变量,只要block不结束,self这个对象就不会释放,等到block执行完毕,局部变量strongSelf也会自动销毁,完成释放。

block底层初识

接下来看几个常见的面试题

void test1()
{
    int value = 10;
    
    void (^block)(void) = ^{
        NSLog(@"value = %d", value);
    };
    
    value = 20;
    
    block(); // 打印结果:value = 10
}

void test2()
{
    __block int value = 10;
    
    void (^block)(void) = ^{
        NSLog(@"value = %d", value);
    };
    
    value = 20;
    
    block(); // 打印结果:value = 20
}

void test3()
{
    static int value = 10;
    
    void (^block)(void) = ^{
        NSLog(@"value = %d", value);
    };
    
    value = 20;
    
    block(); // 打印结果:value = 20
}

int value = 10;
void test4()
{
    void (^block)(void) = ^{
        NSLog(@"value = %d", value);
    };
    
    value = 20;
    
    block(); // 打印结果:value = 20
}

可以看出,先test1中,我们更改了局部变量value的值,但是block中的value值并没有变化,但是在test2、3、4中,却能成功更改。这是为什么呢?
我们尝试着看下它们的底层代码。
终端执行clang -rewrite-objc main.m

block_clang.png

把文件编译成c++代码,得到.cpp文件,打开后估计有接近10万行代码,直接command+⬇️,我们来到文件的最后,再往上面滑一点,可以看到如下:


block_底层代码.png

我们可以看到 void (*block)(void) 是一个没有参数没有返回值的函数指针,既然是一个函数指针,那它就是一个变量,变量里面只能保存函数地址,等式右边接收是下面这个函数的返回值:

  __test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }

这个函数有4个参数,参数 flags= 0,这个参数其实就相当于Swift中指定了一个默认值,不传也有值,可以忽略。所以上面test1中传了3个参数,test2中传了4个参数,其中第三个参数是value,前者是传值,后者是传的value的地址,故后者value的值发生了变化

block_test1.png
block_test2.png

总结:

  • block可以修改全部变量和静态变量,不可以修改局部变量,如果想要修改使用__block,
  • block之所以能够修改全局变量、静态变量、__block修饰的局部变量,是因为把指向变量的指针地址copy到block结构体内部,而局部变量是copy的变量值到block内部.

你可能感兴趣的:(block常见问题及底层初探)