iOS中Block捕获变量

Block捕获变量:

  • 局部变量:基本数据类型:捕获值;对象型:捕获值和所有权修饰符

  • 静态局部变量:捕获地址

  • 全局变量:不捕获

  • 静态全局变量:不捕获

捕获局部变量:非对象、对象、__block(非对象)、__block(对象)。

捕获非对象:

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        int a = 0;
        myBlock = ^() {
            if (a > 0) {
            }
        };
    }
}

// ==>

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        int a = 0;
        myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, a));
    }
}

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;
  int a;
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __blockStudy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0)};

捕获对象:

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        MyObject *myObject = [MyObject new];
        myBlock = ^() {
            myObject = [MyObject new];
        };
    }
}

// ==>

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        MyObject *myObject = ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("new"));
        myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, myObject, 570425344));
    }
}

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;
  MyObject *__strong myObject;
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, MyObject *__strong _myObject, int flags=0) : myObject(_myObject) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __blockStudy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
  void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};

static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {_Block_object_assign((void*)&dst->myObject, (void*)src->myObject, 3/*BLOCK_FIELD_IS_OBJECT*/);}

可以发现__blockStudy_block_desc_0相比捕获非对象的多了两个函数指针:copydispose。当block被拷贝到堆上面时copy函数会执行,调用_Block_object_assign拷贝捕获的对象的地址并增加该对象的引用计数。

捕获__block(非对象)

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        __block int myInt = 0;
        myBlock = ^() {
            myInt = 1;
        };
    }
}

// ==>

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        __attribute__((__blocks__(byref))) __Block_byref_myInt_0 myInt = {(void*)0,(__Block_byref_myInt_0 *)&myInt, 0, sizeof(__Block_byref_myInt_0), 0};
        myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, (__Block_byref_myInt_0 *)&myInt, 570425344));
    }
}

struct __Block_byref_myInt_0 {
  void *__isa;
__Block_byref_myInt_0 *__forwarding;
 int __flags;
 int __size;
 int myInt;
};

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;
  __Block_byref_myInt_0 *myInt; // by ref
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, __Block_byref_myInt_0 *_myInt, int flags=0) : myInt(_myInt->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __blockStudy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
  void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};

static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {_Block_object_assign((void*)&dst->myInt, (void*)src->myInt, 8/*BLOCK_FIELD_IS_BYREF*/);}

这里和捕获对象自由变量的block相比同样也有copy函数指针,不过在调用_Block_object_assign传的第三个参数不一样。

捕获__block(对象)

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        __block MyObject *myObject = [MyObject new];
        myBlock = ^() {
            myObject = [MyObject new];
        };
    }
}

// ==>

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        __attribute__((__blocks__(byref))) __Block_byref_myObject_0 myObject = {(void*)0,(__Block_byref_myObject_0 *)&myObject, 33554432, sizeof(__Block_byref_myObject_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((MyObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MyObject"), sel_registerName("new"))};
        myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, (__Block_byref_myObject_0 *)&myObject, 570425344));
    }
}

struct __Block_byref_myObject_0 {
  void *__isa;
__Block_byref_myObject_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 MyObject *__strong myObject;
};

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;
  __Block_byref_myObject_0 *myObject; // by ref
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, __Block_byref_myObject_0 *_myObject, int flags=0) : myObject(_myObject->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __blockStudy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
  void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {_Block_object_assign((void*)&dst->myObject, (void*)src->myObject, 8/*BLOCK_FIELD_IS_BYREF*/);}

这里的block同样有copy函数指针,但是__block变量的结构体__Block_byref_myObject_0也多了两个函数指针:__Block_byref_id_object_copy__Block_byref_id_object_dispose。当block被拷贝到堆上面时,__block变量的结构体__Block_byref_myObject_0也会被拷贝到堆上面,这时__Block_byref_id_object_copy就会被调用。

_Block_object_assign

看看_Block_object_assign的源码:

/*
 * When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
 * to do the assignment.
 */
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    // (this test must be before next one)
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
        // copying a Block declared variable from the stack Block to the heap
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}

如果block捕获了对象自由变量,在拷贝的时候_Block_object_assign传入的是BLOCK_FIELD_IS_OBJECT,这时候会强引用这个对象,再拷贝地址。

如果block捕获了__block变量,不管是非对象还是对象,在拷贝时_Block_object_assign传入的是BLOCK_FIELD_IS_BYREF,这时拷贝的是__block变量,把__block变量从栈拷贝到堆。

__block变量的拷贝同样调用_Block_object_assign ,这时第三个参数为BLOCK_BYREF_CALLER|BLOCK_FIELD_IS_OBJECT

block对对象的持有是在_Block_object_assign执行时完成的。

block会强引用__block结构体,而__block结构体强引用对象自由变量。

__weak修饰符

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        NSObject *myObject = [NSObject new];
        __weak NSObject *weakMyObjetc = myObject;
        myBlock = ^() {
            NSLog(@"%@", weakMyObjetc);
        };
    }
}

// ==>

void blockStudy()
{
    dispatch_block_t myBlock;
    {
        NSObject *myObject = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new"));
        __attribute__((objc_ownership(weak))) NSObject *weakMyObjetc = myObject;
        myBlock = ((void (*)())&__blockStudy_block_impl_0((void *)__blockStudy_block_func_0, &__blockStudy_block_desc_0_DATA, weakMyObjetc, 570425344));
    }
}

struct __blockStudy_block_impl_0 {
  struct __block_impl impl;
  struct __blockStudy_block_desc_0* Desc;
  NSObject *__weak weakMyObjetc;
  __blockStudy_block_impl_0(void *fp, struct __blockStudy_block_desc_0 *desc, NSObject *__weak _weakMyObjetc, int flags=0) : weakMyObjetc(_weakMyObjetc) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static struct __blockStudy_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __blockStudy_block_impl_0*, struct __blockStudy_block_impl_0*);
  void (*dispose)(struct __blockStudy_block_impl_0*);
} __blockStudy_block_desc_0_DATA = { 0, sizeof(struct __blockStudy_block_impl_0), __blockStudy_block_copy_0, __blockStudy_block_dispose_0};

static void __blockStudy_block_copy_0(struct __blockStudy_block_impl_0*dst, struct __blockStudy_block_impl_0*src) {_Block_object_assign((void*)&dst->weakMyObjetc, (void*)src->weakMyObjetc, 3/*BLOCK_FIELD_IS_OBJECT*/);}

block捕获变量时把所有权修饰符也捕获了:NSObject *__weak weakMyObjetc,可是拷贝的时候_Block_object_assign传入的同样是3/*BLOCK_FIELD_IS_OBJECT*/,并没有区分?进一步查看_Block_object_assign的汇编代码:

___copy_helper_block_:                  ## @__copy_helper_block_
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp6:
    .cfi_def_cfa_offset 16
Ltmp7:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp8:
    .cfi_def_cfa_register %rbp
    subq    $48, %rsp
    movq    %rdi, -8(%rbp)
    movq    %rsi, -16(%rbp)
    movq    -16(%rbp), %rsi
    movq    %rsi, %rdi
    movq    -8(%rbp), %rax
    movq    %rax, %rcx
    addq    $40, %rdi
    movq    %rcx, %rdx
    addq    $40, %rdx
    movq    %rdi, -24(%rbp)         ## 8-byte Spill
    movq    %rdx, %rdi
    movq    -24(%rbp), %rdx         ## 8-byte Reload
    movq    %rsi, -32(%rbp)         ## 8-byte Spill
    movq    %rdx, %rsi
    movq    %rcx, -40(%rbp)         ## 8-byte Spill
    movq    %rax, -48(%rbp)         ## 8-byte Spill
    callq   _objc_copyWeak
    movq    -40(%rbp), %rax         ## 8-byte Reload
    addq    $32, %rax
    movq    -32(%rbp), %rcx         ## 8-byte Reload
    movq    32(%rcx), %rdx
    movq    -48(%rbp), %rsi         ## 8-byte Reload
    movq    $0, 32(%rsi)
    movq    %rax, %rdi
    movq    %rdx, %rsi
    callq   _objc_storeStrong
    addq    $48, %rsp
    popq    %rbp
    retq
    .cfi_endproc

    .align  4, 0x90

可以看到它调用了两个方法,分别是_objc_copyWeak_objc_storeStrong,从这里可以看出weak和strong的确是使用了不用的方法对待的。

看下面的例子

        NSString *a;
        NSLog(@"%p", a);
        void (^ myblock)(void) = ^{
            // block内部也会有一个指针b 这个指针的值和a这个指针的值是一样的 指向同一个地方 但是并不是同一个指针
            // 后面重新给a赋值 修改了a指针的值 也就是修改了a指向的地址 但是并不会影响指针b的值 它指向的还是空
            NSLog(@"%@", a); // 输出null
        };
        a = @"a";
        myblock();

block内部打印出的a是null,是因为block内部捕获a的时候,是创建了一个和a指向同一处的指针b,也就是a和b的值相同,但它们不是同一个指针。后面对a赋值的时候,修改了指针a的值,也就是a指向的地址,但是并不会影响b,所以block内部的还是空。

        __block NSString *a; // 定义了一个__block结构体 里面有个a指针 __block结构体->forwarding->a
        NSLog(@"%p", a);
        void (^ myblock)(void) = ^{
            NSLog(@"%@", a); // 输出a __block结构体->forwarding->a
        };
        a = @"a"; // 本质是__block结构体->forwarding->a
        // 修改的一直是__block结果体内的a指针的值 __block结构体->forwarding->a
        myblock();

用__block来修饰这个变量,本质变成了是在定义一个__block结构体,这个结构体里面包含了我们实际上要声明的变量。后面涉及到这个变量的访问和修改都是通过同一个__block结构体的forwarding来取到的,所以内外的修改能够同步。

        void (^ myblock)(void) = ^{
            myblock();
        };
        myblock();

这个代码运行会报错,因为内部的myblock是空的。可以拆分来看:

        void (^ myblock)(void);
        void (^ tmp)(void)  = ^{
            myblock();
        };
        myblock = tmp;
        myblock();

其实和上面的逻辑一样,block捕获到的时候这个变量是空的,外部的修改并不会影响内部的变量。可以用一个__block来修饰:

        __block void (^ myblock)(int);
        void (^ tmp)(int)  = ^(int num) {
            if (num > 0) {
                return;
            }
            myblock(num - 1);
        };
        myblock = tmp;
        myblock(5);
        // __block结构体->forwarding->block变量
        // block变量->__block结构体
        // 循环引用

这时候可以正常运行了,但是会造成循环引用,block和__block结构体都无法释放。用__weak来打破循环引用:

        __block __weak void (^ myblock)(int);
        void (^ tmp)(int)  = ^(int num) {
            if (num < 0) {
                return;
            }
            NSLog(@"%d", num);
            myblock(num - 1);
        };
        myblock = tmp;
        myblock(5);
        // block变量->弱引用->__block结构体
        // __block结构体->forwarding->block变量
        // 函数运行结束后 block变量被回收 __block结构体也会被回收

block内部其实是弱引用__block结构体,但函数执行结束后__block结构体就会被回收,看下面这种情况:

        __block __weak void (^ myblock)(int);
        void (^ tmp)(int)  = ^(int num) {
            if (num < 0) {
                return;
            }
            NSLog(@"%d", num);
            dispatch_async(dispatch_get_main_queue(), ^{
                myblock(num - 1);
            });
        };
        myblock = tmp;
        myblock(5);
//        // block变量->弱引用->__block结构体
//        // __block结构体->forwarding->block变量
//        // 函数运行结束后 block变量被回收 __block结构体也会被回收

这种情况会报错,因为dispatch_async里面的block执行的时候,myblock其实就是__block结构体已经被回收了,解决方法:

        __block __weak void (^ myblock)(int);
        void (^ tmp)(int)  = ^(int num) {
            __strong void (^ strongmyblock)(int) = myblock;
                if (num < 0) {
                return;
            }
            NSLog(@"%d", num);
            dispatch_async(dispatch_get_main_queue(), ^{
                strongmyblock(num - 1);
            });
        };
        myblock = tmp;
        myblock(5);
        // dispatch_async的block->strongmyblock->myblock 在dispatch_async的block执行之前myblock不会被销毁

dispatch_async的block会持有strongmyblock,而strongmyblock又会持有myblock,所以在dispatch_async的block执行之前myblock不会被释放。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容