弄透Block

目标

  • Block是什么?
  • 总结Block的使用场景
  • 为何Block属性须要用copy修饰?
  • __block修饰后为什么就能够修改?
  • Block循环引用是怎么产生的?

Block是什么?

Block:带有自动变量的匿名函数,它是C语言的拓展功能,之因此是扩展,是由于C语言不容许存在这样的匿名函数编程

  1. 匿名函数

匿名函数是指不带函数名称的函数函数

  1. 带有自动变量

这是由于Block拥有捕获外部变量的功能,在Block中访问一个外部的局部变量,Block会持有它的临时状态,自动捕获变量值,外部局部变量的变化不会影响它的状态ui

int val = 10;
void (^blk)(void) = ^{
  printf("val=%d\n", val);
};
val = 2;
blk(); // 这里输出的值是10,而不是2,由于block在实现时就会对它所在方法中定义的栈变量进行一次只读拷贝
复制代码
  1. 为了解决block不能修改自动变量的值,能够使用 __block 修饰
__block int val = 10;
void (^blk)(void) = ^{
 printf("val=%d\n", val);
};
val = 2;
blk(); // 这里输出的值是2
复制代码

Block的使用场景

  1. 声明Block属性 利用Block属性响应事件或传递数据

UIViewController 须要监听TableView中Cell的某个按钮的点击事件,既能够经过Delegate回调,也能够利用Block回调 Block回调的思路: 声明一个Block属性,注意这里要用copy。 利用Block属性进行回调atom

  1. 方法参数为Block 利用Block实现回调

[UIView animateWithDuration:animations:] 为例,animations是一个block对象,利用block实现调用者与UIView之间的数据传递spa

  1. 链式语法

链式编程思想:将block做为方法的返回值,且返回值的类型为调用者自己,并将该方法以setter的形式返回,从而实现连续调用设计

// CaculateMaker.h
// ChainBlockTestApp

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface CaculateMaker : NSObject

@property (nonatomic, assign) CGFloat result;

/* * 返回类型 CaculateMaker * 传入参数 CGFloat num */
- (CaculateMaker *(^)(CGFloat num))add;

@end


// CaculateMaker.m
// ChainBlockTestApp


#import "CaculateMaker.h"

@implementation CaculateMaker

- (CaculateMaker *(^)(CGFloat num))add;{
    return ^CaculateMaker *(CGFloat num){
        _result += num;
        return self;
    };
}

@end

// 使用
CaculateMaker *maker = [[CaculateMaker alloc] init];
maker.add(20).add(30);
复制代码

为何Block属性须要用copy修饰?

由于在MRC状况下若是Block属性不使用copy修饰,在使用中会出现崩溃,在ARC状况下,Block属性使用strong修饰会被默认进行copy,因此ARC状况下,Block属性能够使用strong或copy修饰,否则会出现崩溃。
**
为什么会有这种现象出现?指针

Block在内存中的位置分为三种类型:code

  • NSGlobalBlock 是位于全局区的block,它是设置在程序的数据区中。
  • NSStackBlock 是位于栈区,当超出变量做用域时,栈上的Block以及__block变量都会被销毁。
  • NSMallocBlock 是位于堆区,在变量做用域结束时不受影响。

这三种类型对应如下三种状况:对象

  1. Block中没有截获自动变量时Block类型是__NSGlobalBlock__
  2. Block中截获自动变量时Block类型是__NSStackBlock__
  3. 堆中的Block没法直接建立,当对__NSStackBlock__类型的Block进行copy时,会将Block放到堆中,Block类型变为__NSMallocBlock__

当Block类型是__NSStackBlock__时,一旦超出了变量做用域,栈上的Block以及__block变量就会被销毁,从而致使调用Block回调时崩溃。所以,Block属性须要用copy修饰来避免这种状况。生命周期

- (void)click:(id)sender {
        TestClass *test = [[TestClass alloc] init];
        
        __block int a = 1;
        // 弱引用,block类型是__NSStackBlock__ 当TestClass执行回调时必崩 EXC_BAD_ACCESS
        test.weakBlock = ^() {
            NSLog(@"ok");
            a = 2;
        };
        
        // block类型是__NSStackBlock__ 当TestClass执行回调时必崩 EXC_BAD_ACCESS
        test.assignBlock = ^() {
            NSLog(@"ok");
            a = 3;
        };
        
        // block类型是__MallocBlock__ 正常执行
        test.copyBlock = ^() {
            NSLog(@"ok");
            a = 4;
        };
        
        // block类型是__MallocBlock__ 正常执行
        test.strongBlock = ^() {
            NSLog(@"ok");
            a = 5;
        };
        
        NSLog(@"copy property: %@", test.copyBlock);
        NSLog(@"assign property: %@", test.assignBlock);
        NSLog(@"weak property: %@", test.weakBlock);
        NSLog(@"strong property: %@", test.strongBlock);
        
        
        [test start];
    }
复制代码

__block修饰后为什么就能够修改局部变量?

首先,须要弄明白一个概念,static声明的静态局部变量是能够在block内进行修改的,为什么会有这种区别呢?
由于,静态局部变量存在于应用程序的整个生命周期,而非静态局部变量仅存在于一个局部上下文中,绝大多数状况下,block都是延后执行的,这就有可能出现非静态局部变量被回收的状况,为了不这个问题,苹果在设计block时,block中非静态局部变量是值传递,这也解释了为什么block会持有变量的临时状态,后续再修改,block中的变量值也再也不改变。

加上__block修饰的局部变量,被block捕获时,就再也不是传递局部变量的值了,而是变成了一个结构体实例。好比:
定义一个 __block int a = 10;  会变成 __Block_byref_a_0 *a; 
__Block_byref_a_0 的结构体以下所示:

struct __Block_byref_a_0 {
 void *__isa;
 __Block_byref_a_0 *__forwarding; // forwarding指针
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 int a; // 原变量同类型变量
};
复制代码

结构体中有一个forwarding指针,此指针指向转换后变量自己,结构体中也有一个原变量同样类型的变量。此后代码中涉及到原变量的地方,都会转换成新变量->forwarding->原变量同类型变量

若是在block中直接修改变量的值,实质的过程是新变量->__forwarding->原变量同类型变量,最终修改的实际上是结构体中原变量同类型变量,很明显这个结构体内的变量已经不属于block的外部变量了,因此能在block内修改。

这个新变量也是非静态局部变量,因此若是没有copy,block执行时,新变量有可能已经被栈回收

总结:
block修饰的变量转换成告终构体,结构体内有一个forwarding指针和一个与原变量相同类型的成员变量,forwarding指针指向结构体内的成员变量。不管在block内外,都是经过forwarding来访问的

Block的循环引用是怎么产生的?

咱们先看一段block致使循环引用的代码:

TestClass *test = [[TestClass alloc] init]; 

test.copyBlock = ^() {
    NSLog(@"ok: %d", test.result);
};
复制代码

当咱们写完这段代码后,Xcode就会提醒咱们,这段代码存在循环引用,事实上也确实存在循环引用。接下来咱们就来分析一下为何会产生循环引用。
test的属性block强引用了SecondViewController中的block,SecondViewController中的block又强引用了test的属性result,从而致使了循环引用。

并不是全部的block都存在循环引用,下面列举一些常见的block使用的示例:

// self-->requestModel-->block-->self 
[self.requestModel requestData:^(NSData *data) {
    self.name = @"leafly";
}];

// 虽然存在引用环,可是经过主动释放requestModel打破了循环
[self.requestModel requestData:^(NSData *data) {
    self.name = @"leafly";
    self.requestModel = nil;
}];

// t-->block-->self 不存在循环引用
Test *t = [[Test alloc] init];

[t requestData:^(NSData *data) {
    self.name = @"leafly";
}];

// AFNetworking-->block-->self 不存在循环引用
[AFNetworking requestData:^(NSData *data) {
    self.name = @"lealfy";
}];

复制代码
相关文章
相关标签/搜索