iOS Block底层解析一

2020-04-13  本文已影响0人  KOLee

一、为啥要自己跟着解析

二、底层解析过程

void test1() {
    void(^block)(void) =  ^{
         NSLog(@"fuck world");
     };
     block();
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test1();
    }
    return 0;
}

然后命令行cd到这个文件目录,在跑xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名称.m 然后你会发现有一个.cpp的文件 然后拉到代码最下面,.cpp代码如下:

// 因为我是main函数调用test1的 从这里开始 第一步
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;  // 自动释放池 
        test1();// 方法调用
    }
    return 0;
}

// 第二步
void test1() {
    // 代码简化后 __test1_block_impl_0j就是block底层的结构体 括号里面的为参数
    void(*block)(void) = &__test1_block_impl_0(__test1_block_func_0, &__test1_block_desc_0_DATA);
    
     block->FuncPtr(block); // FuncPtr指向block
}

// 第四步
struct __block_impl {
  void *isa; // 指向类对象
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 第三步
struct __test1_block_impl_0 {
  struct __block_impl impl; // __block_impl类型结构体
  struct __test1_block_desc_0* Desc; // __test1_block_desc_0类型结构指针
    // 方法函数调用
  __test1_block_impl_0(void *fp, struct __test1_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// 先不关注
static void __test1_block_func_0(struct __test1_block_impl_0 *__cself) {
         NSLog((NSString *)&__NSConstantStringImpl__var_folders_r3_94b6gvh96kj3bgwxst32yjh80000gn_T_main_71fc4b_mi_0);
     }
// 先不关注
static struct __test1_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __test1_block_desc_0_DATA = { 0, sizeof(struct __test1_block_impl_0)};

  1. 自动释放池 这鬼东西不理先 我们今天只搞block,main函数 调用test1()
    2.__test1_block_impl_0一个指针调用后面跟的参数,注释也写清楚了就走个流程
    3.就是block的主要功能 封装了函数和调用环境的地方
    4.__block_impl结构体里面包含了一个isa指针,那么block本质就是一个封装了函数和调用环境的对象,就是这样了 后面都只是比较性质的了
void test2() {
    void(^block)(int,int) =  ^(int a, int b){
        NSLog(@"a = %d, b = %d",a,b);
    };
    block(20,20);
}
clang结构与没带参数的区别 只是多了两个参数 其他一样
void test2() {
    void(*block)(int,int) = &__test2_block_impl_0((void *)__test2_block_func_0, &__test2_block_desc_0_DATA));
   block->FuncPtr(block, 20, 20);
}

三、捕获变量的Block解析过程

//捕获auto变量
void test3() {
     // 局部变量默认 auto 修饰
    int age = 10;  // 相当于 auto int age = 10;
    void(^block)(void) =  ^{
        NSLog(@"age is %d",age);
    };
    age = 20;
    block();
}
// 第三步
void test3() {
    int age = 10;
    // 传age的值进去
    void(*block)(void) = ((void (*)())&__test3_block_impl_0((void *)__test3_block_func_0, &__test3_block_desc_0_DATA, age));
    // 这个是block后面的赋值 没有赋值进block里面 所以修改不了block里面age的值
    age = 20;
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
// 第四步
struct __test3_block_impl_0 {
  struct __block_impl impl;
  struct __test3_block_desc_0* Desc;
  int age; // 多出来的 age 变量
     // int _age, int flags=0) : age(_agez)这里赋值
  __test3_block_impl_0(void *fp, struct __test3_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

1.局部变量的作用域出了函数的大括号就释放了,而block什么时候调用呢,不知道 ,万一局部变量释放了,还调用block那不GG了,所以苹果直接把他传进去放在block的结构里面

void test4() {
    static int age = 10;
     void(^block)(void) =  ^{
         NSLog(@"age is %d",age);
     };
     age = 20;
     block();
}
打印结果:
2020-04-13 16:23:28.410241+0800 blockTest[3000:112691] age is 20

-还是clang一下看看代码 我们还是只看代码不同的地方 如下:

// 第二步
void test4() {
    static int age = 10;
    // &age指针引用
     void(*block)(void) = &__test4_block_impl_0((void *)__test4_block_func_0, &__test4_block_desc_0_DATA, &age));
     age = 20;
     ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
// 第三步
struct __test4_block_impl_0 {
  struct __block_impl impl;
  struct __test4_block_desc_0* Desc;
  int *age; // 指针类型属于地址传递
  __test4_block_impl_0(void *fp, struct __test4_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

1.为啥是20 因为传进去的是static int age = 10;的地址,跟这个指向的是同一块内存地址,所以外面的age变了,block里面的age也跟着变
2.为啥不是直接把值传递进去呢? 是因为static 是静态变量,static修饰的意思就是有且只有一份的意思,所以直接传指针,反正我是这样理解 苹果就是这样

//全局变量

static int age = 10;
int height = 30;
void test5() {
    void(^block)(void) =  ^{
        NSLog(@"age is %d,height is %d",age,height);
    };
    age = 20;
    height = 40;
    block();
}
打印结果:
2020-04-13 16:39:05.945902+0800 blockTest[3070:119722] age is 20,height is 40

1.clang的代码不用看了,为啥?根据苹果的尿性 ,想想 全局变量 作用域是全局的,类里面随时可以访问,还引用个屁啊,直接拿来用就是了
2.同学们可以自己看看,跟平常函数调用全局变量一个鬼样,底层代码不会有任何的不同

 // 对象类型
void test6() {
    Person *p = [Person new];
    p.name = @"hello block!";
    void(^block)(void) = ^{
        NSLog(@"--- %@",p.name);
    };
    block();
}
打印结果:
2020-04-13 16:42:14.056053+0800 blockTest[3093:121623] --- hello block!
// 第二步
void test6() {

    // person初始化 objc_msgSend
    Person *p = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("new"));
    //  p.name = @"hello block!"; setName 方法
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)p, sel_registerName("setName:"), (NSString *)&__NSConstantStringImpl__var_folders_r3_94b6gvh96kj3bgwxst32yjh80000gn_T_main_31b833_mi_0);
    // 开始调用block
    void(*block)(void) = &__test6_block_impl_0((void *)__test6_block_func_0, &__test6_block_desc_0_DATA, p, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
// 第三步
struct __test6_block_impl_0 {
  struct __block_impl impl;
  struct __test6_block_desc_0* Desc;
  Person *p; // 拿到对象的指针
    // Person *_pz对象的指针传过来
  __test6_block_impl_0(void *fp, struct __test6_block_desc_0 *desc, Person *_p, int flags=0) : p(_p) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

// 第五步 copy调用 _Block_object_assign
static void __test6_block_copy_0(struct __test6_block_impl_0*dst, struct __test6_block_impl_0*src) {_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// 第六步 dispose 调用 _Block_object_dispose
static void __test6_block_dispose_0(struct __test6_block_impl_0*src) {_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);}

// 第四步
static struct __test6_block_desc_0 {
  size_t reserved;
  size_t Block_size;
    /*
     生成了copy和 dispose的两个函数指针
     */
  void (*copy)(struct __test6_block_impl_0*, struct __test6_block_impl_0*);
    
  void (*dispose)(struct __test6_block_impl_0*);
    
} __test6_block_desc_0_DATA = { 0, sizeof(struct __test6_block_impl_0), __test6_block_copy_0, __test6_block_dispose_0};

1.第四步:copy 和dispose 分别调用低五步和第六步,__test6_block_copy_0会对block做一次retain操作,__test6_block_dispose_0会对block做一次release操作,引用计数管理嘛,
2.为啥要这么做?Person对象是局部变量,然后block拿去用了,那Person对象的内存管理就得你自己管理咯,比如:你大宝剑被抓了,人家叔叔肯定管吃管住是一样的

四、Block内存管理

在前面的 Block 结构体中都存在一个 isa 指针,且在构造函数的时候赋值 &_NSConcreteStackBlock。所以可以猜测认为 block 其实也是对象的一种, 尝试对 block 调用 class 方法来看看会有什么输出:


图片.png

可以看出来 block 确实是对象且主要的 block 类型(都是继承自NSBlock)有以下三种:

block 是属于哪一种类型总结下来可以用下面的图片表示:

图片.png
图片.png
补充: ARC 环境下下列操作会自动将 block 进行 copy 操作:
1.代码段的block{}出了这个大括号,ARC会帮我们释放了
2.栈上的block系统分配的,啥时候释放我们不知道了
3.堆上的block,就是要自己管理的了,怎么说:就是你把栈上的block copy就会到堆上
Block对变量的内存管理总结:
上一篇下一篇

猜你喜欢

热点阅读