block 底层实现是结构体。

block 本质是对象,更具体的说,是函数以及执行上下文封装起来的对象。

block底层结构体中有isa(指针),所以block是oc对象;底层结构体中有函数指针,block可传入参数,返回值。

常见变量:局部变量、全局变量、局部静态变量、全局静态变量;

block传入局部变量,是值截取;传入局部静态变量,是指针截取;传入全局变量或全局静态变量,直接获取。(__block 修饰的变量,是指针截取)

block分为:局部、全局、静态blcok,

block存放划分:栈block(存放栈stack区)、堆block(存放堆heap区)、全局的block(存放在初始化数据.data区);

不使用外部变量block,是全局block;

使用外部变量,不进行copy,是栈block;

栈block进行copy,是堆block。

验证:

不使用外部变量的 是 全局block

代码:

NSInteger num = 10;
    NSLog(@"%@",[^{ } class]);

运行结果:

__NSGlobalBlock__

使用外部变量,没有进行copy , 是 栈block

代码

NSInteger num = 10;

    NSLog(@"%@",[^{
        NSLog(@"%zd",num);//使用外部变量
    } class]);

运行结果:

__NSStackBlock__

使用外部变量,进行copy , 是 堆block

代码

NSInteger num = 10;
    dispatch_block_t block;
    NSLog(@"*%@",[block class]);//null
    block = ^{
        NSLog(@"%zd",num);//使用外部变量
    };
    NSLog(@"*%@",[block class]);

运行结果:(如果不使用外部变量,block 是全局变量)

__NSMallocBlock__

代码验证练习:

-(void)testWithBlock:(dispatch_block_t) block{
//    block();
    NSLog(@"-%@",[block class]);// -__NSGlobalBlock__ block | __NSStackBlock__ 如果没有使用外部变量,是全局block,如果使用外部变量,没有进行copy,是栈block;
    block = ^(void){
        NSLog(@"***");
        NSLog(@"%@",self); //模拟使用外部变量
    };
//    block();
    NSLog(@"-%@",[block class]);// -__NSGlobalBlock__ block | __NSMallocBlock__如果没有使用外部变量,是全局block,如果使用外部变量,有进行copy,是堆block;

}

-(void)btnClick{
    NSInteger num = 10;
    NSLog(@"%@",[^{ } class]);//__NSGlobalBlock__ 不使用外部变量的 是 全局block
    NSLog(@"%@",[^{
        NSLog(@"---%zd",num);
    } class]);                //__NSStackBlock__ 使用外部变量,没有进行copy , 是 栈block

    [self testWithBlock:^{
        NSLog(@"----");
        NSLog(@"%@",[self class]); //模拟使用外部变量
    }];
    
}

此处分割线-----------------------------------------

分析栈block 进行copy(赋值)后,打印结果是堆block:

代码:

-(void)testWithBlock:(dispatch_block_t) block{
    block();
    NSLog(@"%@",[block class]);//__NSStackBlock__
    dispatch_block_t newBlock = block;
    NSLog(@"block is %@ ,\n newBlock is %@",[block class],[newBlock class]);
//    block is __NSStackBlock__ ,
//    newBlock is __NSMallocBlock__

}
-(void)btnClick{
    NSInteger num = 3;
    [self testWithBlock:^{
        NSLog(@"%zd",num);//调用外部变量,栈block
    }];
     
}

打印结果:

__NSStackBlock__
block is __NSStackBlock__ ,
 newBlock is __NSMallocBlock__

此时,由代码打印结果不难发现,栈block  还是栈block,不是变成堆block或者消失;栈block进行copy,newBlock 是堆block,原来的栈block还是栈block。

总结:对全局block 进行copy,得到的还是全局block,因为已经初始化的,不发生改变,代码如下1;对栈block进行copy,copy在堆区,代码略(上边代码已讲解);对堆区block进行copy,copy还是在堆区,有文章说此时引用计数加一,没有得到验证,代码如下2。

代码1:

-(void)testWithBlock:(dispatch_block_t) block{
    block();
    NSLog(@"%@",[block class]);
    dispatch_block_t newBlock = block;
    NSLog(@"block is %@ ,\n newBlock is %@",[block class],[newBlock class]);
}
-(void)btnClick{
    NSInteger num = 3;
    [self testWithBlock:^{
        NSLog(@"----");//不调用外部变量,全局block
    }];
     
}

代码1打印结果:

__NSGlobalBlock__

block is __NSGlobalBlock__ ,
 newBlock is __NSGlobalBlock__

代码2:

-(void)testWithBlock:(dispatch_block_t) block{
    block();
    dispatch_block_t newBlock = block;
    dispatch_block_t new1Block = newBlock;
    NSLog(@"block is %@ , newBlock is %@ , new1Block is %@",[block class],[newBlock class],[new1Block class]);
    NSLog(@"block  count = %ld",CFGetRetainCount((__bridge CFTypeRef)(block)));
    NSLog(@"newBlock  count = %ld",CFGetRetainCount((__bridge CFTypeRef)(newBlock)));
    NSLog(@"new1Block  count = %ld",CFGetRetainCount((__bridge CFTypeRef)(new1Block)));
}
-(void)btnClick{
    NSInteger num = 3;
    [self testWithBlock:^{
        NSLog(@"%zd",num);//调用外部变量,栈block
    }];
     
}

打印结果:

 3
block is __NSStackBlock__ , newBlock is __NSMallocBlock__ , new1Block is __NSMallocBlock__
block  count = 1
newBlock  count = 1
 new1Block  count = 1

分割线----------------------------------------------------------------------------------------

__block 修饰,对于block截获的外部局部变量赋值,需要使用__block修饰,全局变量不需要__block修饰。

代码分析:

代码:.h文件和.m文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface BlockTest : NSObject

@end

NS_ASSUME_NONNULL_END
#import "BlockTest.h"

@implementation BlockTest

-(void)test0{
    __block NSInteger num = 20;
    void(^block)(void) = ^{
        num+=1;
        NSLog(@"%zd",num);
    };
    block();
}

@end

终端执行命令:

clang -rewrite-objc BlockTest.m

得到.cpp文件,

 

iOS中block使用 ios block在堆里还是栈里_ios

 分析

使用__block 修饰变量num。block截获num指针,并且生成一个新的结构体对象,这里“__forwarding”指向栈block。

struct __Block_byref_num_0 {
  void *__isa;
__Block_byref_num_0 *__forwarding;
 int __flags;
 int __size;
 NSInteger num;
};

__block 中,“__forwarding”指针总结:

在__block 修饰的变量进行赋值时,栈上的“__forwarding”指针指向堆上的“__forwarding”指针,堆上的“__forwarding”指针,指向其自身,对__block修饰的变量进行修改,其实是修改堆上的__block变量。“__forwarding”指针确保无论存放位置在栈还是堆,最终指向同一个变量。

__block 修饰self 造成的循环引用示例:

由于block截取__block修饰变量会持有该变量,当:1__block 修饰self,2且self持有block时,3同时block内部使用到__block修饰的self时,123步骤造成多循环引用,即 self持有block;block持有__block修饰变量;而__block修饰变量持有self,导致内存泄露。

解决方法1:主动断开__block修饰变量对self的持有,可在block内部使用__blockself 后,将其置空(nil);但如果block一直不被调用,循环将一直存在,无法断开。

解决方法2:使用__weak 修饰self。

__block typeof(self) _weakSelf = self;
    dispatch_block_t block;
    block = ^{
        NSLog(@"%@",_weakSelf);
    };
    block();

使用工具Instruments - Leaks 检查如图:

iOS中block使用 ios block在堆里还是栈里_赋值_02