iOS block 总结

一、block 分类
block 常规使用下 分为 全局 堆 栈 但是还有系统级别的3种 所以有六种

  1. 全局blok
	void(^block)(void) = ^{
        
    };
    block();
    NSLog(@"---block---%@",block);// <__NSGlobalBlock__: 0x1000c8190>
  1. 堆区
	int a = 10;
    void(^block)(void) = ^{
        NSLog(@"----blocka--%d",a);
    };
    block();
    NSLog(@"---block---%@",block);// <__NSMallocBlock__: 0x600001c3cf90>
  1. 栈区
 NSLog(@"---staticblock---%@",^{
        NSLog(@"----blocka--%d",a);
    });
    // ---staticblock---<__NSStackBlock__: 0x7ffee2a90f70>

二、block 循环引用

	typedef void(^myBlock)(void);
	@property(nonatomic, copy)NSString *name;
	@property(nonatomic, copy)myBlock block;
	self.name = @"123123";
    self.block = ^{
        NSLog(@"----blocka--%@",self.name);
    };
    self.block();

上述代码会产生循环引用 原因 self 持有block block又持有self 这两个在释放的时候回相互等待 所以造成内存泄漏 所以要用__weak 打破引用链

	self.name = @"123123";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        NSLog(@"----blocka--%@",weakSelf.name);
    };
    self.block();

解释 __weak不会引起引用计数的加1 所以self会释放 打破引用链 更完美的写法是

	self.name = @"123123";
    __weak typeof(self) weakSelf = self;
    self.block = ^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        NSLog(@"----blocka--%@",strongSelf.name);
    };
    self.block();

防止在self释放之后再访问name

三、block 底层原理

  1. 本质是什么 通过clang 编译如下简单代码
int main(){
    void(^block)(void) = ^{
        
    };
    block();
    return 0;
}

结果

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;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
}
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(){
    void(*block)(void) = (&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
    (((__block_impl *)block)->FuncPtr)((__block_impl *)block);

从上边代码可以看出 block本质上是一个结构体 构造函数中保存了 isa flags funcPtr desc 将函数的调用保存在了 funcPtr中

  1. 捕获外界变量 再次clang
int main(){
    int a = 10;
    void(^block)(void) = ^{
        printf("------%d",a)
    };
    block();
    return 0;
}
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 此处的a已经不是外部的a了
        printf("------%d",a);
}
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a)); 将a作为参数进行传递

从上边代码看 block 会重新声明一个a 将原来外部的a的值赋值给 block结构体的a

  1. __block 的实现
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
int main(){
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

上边代码 多了几个关键点 1. 多了 (__Block_byref_a_0 *)&a

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

__block 修饰了以后 会多出来一个结构体 结构体中保存了外部的值和指针然后传递给block的结构体 所以此时block内部可以修改外部的变量

四、block 堆栈变化
在这里插入图片描述
开始读寄存器

 register read x0
      x0 = 0x000000016d457a58
(lldb) po 0x000000016d457a58
<__NSStackBlock__: 0x16d457a58>
 signature: "v8@?0"
 invoke   : 0x1029accc4 (/var/containers/Bundle/Application/3D601EBA-87C6-473A-ABCD-BB1B8E8F40CC/KVOTest.app/KVOTest`__22-[FirstVC viewDidLoad]_block_invoke)

0x102e74c48 <+396>: bl 0x102e75ec0 ; symbol stub for: objc_retainBlock 之后再下断点 再读寄存

register read x0
      x0 = 0x0000000282a65140
(lldb) po 0x0000000282a65140
<__NSMallocBlock__: 0x282a65140>
 signature: "v8@?0"
 invoke   : 0x1029accc4 (/var/containers/Bundle/Application/3D601EBA-87C6-473A-ABCD-BB1B8E8F40CC/KVOTest.app/KVOTest`__22-[FirstVC viewDidLoad]_block_invoke)

此时已经变成 堆上block

五、block 源码解读

  1. _Block_copy 源码
void *_Block_copy(const void *arg) {
    struct Block_layout *aBlock;

    if (!arg) return NULL;
    
    // The following would be better done as a switch statement
    aBlock = (struct Block_layout *)arg;
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    else {
        // Its a stack block.  Make a copy.
        struct Block_layout *result =
            (struct Block_layout *)malloc(aBlock->descriptor->size);
        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.
        result->isa = _NSConcreteMallocBlock;
        return result;
    }
}

上述代码 如果是全局block就直接返回 如果是栈上block就执行一步copy。malloc开辟内存

  1. 如何copy的?
		__block NSString *name = [NSString stringWithFormat:@"xiaobing"];
        void(^block)(void) = ^{
            name = @"hehe";
            NSLog(@"--ccc-----%@",name);
        };
        block();

执行 xcrun -sdk iphonesimulator clang -rewrite-objc main.m
看如下关键代码

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

来看_Block_object_assign的源码 下面是我摘录的关键代码

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: // 是否是个对象
        _Block_retain_object(object);
        *dest = object;
        break;

      case BLOCK_FIELD_IS_BLOCK: // 是否是个block
        *dest = _Block_copy(object);
        break;
    
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK: // 是否是__block 修饰的
      case 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:
        *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:
        *dest = object;
        break;

      default:
        break;
    }
}

直接来看_Block_byref_copy的源码 被block修饰过的 只看关键代码

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;
        copy->forwarding = copy; // patch heap copy to point to itself
        src->forwarding = copy;  // patch stack to point to heap copy
        copy->size = src->size;
      (*src2->byref_keep)(copy, src);
    }
    return src->forwarding;
}

上述代码是将 copy出来的和原来的指向同一块内存 所以__block修饰的变量可以在block内部进行修改
block 的拷贝分为三个过程 1. block从栈到堆。2. 通过block_ref结构体对变量进行copy
3. 对block外的变量的值进行copy

  1. 析构过程。
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
    }
}

这里着重看下 BLOCK_FIELD_IS_BYREF 因为这种情况下block 内部生成了一份copy 内存要自己管理 源码如下

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;
    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}

总结 block的使用早都会了 但是对它底层的原理所知甚少 今天终于搞清楚block什么时候从栈到堆 什么情况下是全局block ,并且block 的copy细节 也清楚了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值