Block分析

Block本质

block本质为一个结构体也可以说是一个匿名函数
我们可以利用clang来进行分析

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

我们也可以找到block的源码

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor; //
    // imported variables
};

Block分类

block分为6种

void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };          //常用在系统级别
void * _NSConcreteFinalizingBlock[32] = { 0 }; //常用在系统级别
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 }; //常用在系统级别

常用的有三种:

  1. _NSConcreteStackBlock 栈
  2. _NSConcreteMallocBlock 堆
  3. _NSConcreteGlobalBlock 全局
 void(^block)(void) = ^{
        NSLog(@"=====");
    };
    block();
    NSLog(@"%@",block);

打印结果<__NSGlobalBlock__: 0x10a84e078>GlobalBlock

  • 当block捕获变量之后就会发生变化,变成MallocBlock,如下:
__block int a = 1;
    void(^block)(void) = ^{
        
        NSLog(@"=====%d",a);
    };
    block();
    NSLog(@"%@",block);
  • StackBlock发生在copy之前
    比如
 void(^block)(void) = ^{
        NSLog(@"=====%d",a);
    };
    block();
    NSLog(@"%@",block);
    NSLog(@"%@",^{
        NSLog(@"1233 - %d",a);
        //<__NSStackBlock__: 0x7ffee8278c38>
    });

循环引用问题

typedef void(^Block)(void);
@interface ViewController ()
@property(nonatomic,copy)Block block;
@property(nonatomic,assign)int age;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.block = ^{
        NSLog(@"=====%d",self.age);
    };
    self.block();
    // Do any additional setup after loading the view.
}
@end

这段代码就会引起循环引用问题,self持有了block,block中也持有了self.

解决办法1 __weak 修饰
    __weak typeof(self) weakSelf = self;
    self.block = ^{
         //类似于中介者模式
        //weakself ->self(weak修饰引用计数不增加)->block->self
        NSLog(@"=====%d",weakSelf.age);
    };
  • 声明一个weakself临时变量,weak修饰的引用计数不增加,只是将self放入到弱引用表中,这里dealloc方法正常调用,调用之后self置位nil,这时候循环引用链打断,不在相互持有.

这样写其实还会有问题,来看一下代码


      __weak typeof(self) weakSelf = self;

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"=====%d",weakSelf.age);
        });
        
    };
    self.block();

这个时候我们pop回去了,调用了dealloc,self也进行释放,这个时候在打印age结果就为错的.

__weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
//        strongSelf -> weakself -> self(引用计数不增加 nil) -> block->strongself
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"=====%d",strongSelf.age);
        });
        
    };
    self.block();
  • strongSelf -> weakself -> self(引用计数不增加 dealloc之后将self置位nil,打断循环引用) -> block->strongself
  • 用strong对weakself进行一个临时的强引用,这样就能够实现,在打印完之后再进行析构的效果.
    继续看这样要一段代码
 (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof(self) weakSelf = self;
    self.handler = ^{
        typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"Self is %@", strongSelf);
    };

    NSTimeInterval interval = 6.0;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), weakSelf.handler);
}

- (void)dealloc {
    NSLog(@"Released");
}
  • 问题
    这个时候如果pop回去,dealloc调用,证明已经释放,但是为什么handler还是会执行输出self is null
  • 答疑
    Block 赋值给了handler,这时就把Block拷贝到了堆上,并被handler持有,而作为GCD参数使用,又会将已经在堆上的Block引用计数+1,这时Block的引用计数大于1,即使self释放,Block release 一次,但是他的引用计数仍然大于0,所以没有被废弃,所以会执行
解决办法2 手动置空
__block ViewController *vc = self;
    self.block = ^{
        NSLog(@"=====%d",vc.age);
        vc = nil;
    };
    self.block();
  • vc -> self -> block -> vc 行程一个闭环
  • 在vc = nil的时候打断循环引用
  • 这里注意一定要对block进行调用,不调用block代码永远不执行,循环引用闭环也无法打破
解决办法3 把self当做参数传进来

修改block

typedef void(^Block)(ViewController *);
self.block = ^(ViewController * vc){
        NSLog(@"=====%d",vc.age);
    };
    self.block(self);

当做参数传进来并没有形成持有关系,不会造成循环引用
小结: 解决block循环应用的三种方式

  1. 用__weak修饰
  2. 声明临时变量__block VC = self 并在block中手动置位nil
  3. 把self当做一个参数传进去

Block为什么要block()进行调用

我们利用clang命令来进行分析
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
//将block也就是fp传进来然后赋值给FuncPtr
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//block中的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
          printf("block");
        }
int main(int argc, char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        //简化代码  构造函数将block中代码也就是__main_block_func_0传进去
      //  void(*block)(void) = __main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
      //简化代码
      block->FuncPtr(block);
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

从以上代码中我们可以得出,

  • __main_block_func_0函数中代码就是我们代码块中的代码,这里将__main_block_func_0通过构造函数传进去,并保存在FuncPtr中并没有调用.这里只是声明
  • 具体的函数实现需要手动调用

Block为什么能够捕获外部变量

 int a = 1;
 void(^block)(void) = ^{
     printf("block=====%d",a);
 };
 block();

重新clang

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
       printf("block=====%d",a);
  }
int main(int argc, char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int a = 1;
        void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));
        block->FuncPtr(block);
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

发现__main_block_impl_0中多了一个属性a.
__main_block_func_0中重新定义了一个局部变量a = __cself->a,其实这里相当于进行了一个值copy,传进来的a跟新加的属性a是不一样的.所以这里是修改不了捕获的变量的
小结:

  • block会自动生成一个属性来捕获外界变量
  • 没有经过__block修饰的变量a和新生成的属性a,地址是不一样的,所以block中修改a跟外界的变量是没有关系的

__block原理

__block int a = 1;修饰之后继续clang

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref

            printf("block=====%d",(a->__forwarding->a));
        }
int main(int argc, char * argv[]) {

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
            (void*)0,
            (__Block_byref_a_0 *)&a,
            0,
            sizeof(__Block_byref_a_0),
            1};
        void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &a, 570425344));
        block->FuncPtr(block);
        return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
    }
}

__block之后会生成 一个__Block_byref_a_0的结构体,然后进行初始化.将外界a的变量指针保存在__forwarding中.将值保存在__Block_byref_a_0的属性a中.
然后在调用的时候

//这里是进行了指针拷贝,block内部的a和外界变量a是同一个地址
__Block_byref_a_0 *a = __cself->a; // bound by ref

小结:

  • __block修饰之后,会生成相应的结构体__Block_byref_a_0,保存了原始变量的指针和值
  • 调用的时候传递一个指针地址给block,所以经过__block修饰之后就可以对外界变量进行修改

Block结构

block源码

// 可选
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor; //
    // imported variables
//下边可能有Block_descriptor_2 Block_descriptor_3
};

为什么说可能有2和3呢,因为他是根据一个枚举来进行动态添加的(2 BLOCK_HAS_COPY_DISPOSE ; 3 BLOCK_HAS_SIGNATURE)

    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

通过调用一下方法进行判断添加

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
    uint8_t *desc = (uint8_t *)aBlock->descriptor;
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}

sizeof内存大小,那么我们可以通过内存偏移来进行读取.

Block内存变化

打断点

打断点然后看汇编,汇编会来到objc_retainBlock,我们在这里打断点,然后读取寄存器,

(lldb) register read x0
      x0 = 0x0000000100cc0038  004-Block结构与签名`__block_literal_global
(lldb) po 0x0000000100cc0038
<__NSGlobalBlock__: 0x100cc0038>
 signature: "v8@?0"
 invoke   : 0x100cbe198 (/private/var/containers/Bundle/Application/E874D6A7-6D8F-49D1-A88C-D65A9A862D99/004-Block结构与签名.app/004-Block结构与签名`__29-[ViewController viewDidLoad]_block_invoke)

是一个_NSGlobalBlock_.

 NSString *name = @"JasonLee";
    void (^block1)(void) = ^{
        NSLog(@"LG_Block - %@",name);
    };
    block1();

访问外界变量,再来看汇编

 register read x0
      x0 = 0x000000016fa49ba8
(lldb) po 0x000000016fa49ba8
<__NSStackBlock__: 0x16fa49ba8>
 signature: "v8@?0"
 invoke   : 0x1003ba16c (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__29-[ViewController viewDidLoad]_block_invoke)
 copy     : 0x1003ba198 (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__copy_helper_block_e8_32s)
 dispose  : 0x1003ba1a0 (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__destroy_helper_block_e8_32s)

此时变成了__NSStackBlock__.
我们继续stepIn一步一步往下走,最终来到_Block_copy,

libsystem_blocks.dylib`_Block_copy:
->  0x1981ef838 <+0>:   pacibsp 
    0x1981ef83c <+4>:   stp    x22, x21, [sp, #-0x30]!
    0x1981ef840 <+8>:   stp    x20, x19, [sp, #0x10]
    0x1981ef844 <+12>:  stp    x29, x30, [sp, #0x20]
    0x1981ef848 <+16>:  add    x29, sp, #0x20           
    .
    .
    .
   0x1981ef944 <+268>: b      0x1981ef94c               ; <+276>
    0x1981ef948 <+272>: mov    x20, #0x0
    0x1981ef94c <+276>: mov    x0, x20
    0x1981ef950 <+280>: ldp    x29, x30, [sp, #0x20]
    0x1981ef954 <+284>: ldp    x20, x19, [sp, #0x10]
    0x1981ef958 <+288>: ldp    x22, x21, [sp], #0x30
    0x1981ef95c <+292>: retab  

最后一行return出断点,继续读取寄存器

(lldb) register read x0
      x0 = 0x00000002823c49c0
(lldb) po 0x00000002823c49c0
<__NSMallocBlock__: 0x2823c49c0>
 signature: "v8@?0"
 invoke   : 0x1003ba16c (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__29-[ViewController viewDidLoad]_block_invoke)
 copy     : 0x1003ba198 (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__copy_helper_block_e8_32s)
 dispose  : 0x1003ba1a0 (/private/var/containers/Bundle/Application/4B6171C1-7629-48A6-B992-3F63E26E2EDF/004-Block结构与签名.app/004-Block结构与签名`__destroy_helper_block_e8_32s)

此时block变为__NSMallocBlock__堆block
小结:

  • 当block访问外界变量时会从全局block变为栈block然后在进行block_copy之后变为堆block

block签名

从上边的读取寄存器我们可以看到signature: "v8@?0",证明block是有签名的

BlockCopy(第一层拷贝)

我们知道block怎么从栈去copy到堆区呢,刚才我们汇编也看到了,当捕获外界变量时会调用_Block_copy,来看下源码

// 栈 -> 堆 研究拷贝
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;
//判1空
    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
//强转
    aBlock = (struct Block_layout *)arg;
//block的内存管理 不受底层runtime管理,自己管理
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
//如果已经是一个全局block,则直接return
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
//重点来了
        // Its a stack block.  Make a copy.
//重新申请一个跟aBlock大小一样的空间地址
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
//判空如果申请地址失败直接返回NULL
        if (!result) return NULL;
//接下来就是平移,拷贝操作
        memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;
#endif
        // reset refcount
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        _Block_call_copy_helper(result, aBlock);
        // Set isa last so memory analysis tools see a fully-initialized object.
//然后将block标记为堆block
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

static int32_t latching_incr_int(volatile int32_t *where) {
    while (1) {
        int32_t old_value = *where;
        if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
            return BLOCK_REFCOUNT_MASK;
        }
//这里说下为什么要+2 而不是+1 ,因为这里0X1代表的是一个BLOCK_DEALLOCATING是否正在析构的枚举
/**
// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};

*/
        if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
            return old_value+2;
        }
    }
}

第二层拷贝(捕获变量的内存空间)

重新回到clang的cpp文件

void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0,
         &__main_block_desc_0_DATA, 
        (__Block_byref_lg_name_0 *)&lg_name, 
        570425344)
);

必定会调用__main_block_desc_0_DATA,看下他是个什么东西

__main_block_desc_0_DATA = { 0, 
      sizeof(struct __main_block_impl_0),
       __main_block_copy_0, 
      __main_block_dispose_0
}

然后调用__main_block_copy_0

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

那么来看看_Block_object_assign的源码 注意这里传进来的是个8/BLOCK_FIELD_IS_BYREF/


void _Block_object_assign(void *destArg, const void *object, const int flags) {
    const void **dest = (const void **)destArg;
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_OBJECT:
        /*******
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /*******
        void (^object)(void) = ...;
        [^{ object; } copy];
        ********/

        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        /*******
         // copy the onstack __block container to the heap
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __block ... x;
         __weak __block ... x;
         [^{ x; } copy];
         ********/
           //根据传进来的flag BLOCK_FIELD_IS_BYREF 代码回来到这行
        *dest = _Block_byref_copy(object);
        break;
        
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
        /*******
         // copy the actual field held in the __block container
         // Note this is MRC unretained __block only. 
         // ARC retained __block is handled by the copy helper directly.
         __block id object;
         __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        /*******
         // copy the actual field held in the __block container
         // Note this __weak is old GC-weak/MRC-unretained.
         // ARC-style __weak is handled by the copy helper directly.
         __weak __block id object;
         __weak __block void (^object)(void);
         [^{ object; } copy];
         ********/

        *dest = object;
        break;

      default:
        break;
    }
}
/**
// Runtime entry points for maintaining the sharing knowledge of byref data blocks.

// A closure has been copied and its fixup routine is asking us to fix up the reference to the shared byref data
// Closures that aren't copied must still work, so everyone always accesses variables after dereferencing the forwarding ptr.
// We ask if the byref pointer that we know about has already been copied to the heap, and if so, increment and return it.
// Otherwise we need to copy it and update the stack forwarding pointer
*/
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // src points to stack
        struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
        copy->isa = NULL;
        // byref value 4 is logical refcount of 2: one for caller, one for stack
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        
        //✔️ 问题 - __block 修饰变量 block具有修改能力
//这里无论是在原来的区域还是在新的区域同时指向同一个对象,所以block具有了修改能力
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        
        copy->size = src->size;

        if (src->flags & BLOCK_BYREF_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
            struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
            struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
            copy2->byref_keep = src2->byref_keep;
            copy2->byref_destroy = src2->byref_destroy;

            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
                struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
                struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
                copy3->layout = src3->layout;
            }

            (*src2->byref_keep)(copy, src);
        }
        else {
            // Bitwise copy.
            // This copy includes Block_byref_3, if any.
            memmove(copy+1, src+1, src->size - sizeof(*src));
        }
    }
    // already copied to heap
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    return src->forwarding;
}

第三层copy

继续看clang之后的代码

 __Block_byref_lg_name_0 lg_name = {
            (void*)0,
            (__Block_byref_lg_name_0 *)&lg_name,
            33554432,
            sizeof(__Block_byref_lg_name_0),
            __Block_byref_id_object_copy_131, == keep
            __Block_byref_id_object_dispose_131,
            
            ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_5s_4100t0cd5rn_d7gx0n5wqh8w0000gn_T_main_0098f0_mi_0)};
        // bref 结构体
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

这里为什么要看__Block_byref_id_object_copy_131 呢? 在_Block_byref_copy中有这样一行代码(*src2->byref_keep)(copy, src);
byref_keep是个什么呢

struct Block_byref {
    void *isa;
    struct Block_byref *forwarding;
    volatile int32_t flags; // contains ref count
    uint32_t size;
};

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep;
    BlockByrefDestroyFunction byref_destroy;
};

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

跟之前说的block结构体是一样的根据flag判断是否有Block_byref_2 Block_byref_3. __Block_byref_id_object_copy_131在第五个位置,那么也就是说__Block_byref_id_object_copy_131 = keep.
_Block_object_assign我们熟悉,刚研究过
那么为什么_Block_object_assign((char*)dst + 40, *(void * ) ((char)src + 40), 131); 这里要加上40呢,我们继续回来看看__block修饰以后生成的结构体

struct __Block_byref_lg_name_0 {
  void *__isa;                  //8字节
__Block_byref_lg_name_0 *__forwarding;                //8字节
 int __flags;                //4字节
 int __size;                //4字节
 void (*__Block_byref_id_object_copy)(void*, void*);                 //8字节
 void (*__Block_byref_id_object_dispose)(void*);                //8字节
 NSString *lg_name;
};

5 * 8 = 40 内存便宜40正好得到我们的name.找到我们要修改的对象,然后在进行对象copy.这就是我们的block的三层拷贝.

总结:
block分类

block分为6中,常用的有

  • _NSConcreteStackBlock 栈
  • _NSConcreteMallocBlock 堆
  • _NSConcreteGlobalBlock 全局 三种
解决block循环应用的三种方式
  1. 用__weak修饰
  2. 声明临时变量__block VC = self 并在block中手动置位nil
  3. 把self当做一个参数传进去
block内存变化

当block访问外界变量时会从全局block变为栈block然后在进行block_copy之后变为堆block

block签名

从上边的读取寄存器我们可以看到signature: "v8@?0",证明block是有签名的

block捕获外界变量
  • block会自动生成一个属性来捕获外界变量
  • 没有经过__block修饰的变量a和新生成的属性a,地址是不一样的,所以block中修改a跟外界的变量是没有关系的
  • 经过__block修饰,之后会发生三层拷贝,这时候存储的不在是值,而是捕获对象的指针.拷贝之后的指针和原指针指向的是同一片地址所以block才具有了修改的能力.

你可能感兴趣的:(Block分析)