block探究

前面提出了一个关于Block的问题,下面我们通过源码先来了解一下block,再来详细分析这个问题


======================以下如不特别说明,均是ARC运行环境======================


首先我们把c语言中的变量分成以下几种:

自动变量

函数参数

静态局部变量

静态全局变量

全局变量


我们首先运行以下程序:

#include<stdio.h>
#import <Foundation/Foundation.h>

int globalVar = 1;
static int staticGlobalVar = 1;

int main() {
    
    static int staticVar = 1;
    int var = 1;
    
    printf("初始:\r\n");
    printf("globalVar:地址:%p 值:%d\r\n",&globalVar,globalVar);
    printf("staticGlobalVar:地址:%p 值:%d\r\n",&staticGlobalVar,staticGlobalVar);
    printf("staticVar:地址:%p 值:%d\r\n",&staticVar,staticVar);
    printf("var:地址:%p 值:%d\r\n",&var,var);
    
    void (^myBlock)(void) = ^{
        globalVar++;
        staticGlobalVar++;
        staticVar++;
        
        printf("block内:\r\n");
        printf("globalVar:地址:%p 值:%d\r\n",&globalVar,globalVar);
        printf("staticGlobalVar:地址:%p 值:%d\r\n",&staticGlobalVar,staticGlobalVar);
        printf("staticVar:地址:%p 值:%d\r\n",&staticVar,staticVar);
        printf("var:地址:%p 值:%d\r\n",&var,var);
    };
    
    globalVar++;
    staticGlobalVar++;
    staticVar++;
    var++;
    
    printf("block外:\r\n");
    printf("globalVar:地址:%p 值:%d\r\n",&globalVar,globalVar);
    printf("staticGlobalVar:地址:%p 值:%d\r\n",&staticGlobalVar,staticGlobalVar);
    printf("staticVar:地址:%p 值:%d\r\n",&staticVar,staticVar);
    printf("var:地址:%p 值:%d\r\n",&var,var);
    
    myBlock();

}

打印信息如下:

初始:

globalVar:地址:0x105f9f290 值:1

staticGlobalVar:地址:0x105f9f298 值:1

staticVar:地址:0x105f9f294 值:1

var:地址:0x7fff59c65414 值:1

block外:

globalVar:地址:0x105f9f290 值:2

staticGlobalVar:地址:0x105f9f298 值:2

staticVar:地址:0x105f9f294 值:2

var:地址:0x7fff59c65414 值:2

block内:

globalVar:地址:0x105f9f290 值:3

staticGlobalVar:地址:0x105f9f298 值:3

staticVar:地址:0x105f9f294 值:3

var:地址:0x60800004e6c0 值:1


我们看到,只有var在block内部的地址发生了变化,我们可以猜想var在block内部只是进行了值的拷贝,因此在执行block的时候var的值依然是它的初始值1;

block实际上是作为极普通的C语言源代码来处理的,通过支持Block的编译器,含有Block语法的源代码转换为一般C语言编译器能够处理的源代码,并作为极为普通的C语言源代码被编译。

现在我们就通过源码来验证一下,我们通过clang工具将含有Block语法的源代码变换为C++源代码:

clang -rewrite-objc 文件名

我们截取最要部分如下:

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

int globalVar = 1;
static int staticGlobalVar = 1;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *staticVar;
  int var;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staticVar, int _var, int flags=0) : staticVar(_staticVar), var(_var) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *staticVar = __cself->staticVar; // bound by copy
  int var = __cself->var; // bound by copy

        globalVar++;
        staticGlobalVar++;
        (*staticVar)++;

        printf("block内:\r\n");
        printf("globalVar:地址:%p 值:%d\r\n",&globalVar,globalVar);
        printf("staticGlobalVar:地址:%p 值:%d\r\n",&staticGlobalVar,staticGlobalVar);
        printf("staticVar:地址:%p 值:%d\r\n",&(*staticVar),(*staticVar));
        printf("var:地址:%p 值:%d\r\n",&var,var);
    }

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 main() {

    static int staticVar = 1;
    int var = 1;

    printf("初始:\r\n");
    printf("globalVar:地址:%p 值:%d\r\n",&globalVar,globalVar);
    printf("staticGlobalVar:地址:%p 值:%d\r\n",&staticGlobalVar,staticGlobalVar);
    printf("staticVar:地址:%p 值:%d\r\n",&staticVar,staticVar);
    printf("var:地址:%p 值:%d\r\n",&var,var);

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &staticVar, var));

    globalVar++;
    staticGlobalVar++;
    staticVar++;
    var++;

    printf("block外:\r\n");
    printf("globalVar:地址:%p 值:%d\r\n",&globalVar,globalVar);
    printf("staticGlobalVar:地址:%p 值:%d\r\n",&staticGlobalVar,staticGlobalVar);
    printf("staticVar:地址:%p 值:%d\r\n",&staticVar,staticVar);
    printf("var:地址:%p 值:%d\r\n",&var,var);

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

}

分析:

我们的myBlock通过__main_block_impl_0结构体的构造函数创建并初始化,我们先来看看它的命名,main代表Block所属的函数,0代表该Block在该函数中出现的顺序,如果还有其他Block,则为1,2...依次往后排。

__main_block_impl_0结构自身其实就两个变量,一个是__block_impl结构,一个是__main_block_desc_0结构指针,其他的变量都是捕获进来的,我们先看看__block_impl结构:

struct __block_impl {
  void *isa;//和Class的isa一样
  int Flags;//一些标识,下面会讲到
  int Reserved;//预留的
  void *FuncPtr;//指向Block实际执行的代码块
};

本例中isa指向_NSConcreteStackBlock,Flags为0,FuncPtr指向__main_block_func_0,

看到isa我们直接想到的就是每个类都有个Class类型的isa变量,

typedef struct objc_class *Class;

我从原始的Objc2的源码发现_NSConcreteStackBlock的定义如下:

struct objc_class _NSConcreteStackBlock;

说明Block的实际其实也是Object-C的对象,它的isa所指向的_NSConcreteStackBlock中存储了该类的信息。

除了_NSConcreteStackBlock,Block还有_NSConcreteMallocBlock,_NSConcreteGlobalBlock两种类型,后面两种类型上面情况会出现呢?待会我们会讲到。

上面我们看到Block的调用实际上就是执行FuncPtr指针所指的函数

我们继续看看__main_block_desc_0结构

static struct __main_block_desc_0 {
  size_t reserved;//预留的
  size_t Block_size;//Block的大小
}

本例中Block_size就是__main_block_impl_0的大小

__main_block_func_0中的__cself相当于C++中的this指针,是编译器自动传进去的。

我们从__main_block_impl_0结构体可以发现多了两个变量

int *staticVar;
int var;

这两个变量中从Block的构造函数创建可以看出,staticVar捕获的是指针,而var捕获的是数值,因此,我们在Block外边对var进行自加操作,是不会影响Block内部的var的,而静态全局变量和全局变量完全不用捕获就可以直接使用,因为他们是全局的。

这里我没有在Block内部对var++是因为编译器会报错,Block内部只能修改修饰为__block的变量,也就是我们上一篇文章讲到的题目,现在我们来分析一下:

通过clang工具将其转换成C++如下:

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

        (i->__forwarding->i) ++;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_k5_zb4rs_592rqcsch9nb65xxgm0000gn_T_main_806a08_mi_1,&(i->__forwarding->i),(i->__forwarding->i));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main() {

    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 0};
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_k5_zb4rs_592rqcsch9nb65xxgm0000gn_T_main_806a08_mi_0,&(i.__forwarding->i),(i.__forwarding->i));

    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));

    (i.__forwarding->i)++;
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_k5_zb4rs_592rqcsch9nb65xxgm0000gn_T_main_806a08_mi_2,&(i.__forwarding->i),(i.__forwarding->i));

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

}

我们对比这两个例子的编译器源码,发现__main_block_desc_0中多了copy和dispose两个函数指针变量,分别指向__main_block_copy_0和__main_block_dispose_0。

570425344 就是0x22000000,也就是下面的BLOCK_HAS_COPY_DISPOSE|BLOCK_HAS_DESCRIPTOR

enum {
    BLOCK_REFCOUNT_MASK =     (0xffff),
    BLOCK_NEEDS_FREE =        (1 << 24),
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
    BLOCK_HAS_CTOR =          (1 << 26), /* Helpers have C++ code. */
    BLOCK_IS_GC =             (1 << 27),
    BLOCK_IS_GLOBAL =         (1 << 28),
    BLOCK_HAS_DESCRIPTOR =    (1 << 29)
};

我们看到i被转换成了__Block_byref_i_0对象,并且Block捕获了这个对象,并且是通过指针的形式捕获进去的,程序里面所有对i的访问的都是通过i.__forwarding->i来实现的,这个__forwarding是个啥,创建它有啥意义,现在我们就从这个__forwarding来揭开谜团。

首先我们来看个问题

Block中可以存有超过其变量作用域的被截获对象的自动变量,变量作用域结束的同时,原来的自动变量被废弃,因此Block中超过变量作用域而存在的变量同静态变量一样,将不能通过指针访问原来的自动变量,如何解决这个问题呢?

Block通过将Block和__block变量从栈上复制到堆上的方法来解决这个问题。

我们通过” clang -S -fobjc-arc 文件名 “将源码转换为汇编代码,我们会看到_objc_retainBlock的调用,如下:

movq	%rax, -48(%rbp)
	leaq	-80(%rbp), %rdi
	callq	_objc_retainBlock
	movq	%rax, -40(%rbp)
	movq	16(%rax), %rcx

我们再来看看_objc_retainBlock的实现:

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

其实就是对Block执行了copy,将栈上的Block拷贝到了堆上,我们继续看看内部实现,

void *_Block_copy(const void *arg) {
    return _Block_copy_internal(arg, WANTS_ONE);
}

最终是调用的_Block_copy_internal函数:

源码在这里:http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntime/runtime.c

/* Copy, or bump refcount, of a block.  If really copying, call the copy helper if present. */
static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;

    if (!arg) return NULL;

    aBlock = (struct Block_layout *)arg;

       //如果block是创建在堆上,引用计数加1,并返回当前block
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    //这个不用管
    else if (aBlock->flags & BLOCK_IS_GC) {
        // GC refcounting is expensive so do most refcounting here.
        if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 1)) {
            // Tell collector to hang on this - it will bump the GC refcount version
            _Block_setHasRefcount(aBlock, true);
        }
        return aBlock;
    }
    //如果block是全局的,直接返回
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

    //如果block是栈上的,那么拷贝到堆上
    if (!isGC) {
        //根据block的大小在堆上申请一块内存
        struct Block_layout *result = malloc(aBlock->descriptor->size);
        if (!result) return (void *)0;
        //对result初始化,将栈上的block的数据拷贝到堆上创建的block中去
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
        // 重新设置标识符和isa,表示result为堆上创建的block
        result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 1;
        result->isa = _NSConcreteMallocBlock;
        //如果block有copy和dispose实现,则调用copy方法,也就是我们之前说到的__main_block_desc_0中多出来的copy和dispose
        if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
            //printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
            (*aBlock->descriptor->copy)(result, aBlock); // do fixup
        }
        return result;
    }
       //以下请无视
    else {
        //…
    }
}

那么我们来看看copy方法的实现

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

里面调用了_Block_object_assign,我们通过svn co http://llvm.org/svn/llvm-project/compiler-rt/trunk compiler-rt获取源码

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);
    }
}
enum {
    /* See function implementation for a more complete description of these fields and combinations */
    BLOCK_FIELD_IS_OBJECT   =  3,  /* id, NSObject, __attribute__((NSObject)), block, ... */
    BLOCK_FIELD_IS_BLOCK    =  7,  /* a block variable */
    BLOCK_FIELD_IS_BYREF    =  8,  /* the on stack structure holding the __block variable */
    BLOCK_FIELD_IS_WEAK     = 16,  /* declared __weak, only used in byref copy helpers */
    BLOCK_BYREF_CALLER      = 128  /* called from __block (byref) copy/dispose support routines. */
};
static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
        
    if (src->forwarding->flags & BLOCK_IS_GC) {
        ;   // don't need to do any more work
    }
    //前面通过forwarding初始化的时候flags是置0的
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        //printf("making copy\n");
        // src points to stack
        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
        // if its weak ask for an object (only matters under GC)
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        //这两条为关键语句,解释了为什么block外的i和初始的i地址发生了变化,将堆上的i的forwarding指向自己,将栈上的i的forwarding指向了堆上的i,这样无论通过栈还是堆上的i的forwarding访问的都是堆上的i,因此block创建以后,block外和block内的i的地址,编译器源码看到都是通过&(i.__forwarding->i)来访问的,因此最后输出的都是堆上的i的i实例变量的地址,而初始的i输出的地址还是栈上的i的i实例变量的地址。这样,就实现了无论__block变量配置在栈上还是堆上,都能够正确访问__block变量。
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        if (isWeak) {
            copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning
        }
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            copy->byref_keep = src->byref_keep;
            copy->byref_destroy = src->byref_destroy;
            (*src->byref_keep)(copy, src);
        }
        else {
            // just bits.  Blast 'em using _Block_memmove in case they're __strong
            _Block_memmove(
                (void *)&copy->byref_keep,
                (void *)&src->byref_keep,
                src->size - sizeof(struct Block_byref_header));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    // assign byref data block pointer into new Block
    _Block_assign(src->forwarding, (void **)destp);
}

最终执行的是_Block_assign。相当于值拷贝。

static void _Block_assign_default(void *value, void **destptr) {
    *destptr = value;
}

前面讲到Block有以下三种类型,

_NSConcreteStackBlock 存储在栈上:ARC下weak指针指向的block还在栈上

_NSConcreteMallocBlock 存储在堆上: _NSConcreteMallocBlock只需要对_NSConcreteStackBlock进行copy操作就可以获取,ARC下由于对象创建默认都是__strong类型的,因此创建block也一样,会直接在堆上创建

_NSConcreteGlobalBlock 存储在数据区:1.全局的Block;2.不捕获自动变量(也就是Block内部没有用到自动变量,可以使用全局变量,静态全局变量和静态局部变量)


我们再来看看哪些情况编译器会自动将Block从栈复制到堆上

typedef int (^blk_t)(int);

blk_t func(int rate)
{
    return ^(int count){return rate*count;};
}

汇编源码如下:

Ltmp2:
	.cfi_def_cfa_register %rbp
	subq	$48, %rsp
	movl	%edi, -4(%rbp)
	movq	__NSConcreteStackBlock@GOTPCREL(%rip), %rax
	movq	%rax, -40(%rbp)
	movl	$-1073741824, -32(%rbp) ## imm = 0xFFFFFFFFC0000000
	movl	$0, -28(%rbp)
	leaq	___func_block_invoke(%rip), %rax
	movq	%rax, -24(%rbp)
	leaq	___block_descriptor_tmp(%rip), %rax
	movq	%rax, -16(%rbp)
	movl	-4(%rbp), %edi
	movl	%edi, -8(%rbp)
	leaq	-40(%rbp), %rdi
	callq	_objc_retainBlock
	movq	%rax, %rdi
	addq	$48, %rsp
	popq	%rbp
	jmp	_objc_autoreleaseReturnValue ## TAILCALL

可见,Block作为函数返回值返回时,编译器会自动生成复制到堆上的代码

还有两种情况我们不需要手动copy

Cocoa框架的方法且方法中含有usingBlock等时

GCD的API

其他的情况比如向方法或函数的参数中传递Block时都要手动copy我们的Block。

下面举个例子:

typedef void (^blk_t)(void);
id getBlockArray()
{   
    int val = 10;
    return [[NSArray alloc]initWithObjects:^{NSLog(@"block1:%d",val);},^{NSLog(@"block2:%d",val);}, nil];
}

运行如下代码:

 id array = getBlockArray();
    blk_t block = (blk_t)[array objectAtIndex:0];
    block();

崩溃,因为栈上的Block在函数结束时被废弃,array实际是空的。

修改如下即可:

id getBlockArray()
{
    int val = 10;
    return [[NSArray alloc]initWithObjects:[^{NSLog(@"block1:%d",val);} copy],[^{NSLog(@"block2:%d",val);} copy], nil];
}

总结一下什么时候栈上的Block会复制到堆上:

1.调用Block的copy实例方法时

2.Block作为函数返回值返回时

3.将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时

4.在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时


前面的源代码我们看到了,对栈上的Block执行copy,会复制到堆上,堆上的Block执行copy,会增加引用计数,全局的Block执行copy,啥也不做。

那么不管Block配置在哪里,用copy方法复制都不会引起任何问题,因此在不确定的情况下,使用copy即可,下面我们来验证一下:

typedef void (^blk_t)(void);

int main() {
    int var = 10;
    blk_t block = ^{NSLog(@"block-%d",var);};
    [block copy];
    [block copy];
    [block copy];
}

汇编源代码如下:

Ltmp2:
	.cfi_def_cfa_register %rbp
	subq	$64, %rsp
	leaq	-56(%rbp), %rax
	leaq	___block_descriptor_tmp(%rip), %rcx
	leaq	___main_block_invoke(%rip), %rdx
	movq	__NSConcreteStackBlock@GOTPCREL(%rip), %rsi
	movl	$10, -4(%rbp)
	movq	%rsi, -56(%rbp)
	movl	$-1073741824, -48(%rbp) ## imm = 0xFFFFFFFFC0000000
	movl	$0, -44(%rbp)
	movq	%rdx, -40(%rbp)
	movq	%rcx, -32(%rbp)
	movl	-4(%rbp), %edi
	movl	%edi, -24(%rbp)
	movq	%rax, %rdi
	callq	_objc_retainBlock
	movq	%rax, -16(%rbp)
	movq	-16(%rbp), %rax
	movq	L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
	movq	%rax, %rdi
	callq	_objc_msgSend
	movq	%rax, %rdi
	callq	_objc_release
	movq	-16(%rbp), %rax
	movq	L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
	movq	%rax, %rdi
	callq	_objc_msgSend
	movq	%rax, %rdi
	callq	_objc_release
	movq	-16(%rbp), %rax
	movq	L_OBJC_SELECTOR_REFERENCES_(%rip), %rsi
	movq	%rax, %rdi
	callq	_objc_msgSend
	movq	%rax, %rdi
	callq	_objc_release
	xorl	%r8d, %r8d
	movl	%r8d, %esi
	leaq	-16(%rbp), %rax
	movq	%rax, %rdi
	callq	_objc_storeStrong
	xorl	%eax, %eax
	addq	$64, %rsp
	popq	%rbp
	retq

可以看到首先在栈上创建了Block,ARC环境会通过调用_objc_retainBlock来copy到堆上,然后下面三次调用copy和release操作,最后作用域结束,调用_objc_storeStrong来将Block清空。


下面我们再来看看Block截获对象的时候是什么情况

id array = [[NSMutableArray alloc]init];
    void (^myBlock)(void) = ^{
        
        id obj = [[NSObject alloc]init];
        [array addObject:obj];
        
    };

    myBlock();

编译器源码如下:

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


        id obj = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
        ((void (*)(id, SEL, ObjectType))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);

    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 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};
int main() {
    id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

这里给_Block_object_assign方法传递的是BLOCK_FIELD_IS_OBJECT(截获对象时),源码实现中,调用了_Block_retain_object。也就是说,Block对捕获的对象会retain(没有__block修饰符的时候),这也就是导致循环引用的原因。

我们继续执行以下代码:

id array = [[NSMutableArray alloc]init];
    void (^myBlock)(void) = ^{
        
        array = [[NSMutableArray alloc]init];
        
    };

    myBlock();

编译器报错,可见,使用截获的对象是没有问题的,但是赋值给截获的自动变量就会产生编译错误。

下面我们修改如下:

__block id array = [[NSMutableArray alloc]init];
    void (^myBlock)(void) = ^{
        
        array = [[NSMutableArray alloc]init];
    };
    
    myBlock();

编译器源码如下:

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 __Block_byref_id_object_dispose_131(void *src) {
 _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

struct __Block_byref_array_0 {
  void *__isa;
__Block_byref_array_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id array;
};

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


        (array->__forwarding->array) = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main() {
    __attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"))};
    void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_array_0 *)&array, 570425344));

    ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}

可见,只要是通过__block修饰的变量,编译器都会将其进行新的封装,并且给_Block_object_assign方法传递的是BLOCK_FIELD_IS_BYREF(使用__block变量时)。

33554432,就是0x2000000,也就是上面的BLOCK_HAS_COPY_DISPOSE

我们继续把源码贴过来加以分析:

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    struct Block_byref **destp = (struct Block_byref **)dest;
    struct Block_byref *src = (struct Block_byref *)arg;
        
    if (src->forwarding->flags & BLOCK_IS_GC) {
        ;   // don't need to do any more work
    }
    //array的flags是0x2000000,也就是上面的BLOCK_HAS_COPY_DISPOSE,所以跳进来
    else if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        
        bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
        
        //这里有个类型转换,下面调用byref_keep会用到
        struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
        copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
        //这两条为关键语句,这里我们可以自行打印array的地址,会发现和之前的i一样,Block创建以后,Block外array的地址和Block内array的地址一样,将堆上Block重的array的forwarding指向自己,将原始的堆上的array的forwarding指向了堆上的Block中的array,这样无论通过原始的array还是堆上的Block的array的forwarding访问的都是堆上Block中的array,因此block创建以后,block外和block内的i的地址,编译器源码看到都是通过&(array.__forwarding->array)来访问的,因此最后输出的都是堆上Block的array的array实例变量的地址,而初始的array输出的地址还是原始的array的array实例变量的地址。这样,就实现了无论__block变量配置在栈上还是堆上,都能够正确访问__block变量。
        copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
        if (isWeak) {
            //…
        }
        //这里是和之前i不同的地方,当__block变量是对象的时候,会跳进来,这里的byref_keep就是__Block_byref_id_object_copy_131,byref_destroy就是__Block_byref_id_object_dispose_131,因此这里调用了__Block_byref_id_object_copy_131,131就是0x83,也就是上面的BLOCK_FIELD_IS_OBJECT| BLOCK_BYREF_CALLER,因此最终只是调用了_Block_assign,没有执行retain操作,这也是我们在MRC下使用__block避免循环引用的原因了。最后温馨提示,尽量不要用__block来避免循环引用,虽然,在Block中将__block变量置为nil能避免循环引用,但是如果你忘记执行Block了,就会循环引用并引发内存泄漏。
        if (src->flags & BLOCK_HAS_COPY_DISPOSE) {
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            copy->byref_keep = src->byref_keep;
            copy->byref_destroy = src->byref_destroy;
            (*src->byref_keep)(copy, src);
        }
        else {
           //…
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_NEEDS_FREE) == BLOCK_NEEDS_FREE) {
        //…
    }
    // assign byref data block pointer into new Block
    _Block_assign(src->forwarding, (void **)destp);
}


最后要注意C语言数组不能被Block截获,解决办法是使用指针。




猜你喜欢

转载自blog.csdn.net/junjun150013652/article/details/53321552