iOS底层探索--block底层分析

625 阅读6分钟

小谷底层探索合集

  • block这个东西--基本就和我们生活息息相关了,😆,毕竟用的太多了。今天兄弟们一起探究下block到底是个啥?🐯

1. block分类

  • 真男人从不拐弯抹角。上——代码

1.1. 全局block

    void (^block)(void) = ^{
        NSLog(@"xiaogu");
    };
    NSLog(@"%@",block);
  • 输出:

当没有外部变量时是 全局block

1.2. 堆区block

    int a = 10;
    void (^block)(void) = ^{
        NSLog(@"xiaogu -- %d",a);
    };
    NSLog(@"%@",block);
  • 输出:

有外部变量引入之后会变成 堆block

1.3. 栈block

    int a = 10;
    void (__weak ^block)(void) = ^{
        NSLog(@"xiaogu -- %d",a);
    };
    NSLog(@"%@",block);
  • 输出:

__weak修饰之后,变成了栈block

2. block 循环引用

我赌我全部财产:所有使用block的兄弟们,都经历过循环引用

    1. 循环引用(我喜欢说大白话)就是:我持有你,你持有我。结果放不开了~
    1. 举个例子:
typedef void(^XGBlock)(void);
@interface XGTestVC ()
@property(nonatomic, copy)XGBlock block;
@property(nonatomic,copy)NSString * name;
@end

//实现
    self.name = @"xiaogu";
    self.block = ^(void){
        NSLog(@"-%@-",self.name);
    };
    self.block();

这就造成了循环引用 self —持有--> block --持有—>self

    1. 测试的话,可以写个dealloc方法,看退出界面是否释放
- (void)dealloc
{
    NSLog(@"dealloc");
}

2.1. 解决方案

  • 当我们遇到问题时,都会有各种各样的解决方法。今天和兄弟们简单说几种

2.1.1. 强弱共舞(strong - weak - dance

兄弟们肯定都听说这个词。今天我来献一波丑~

    1. 刚开始做iOS的兄弟们想到的办法就是解决依赖,然后来一波weakSelf
    __weak typeof(self) weakSelf = self;
    self.block = ^(void){
        NSLog(@"-%@-",weakSelf.name);
    };

然后发现真的解决了循环引用,就不管了~ 其实这样是有问题的

因为weakSelf这样加在了弱引用表,你不知道什么时候释放。如果block中有耗时操作,那么这个可能就是nil

    1. 强弱共舞(strong - weak - dance),来吧~ 展示~
    __weak typeof(self) weakSelf = self;
    self.block = ^(void){
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        //耗时操作啥的
        NSLog(@"-%@-",strongSelf.name);
    };

这就是强弱共舞strongSelf的生命周期会在出去 block代码块 释放,这是一种完美的解决方案(我感觉)~因为我喜欢,😆

2.1.2. 中间者模式 -- 手动释放

当然,也有兄弟们不喜欢强弱共舞,那也可以手动释放

  • __block可以修改外部变量。那么我这么做
    __block XGTestVC *tvc = self;

    self.block = ^(void){

        //耗时操作啥的

        NSLog(@"-%@-",tvc.name);

        //用完了就置nil
        tvc = nil;
    };

这也可以解决循环引用。毕竟tvc是有作用域的,出了作用域就释放了

2.1.3. 传值模式

    1. 这中就没啥好说的了,就是传过来个值,他没有持有,就不会造成循环引用了
    1. 如果如果循环引用,我更倾向 强弱共舞 的解决方便,这让人很舒服毕竟,😆

3. block结构探索

上面那些解决方法很简单的说了下,接下来才是重头戏~

3.1. 全局block

    1. 我们先写一个比较简单的block

    1. 看过我原先博客的兄弟们,都知道我要怎么看了。 xcrun -sdk iphonesimulator clang -rewrite-objc main.m
    1. 我把得出的.cpp对比了下

其实block就是一个结构体,所以可以@“%@”输出,然后他有自己的构造方法。

~ xgblock->FuncPtr(xgblock);~(xgblock)会议隐藏参数的形式传入

3.2. 堆block

    1. 我们可以看看,如果引入外部参数会怎么样
        int a = 180;
        void (^xgblock)(void) = ^{
            NSLog(@"xiaogu-height-%d",a);
        };
        xgblock();

😆,我们xcrun-clang看下

栈的话就自己看吧,哈哈哈🙄

3.2.1. __block 修饰

兄弟们也比较清楚:在block块中改变外部的值需要用到__block修饰,那我们既然探索了,就看下,__block做了什么

    1. 测试代码
       __block int a = 180;
        void (^xgblock)(void) = ^{
            a++;
            NSLog(@"xiaogu-height-%d",a);
        };
        xgblock();
    1. 我们xcrun-clang看下

    1. 我们看到通过__block修饰之后,多了这么一条。我把完整的给大家看下
    1. 结论:通过__block修饰变量后,会生成一个结构体。然后这个结构体会把变量存起来,更改的时候会改结构体里面的值。(其实就是把原先的变量封装成相应的对象)

4. block 源码分析

4.1. 定位源码

我们研究之前,首先要解决一个问题:就是目前我们还不知道block的源码在哪?观察谁

    1. 首先我们要定位源码。(以下是我的定位过程~)
    1. 我们找到源码之后。就舒服了。(这个源码是可以编译的。所以调试起来十分舒适~)(我用目前最新的源码跟兄弟们一起走一波)

4.2. block 栈—>堆

    1. 我看了好多网上block的博客,都会说:block从栈区,然后copy成了堆区block
    1. 兄弟们,我们要探究底层,当然要感受下_Block_copy做了什么啊。怎么从栈区到堆区的
    1. 我们先写个堆区的block
        __block int a = 10;
        void (^mallocBlock)(void) = ^void {
            a++;
        };
        NSLog(@"MallocBlock is %@", mallocBlock);
    1. 然后通过定位源码过程中,我们知道它会执行_Block_copy(这其实就是关键方法),而且我们可以调试😆,那我们断点下,😆

    1. 然后我们看下他刚进 时的值

现在他还是个栈block

    1. 嘿嘿。😝,我们看看他怎么变成堆block的。我先把代码贴上,也可以帮助下没有源码的兄弟们~
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. — 如果是栈block,进行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;
    }
}
    1. 我的断点调试

通过malloc申请内存空间用于接收block

通过memmoveblock拷贝至新申请的内存中

设置block对象的类型为堆区block,即result->isa = _NSConcreteMallocBlock

4.3. block 三层 copy

据说这个是,面试不咋问,但是和兄弟们喝酒吹牛逼容易说的技术点😆

    1. 当用__block修饰的变量是对象时
        __block NSString *name = [NSString stringWithFormat: @"XG"];
        
        void(^blockCopy)(void) = ^{
            name = @"xiaogu";
            NSLog(@"%@",name);
        };
        blockCopy();
    1. 然后我们用xcrun得到.cpp文件
// name最开始的赋值  __Block_byref_name_0 修饰。(结构体的赋值,跟__Block_byref_name_0一一对应)
 __attribute__((__blocks__(byref))) __Block_byref_name_0 name = {
 (void*)0,
 (__Block_byref_name_0 *)&name,
 33554432,
 sizeof(__Block_byref_name_0),
 __Block_byref_id_object_copy_131,
 __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_6j_hfrbcdq96lsbr8vr1qjw1fkm0000gn_T_main_b45ce1_mi_0)
 
 };


//__Block_byref_name_0的结构体
 struct __Block_byref_name_0 {
   void *__isa;
 __Block_byref_name_0 *__forwarding;
  int __flags;
  int __size;
  void (*__Block_byref_id_object_copy)(void*, void*);
  void (*__Block_byref_id_object_dispose)(void*);
  NSString *name;
 };

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);
}


//block 块
//这里面就不说了~毕竟兄弟们已经有条件探索了,~上面也提过了,会发生内存拷贝。
void(*blockCopy)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_name_0 *)&name, 570425344));
        ((void (*)(__block_impl *))((__block_impl *)blockCopy)->FuncPtr)((__block_impl *)blockCopy);

我是用源调试的,所以比较好理解点

    1. 简单的总结下:通过__block修饰,且变量是对象会发生 三层 copy

    • 3.1. 第一层copy就是 _Block_copy --> 自身的copy --> 从栈—>堆

    • 3.2. 第二层copy_Block_byref_copy-->把修饰的对象,copyBlock_byref结构体类型

    • 3.3.第三层copy:_Block_object_assign,修饰的是对象的时候__Block_byref_id_object_copy_131,对__block修饰的当前对象copy

OK了,,哎,年底了,好忙,我又要去加班了。。o(╥﹏╥)o