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不会被释放。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,332评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,508评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,812评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,607评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,728评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,919评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,071评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,802评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,256评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,576评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,712评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,389评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,032评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,026评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,473评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,606评论 2 350

推荐阅读更多精彩内容