Block

block实际上是一种回调机制,我们可以想一般的方法和block对比。。。

常规方法调用。A调用B方法
methodA调用methodB A的参数给B应用。。B执行完结束

block回调。A调用B方法
methodA调用methodB B参数是一个block声明。方法体力调用。则实现了。把block当参数传递给B 。B中携带某个数据返还给A去操作。方法执行完毕。

此处可以实现两个东西,第一;得知methodB方法调用了。。第二获取了B的信息。。。

1、基础用法
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // … };

blockName(var);

2、define定义
typedef return_type (^BlockTypeName)(var_type);

做方法属性:
//声明 
typedef void(^ClickBlock)(NSInteger index); 
//block属性 
@property (nonatomic, copy) ClickBlock imageClickBlock;

做方法参数:
//声明 
typedef void (^handleBlock)(); 
//block作参数 
- (void)requestForRefuseOrAccept:(MessageBtnType)msgBtnType messageModel:(MessageModel *)msgModel handle:(handleBlock)handle{
  …

3、直接定义
@property(nonatomic,copy)  void(^bBlock)(NSString *_Nullable);
- (void)resetResult:(NSString *)result completeBlock:(void(^)(NSString *))comBlock;

一些框架用法
1、Block内部调用自身,递归调用是很多算法基础,特别是在无法提前预知循环终止条件的情况下。注意:由于Block内部引用了自身,这里必须使用__block避免循环引用问题。
__block return_type (^blockName)(var_type) = [^return_type (var_type varName)
{ if (returnCondition)
    {
        blockName = nil; return;
    } // ... // 【递归调用】 blockName(varName);
} copy];

【初次调用】
blockName(varValue);

2、方法的返回值是一个Block,可用于一些“工厂模式”的方法中:

- (return_type(^)(var_type))methodName
{ 
   return ^return_type(var_type param) { // … };
}

Block使用注意

截获自动变量与__block说明符

前面讲过block所在函数中的,捕获自动变量。但是不能修改它,不然就是“编译错误”。但是可以改变全局变量、静态变量、全局静态变量。其实这两个特点不难理解:

  • 不能修改自动变量的值是因为:block捕获的是自动变量的const值,名字一样,不能修改
  • 可以修改静态变量的值:静态变量属于类的,不是某一个变量。由于block内部不用调用self指针。所以block可以调用。
    解决block不能修改自动变量的值,这一问题的另外一个办法是使用__block修饰符。
    截获对象

对于捕获ObjC对象,不同于基本类型;Block会引起对象的引用计数变化。
Block引起的循环引用

一般来说我们总会在设置Block之后,在合适的时间回调Block,而不希望回调Block的时候Block已经被释放了,所以我们需要对Block进行copy,copy到堆中,以便后用。
Block可能会导致循环引用问题,因为block在拷贝到堆
在上述使用 block中,虽说使用__weak,但是此处会有一个隐患,你不知道 self 什么时候会被释放,为了保证在block内不会被释放,我们添加__strong。更多的时候需要配合strongSelf使用,如下:
__weak __typeof(self) weakSelf = self;
self.testBlock = ^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
[strongSelf test];
});
所有的Block里面的self必须要weak一下?

很显然答案不都是,有些情况下是可以直接使用self的,比如调用系统的方法:
[UIView animateWithDuration:0.5 animations:^{ NSLog(@"%@", self);
}];
因为这个block存在于静态方法中,虽然block对self强引用着,但是self却不持有这个静态方法,所以完全可以在block内部使用self。
另外,来看一个Masonry代码布局的例子,这里面的self会不会造成循环引用呢?
[self.headView mas_makeConstraints:^(MASConstraintMaker *make) {
make.centerY.equalTo(self.otherView.mas_centerY);
}];
并不是 block 就一定会造成循环引用,是不是循环引用要看是不是相互持有强引用。block 里用到了 self,那 block 会保持一个 self 的引用,但是 self 并没有直接或者间接持有 block,所以不会造成循环引用。可以看一下Masonry的源代码:

  • View+MASAdditions.m
  • (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {

    self.translatesAutoresizingMaskIntoConstraints = NO;
    MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
    block(constraintMaker);
    return [constraintMaker install];
    }

  • MASConstraintMaker.m
  • (id)initWithView:(MAS_VIEW *)view {
    self = [super init];
    if (!self) return nil;
    self.view = view;
    self.constraints = NSMutableArray.new;
    return self;
    }
    持有链是这样的,并没有形成引用循环:
    self ->self.headView ··· MASConstraintMaker构造block->self
    注意观察,这个作为方法参数的Block体并没有被任何方持有。因此,我们放心在Masonry中使用self.xxx 不会循环引用的。而且这个block里面用weakSelf还有可能会出问题,因为mas_qeual如果得到一个nil参数的话应该会导致程序崩溃。
    因为UIView未强持有block,所以这个block只是个栈block,而且构不成循环引用的条件。栈block有个特性就是它执行完毕之后就出栈,出栈了就会被释放掉。看mas_makexxx的方法实现会发现这个block很快就被调用了,完事儿就出栈销毁,构不成循环引用,所以可以直接放心的使self。另外,这个与网络请求里面使用self道理是一样的。

Block的底层结构

image.png

isa指针:指向表明该block类型的类。
flags:按bit位表示一些block的附加信息,比如判断block类型、判断block引用计数、判断block是否需要执行辅助函数等。
reserved:保留变量
FuncPtr,block执行时调用的函数指针 ,它包含了isa指针(包含isa指针的皆为对象),也就是说block也是一个对象(runtime里面,对象和类都是用结构体表示)。
desc:block的附加描述信息,比如保留变量数、block的大小、进行copy或dispose的辅助函数指针。

  • reserved:保留字段
  • Block_size:block大小(sizeof(struct __main_block_impl_0))

Block的分类:堆、栈、全局。。。
程序的内存分配:
栈区(stack):由系统自动分配,由编译器自动创建与释放。函数参数值、局部变量等,后进先出、先进后出的原则。
方法内部的 int a int b 全部存放在栈区
堆区(heap):由程序员申请大小,释放。如果不释放,可能会由OS回收。堆区的管理是采用链表式。
student *a;栈区
a = new student 堆区
静态区(全局):由系统释放;存放全局变量、静态变量。但初始化和未初始化的存放不同。
常量区:主要是字符串,由系统释放
代码区:这个区域主要存放函数体的二进制代码。

//全局变量
int globalVar = 10;
//全局静态变量
static int staticGlobalVar = 99;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //局部变量
    int autoVar = 9;
    //局部静态变量
    static int staticLocVar = 8;
    void (^block)() = ^(){
        NSLog(@"a-%d,,,b-%d",autoVar,staticLocVar);
    };
    /**
    struct  __main_block_imp_0 {
     *******
       //全局、全局静态不捕获
    // 局部直接捕获。。局部静态地址捕获
       int autoVar = 9;
       int * staticLocVar
     *********
     }
     */
    globalVar = 0;
    staticGlobalVar = 0;
    
    autoVar = -1;
    staticLocVar = -1;
    block();
}

一般,我们方法内默认声明的变量都属于auto类型。。。总结如下:


image.png

堆、栈、全局block的区分:


image.png

测试:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    auto int a = 10;
    static int b = 5;
    void (^gloBlock)(void) = ^(){
    };
    //不访问的。是全局block
    NSLog(@"gloBlock is %@",[gloBlock class]);
    
    void (^globlock2)(void) = ^(){
//        a = 11; //error : Variable is not assignable (missing __block type specifier)
        NSLog(@"%d",a);
    };
    //访问auto的 成为了栈block 系统默认copy操作成为堆block
    NSLog(@"gloBlock is %@",[globlock2 class]);
    
    void (^mallocblock)(void) = ^(){
        NSLog(@"%d",b);
    };
    //访问static的 依旧是全局block
    NSLog(@"gloBlock is %@",[mallocblock class]);
    
    
}

out:

KKKThread[38084:5484508] gloBlock is __NSGlobalBlock__
KKKThread[38084:5484508] gloBlock is __NSMallocBlock__
KKKThread[38084:5484508] gloBlock is __NSGlobalBlock__

可见只有访问了auto变量的block成为了staticblock。。但是arc机制下默认copy操作。成为了堆block。。。。不访问auto变量即便访问static变量也依旧是全局block

当block内部访问了对象类型的auto变量时

  • 如果block在栈上,将不会对auto变量产生强引用
  • 如果block被拷贝到堆上
  1. 会调用block内部的copy函数
  2. copy函数内部会调用Block_object_assign 函数
  3. Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的>操作,形成强引用(retain)或者弱引用
  • 如果block从堆上移除
  1. 会调用block内部的dispose函数
  2. dispose函数内部会调用_Block_object_dispose函数
  3. _Block_object_dispose函数会自动释放引用的auto变量
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容