iOS-Block知识梳理

Block知识梳理

1、什么是block

  • 将函数和上下文封装起来的对象
/**
*    比如
**/
void (^block)(NSInteger) = ^(NSInteger age){
        NSLog(@"%zd",age);
};
block(18);
  • 在ViewController.m中编写了上面的block,然后使用(xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc 文件名[这里的文件名是ViewController.m])命令clang一下,会发现文件夹中出现xxx.cpp文件,查看文件会有如下代码 :
struct __block_impl {
  void *isa;//isa指针
  int Flags;
  int Reserved;
  void *FuncPtr;//函数指针
};

从结构体中可以发现block内部有isa,所以其本质是一个oc对象。
block内部实现为:

static __NSConstantStringImpl __NSConstantStringImpl__var_folders_r4_mlnz1vw53r12b9s528b98m6r0000gn_T_ViewController_d1f10e_mi_0 __attribute__ ((section ("__DATA, __cfstring"))) = 
{
    __CFConstantStringClassReference,0x000007c8,"%zd",3
};

所以说block是将函数及其上下文封装起来的对象

2、Block变量截获

  • 局部变量截获:基本数据类型的局部变量是值截获,引用数据类型的局部变量是指针截获
/**
*基本数据类型的局部变量截获
**/
NSInteger age = 18;
void (^block)(void) = ^(void){
        NSInteger tAge = 2 * age;
        NSLog(@"base data type----:%zd",tAge);
};
age = 19;
block();
//打印输出的结果:
2021-02-06 12:34:55.706828+0800 DSPracticeDemo[31320:1720535] base data type----:36
/**
*引用数据类型的局部变量截获
*/
NSMutableDictionary *dictage = [NSMutableDictionary dictionary];
[dictage setValue:@(18) forKey:@"age"];
void (^nblock)(void) = ^(void){
        NSNumber *qage = [dictage objectForKey:@"age"];
        NSInteger tAge = 2 * qage.intValue;
        NSLog(@"object data type----: %zd",tAge);
};
[dictage setValue:@(19) forKey:@"age"];
nblock();
//打印输出的结果:
2021-02-06 12:34:55.706949+0800 DSPracticeDemo[31320:1720535] object data type----: 38
  • 局部静态变量截获:是指针截获
/**
*局部静态变量截获
**/
static NSInteger ageNum = 18;
void (^sBlock)(void) = ^(void){
        NSInteger tAge = 2 * ageNum;
        NSLog(@"static data type---:%zd",tAge);
};
ageNum = 19;
sBlock();
//打印输出结果
2021-02-06 12:52:25.678532+0800 DSPracticeDemo[31376:1730406] static data type---:38
  • 全局变量、全局静态变量截获:不截获,直接取值
/**
*源码部分
**/
//全局定义变量
NSInteger globalAge = 18;
static NSInteger globalStaticAge = 18;
//方法定义
- (void)blockTest1{
    NSInteger baseAge = 18;
    static NSInteger staticBaseAge = 18;
    __block NSInteger blockBaseAge = 18;
    void (^block)(void) = ^(void){
        NSLog(@"局部变量  %zd",baseAge);
        NSLog(@"局部静态变量  %zd",staticBaseAge);
        NSLog(@"局部block变量  %zd",blockBaseAge);
        NSLog(@"全局变量  %zd",globalAge);
        NSLog(@"全局静态变量  %zd",globalStaticAge);
    };
    block();
}

同样,clang一下上面的代码,会发现如下代码:可以发现并没有重新定义全局变量、全局静态变量,所以全局变量、全局静态变量在block里是直接取值的

/**
*源码clang成C++后
**/
struct __ViewController__blockTest_block_impl_0 {
       struct __block_impl impl;
       struct __ViewController__blockTest_block_desc_0* Desc;
       NSInteger baseAge;//局部变量
       NSInteger *staticBaseAge;//局部静态变量
       __Block_byref_blockBaseAge_0 *blockBaseAge; // __block 修饰的局部变量
      __ViewController__blockTest_block_impl_0(void *fp, struct __ViewController__blockTest_block_desc_0 *desc, NSInteger _baseAge, NSInteger *_staticBaseAge, __Block_byref_blockBaseAge_0 *_blockBaseAge, int flags=0) : baseAge(_baseAge), staticBaseAge(_staticBaseAge), blockBaseAge(_blockBaseAge->__forwarding) {
          impl.isa = &_NSConcreteStackBlock;
          impl.Flags = flags;
          impl.FuncPtr = fp;
          Desc = desc;
      }
};

3、block的几种形式

  • 有全局Block(_NSConcreteBlock)、栈Block(_NSConcreteStackBlock)和堆Block(_NSConcreteMallocBlock)三种形式的Block
  • 1、什么是全局block:不使用外部变量的block
//比如
NSLog(@"%@",[^(void){
        NSLog(@"global block");
    } class]);
//打印输出的结果
2021-02-06 15:06:10.768730+0800 DSPracticeDemo[31560:1778151] __NSGlobalBlock__
  • 2、什么是栈block:使用外部变量但未进行copy操作的block
//比如
NSInteger age = 18;
    NSLog(@"%@",[^(void){
        NSLog(@"stack block:%zd",age);
    } class]);
//打印输出的结果
2021-02-06 15:09:52.319949+0800 DSPracticeDemo[31580:1780686] __NSStackBlock__
  • 3、什么是堆block:对栈block进行copy操作的为堆block
//比如
NSInteger tage = 18;
    void (^block)(void) = ^(void){
        NSLog(@"malloc block:%zd",tage);
    };
    NSLog(@"%@", [block class]);
//打印输出的结果
2021-02-06 15:13:55.833683+0800 DSPracticeDemo[31600:1783269] __NSMallocBlock__

4、总结:

  • a、全局block:不使用外部变量的block
  • b、栈block:使用外部变量未进行copy操作的block
  • c、堆block:对栈block进行copy操作的block
  • d、对栈block进行copy会将block拷贝至堆中;对全局block进行copy操作,因为已经初始化了,所以什么也不会做;对堆block进行copy操作,其引用数会增1
  • e、另外,对__block修饰的变量进行copy时,由于__forwarding的存在,栈上的__forwarding会指向堆中block,而堆中__forwarding指向自身,所以修改__block修饰的变量,实质上是修改堆中的__block变量;即__forwarding存在的意义是:无论在内存的哪个位置,都可以顺利的访问同一block

5、补充

  • block捕获__block的变量会去持有它,如果__block修饰self时,且self持有block,并且block内部使用到__block修饰的self,那么会造成循环引用;即self持有block,block持有__block,__block持有self,出现循环引用,对象无法释放,造成内存泄漏
//比如
__block typeof(self) weakSelf = self;
    _block = ^(void){
        NSLog(@"%@",weakSelf);
    };
    _block();
  • 解决方式:在block中使用完weakSelf后手动设置为nil,但是会出现这样一种情况,如果block未被执行,则它们会相互持有(循环引用),所以使用__weak修饰self比较恰当

  • 苹果源码网址:https://opensource.apple.com/tarballs/

  • 《如有错误理解,还请各路大神批评指出》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值