面试遇到block的第一天-本质

block的本质

  • block本质上也是一个OC对象,他的内部也有isa指针
  • block是封装了函数调用以及函数调用环境(好比函数参数)的OC 对象

写一个简单的block demo验证分析一下block的本质:bash

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        void(^myBlock)(void) = ^{
            
            NSLog(@"this is a block");
        };
        myBlock();
        NSLog(@"%@",myBlock);
        
    }
    return 0;
}
复制代码

使用命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m编译成C++代码,找到C++代码最后咱们能够看到block被编译成一个结构体iphone

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;
  }
};
复制代码

第一个属性__block_impl又是一个结构体函数

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
复制代码

这里isa指针是指向了block的类型,这个后面再说,在后面贴的C++代码中能够注意一下,都是block的类型。
咱们这个简单的myBlock内部只有一句NSLog的打印,被封装在了__main_block_func_0这个函数中测试

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_26cca1_mi_0);
        }
复制代码

__main_block_func_0的函数地址又做为__main_block_impl_0构造函数的第一个参数fp,被赋值给implFuncPtr ui

最后经过 ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);调用FuncPtr,也就是调用myBlock中封装的函数。

根据以上对源码的分析 ,也验证了咱们最开始说的两点block的本质。this

变量捕获

上面探究block本质的demo代码特别简单,下面咱们由深到浅的继续探究block是如何获取外部变量的。spa

局部变量(auto变量)

所谓auto变量也叫自动变量,就是离开做用域(他所在的{})就会自动销毁,也就是局部变量,由于auto能够省略,因此咱们平时均可以直接定义局部变量,不须要在前面加上auto修饰符。
先看一段示例代码:指针

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        int age = 10;
        void(^myBlock)(void) = ^{
            
            NSLog(@"this is a block - %d ",age);
        };
        age = 20;
        myBlock();        
    }
    return 0;
}
复制代码

那么请问这段代码的打印age的值是多少呢?有人以为是20,有人以为是10,这里确定的告诉你们,或者你们手动写一写这段代码测试一下打印结果,实际上是10.仍是用命令编译成C++代码一看究竟code

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


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_edbb92_mi_0,age);
        }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        int age = 10;
        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
        age = 20;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);


    }
    return 0;
}
复制代码

一样的结构体,一样的调用方式,上面说过的内容就不重复了,主要看局部变量age是如何被block获取到的:cdn

  1. __main_block_impl_0结构体中添加了一个age属性,构造函数中也添加了age: age(_age) 是C++语法,表示默认执行age = _age
  2. void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age)); 直接把age的值获取到,而且保存在结构体内部,并且是值传递
  3. age = 20; 虽然修改了age的值,可是结构体内部的age的值并不会发生改变

static变量

auto相对的,还有static修饰的变量,那么blockstatic变量捕获的方式和auto变量有什么不一样呢?看下面的测试代码

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        auto int age = 10;
        static int height = 50;
        void(^myBlock)(void) = ^{
            
            NSLog(@"this is a block - %d ,height is %d",age, height);
        };
        age = 20;
        height = 100;
        myBlock();        
    }
    return 0;
}
复制代码

输出

block[87871:3465064] this is a block - 10 ,height is 100
复制代码

很明显咱们在block定义以后修改static变量的值,block内部是能够知道的,再看C++代码

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


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_716be0_mi_0,age, (*height));
        }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        auto int age = 10;
        static int height = 50;
        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
        age = 20;
        height = 100;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}
复制代码

根据上面编译完的代码能够很明显看到变量age和height被block获取时的区别,height被结构体__main_block_impl_0存储的是地址

全局变量

继续根据测试代码和C++代码,分析block对变量的捕获

#import <Foundation/Foundation.h>
int age = 10;
static int height = 50;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
       
        void(^myBlock)(void) = ^{
            
            NSLog(@"this is a block - %d ,height is %d",age, height);
        };
        age = 20;
        height = 100;
        myBlock();        
    }
    return 0;
}

复制代码

C++代码:

#pragma clang assume_nonnull end
int age = 10;
static int height = 50;

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


            NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_1de4db_mi_0,age, height);
        }

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(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        age = 20;
        height = 100;
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}
复制代码

能够得出结论:

  • 全局变量不会被捕获到block内部,无论在程序哪里均可以直接访问
  • 为何局部变量须要捕获呢?其实上面也说过了,是由于做用域的问题,最后block的调用,是在跨函数访问变量,若是不捕获到block内部,访问auto变量的时候已经释放了

self的捕获

其实self的捕获,才是开发中常常遇到的状况,也是引发block循环引用的罪魁祸首。固然self的捕获能够归到上面两类中,但仍是想拿出来单独分析一下。也算是对上面两类变量分析结果的实践吧。

#import "Person.h"

@implementation Person
- (void)test_block {
    void(^block)(void) = ^{
        NSLog(@"this is a block - %p",self);
    };
}
@end

复制代码

先思考一下,这里self会被捕获到block中嘛,self在这里是一个auto变量仍是static变量仍是全局变量呢?先分析出是什么变量,下一步咱们才能知道self是以什么形式被捕获到block中或者会不会被捕获到block中。

你知道答案了吗?

看看下面的C++代码:

struct __Person__test_block_block_impl_0 {
  struct __block_impl impl;
  struct __Person__test_block_block_desc_0* Desc;
  Person *self;
  __Person__test_block_block_impl_0(void *fp, struct __Person__test_block_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
复制代码

能够获得结论,block是会捕获self的,但是self是个局部变量嘛?这里又要提到method的两个默认参数了self_cmd,代码中的test方法被编译成_I_Person_test_block,他有两个参数是self_cmd,函数的参数就是局部变量,而后被捕获到block中赋值给struct中的self

static void _I_Person_test_block(Person * self, SEL _cmd) {
    void(*block)(void) = ((void (*)())&__Person__test_block_block_impl_0((void *)__Person__test_block_block_func_0, &__Person__test_block_block_desc_0_DATA, self, 570425344));
}
复制代码

同理,若是是block中访问self的成员变量(self.name)或者经过self调用别的方法([self method]),也一样会捕获self到block中。

相关文章
相关标签/搜索