iOS-Block

366 阅读10分钟

问题先行

1、👇存在什么问题吗?能正常执行吗?

int a = 0;
void(^ __weak block)(void) = ^{
  a;
  NSLog(@"weak block");
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  block();
});

2、👇存在什么问题吗?能正常执行吗?

void(^ __weak block)(void) = ^{
  NSLog(@"weak block");
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  block();
});

3、👇存在什么问题吗?能正常执行吗?

int a = 0;
void(^ __strong block)(void) = ^{
 a;
 NSLog(@"weak block");
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
 block();
});

4、👇weakBlock是什么block? 代码运行正常吗?

- (void)test {
  int a;
  void(^__weak weakBlock)(void) = nil;
  {
    int b = 10;
    void(^__weak weakBlock2)(void) = ^{
      NSLog(@"====%d",b);
    };
    a = b;
    weakBlock = weakBlock2;
  }
  weakBlock();
}

5、👇weakBlock是什么block? 代码能正常执行吗?

- (void)test {
  void(^__weak weakBlock)(void) = nil;
  {
    int b = 10;
    void(^__strong weakBlock2)(void) = ^{
      NSLog(@"====%d",b);
    };
    weakBlock = weakBlock2;
  }
  weakBlock();
}

6、👇ViewController返回能正常释放吗?

static ViewController *staticVC = nil;
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.view.backgroundColor = [UIColor whiteColor];
    __weak typeof(self) weakSelf = self;
    staticVC = weakSelf;
}

7、👇代码存在什么问题吗?

@property (nonatomic, copy)void(^block1)(void);
@property (nonatomic, copy)void(^block2)(void);
__weak typeof(self) weakSelf = self;
self.block1 = ^{
  __strong typeof(self) strongSelf = weakSelf;
  strongSelf.block2 = ^{
    NSLog(@"====%@",strongSelf);
  };
};
self.block1();

8、👇block1是什么类型的block?

void(^block)(void) = ^{
​
};
id block1 = [block copy];
NSLog(@"======%@",block1);

9 👇进入TestViewController之后,立马返回下面的打印顺序是?

@implementation TestViewController
​
- (void)dealloc {
  NSLog(@"dealloc");
}
​
- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view.
  [self test];
}
​
- (void)test {
  __weak typeof(self) weakSelf = self;
  self.block2 = ^{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      NSLog(@"==========%@",weakSelf);
    });
  };
  self.block2();
}
​
@end

10、👇进入TestViewController之后,立马返回下面的打印顺序是?

@implementation TestViewController
​
- (void)dealloc {
  NSLog(@"dealloc");
}
​
- (void)viewDidLoad {
  [super viewDidLoad];
  // Do any additional setup after loading the view.
  [self test];
}
​
- (void)test {
  __weak typeof(self) weakSelf = self;
  self.block2 = ^{
    __strong typeof(self) strongSelf = weakSelf;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
      NSLog(@"==========%@",strongSelf);
    });
  };
  self.block2();
}
@end

概述

闭包 = 一个函数【或指向函数的指针】+ 该函数执行的外部的上下文变量【也就是自由变量】
Block是Objective-C对于闭包的实现

  • 可以嵌套定义,定义Block方法和定义函数方法相似
  • Block可以定义在方法内部或外部
  • 只有调用Block时候,才会执行体内的代码
  • 本质是对象,使代码高聚合

定义

/// 无参数无返回值
void (^MyBlcok1)(void) = ^(void){
    NSLog(@"");
};
MyBlcok1();
/// 有参数无返回值
void (^MyBlcok2)(int) = ^(int b){
    NSLog(@"==== %d",b);
};
MyBlcok2(100);
/// 有参数有返回值
int (^MyBlcok3)(int,int) = ^(int a, int b){
    return a + b;
};
MyBlcok3(100, 200);
/// 无参数有返回值
int (^MyBlcok4)(void) = ^(void){
    return 100;
};
MyBlcok4();
/// typedef定义
typedef void(^MyBlock) (void);
/// block作为参数
- (void)addClickedBlock:(void(^)(id obj))clickedAction;
/// block作为返回值
- (void(^)(NSInteger count))methodName:(NSInteger)a {
    return ^void(NSInteger count) {
        count = count * a;
     };
}
​
/// 链式语法
[self.containerView addSubview:self.bannerView];
[self.bannerView mas_makeConstraints:^(MASConstraintMaker *make) {     make.leading.equalTo(self.containerView.mas_leading);     make.top.equalTo(self.containerView.mas_top);     make.trailing.equalTo(self.containerView.mas_trailing);     make.height.equalTo(@(kViewWidth(131.0)));}];
/// 链式编程思想:核心思想为将block作为方法的返回值,且返回值的类型为调用者本身,并将该方法以setter的形式返回,这样就可以实现了连续调用,即为链式编程。

底层原理

兼容编译(代码少)clang -rewrite-objc -rewrite-objc main.m -o main-arm64.cpp
完成编译(不报错)xcrun -sdk iphonesimulator clang -rewrite-objc -rewrite-objc main.m -o main-arm64.cpp

int main(int argc, const char * argv[]) {
    __block int a =10;
    void(^block)(void) = ^{
        a++;
        printf("a=%d",a);
    };
    block();
    return 0;
}

通过如上clang命令编译成cpp文件,主要代码如下

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

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

        (a->__forwarding->a)++;
        printf("a=%d",(a->__forwarding->a));
    }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
    void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    return 0;
}

可以看出block等于__main_block_impl_0,是一个函数、结构体,同时也可以说block是一个__main_block_impl_0类型的对象。
关系如下所示 image.png 通过关系图可知,block其根本是__main_block_impl_0结构体,通过其同名构造函数创建。第一个传入的block的内部实现代码块即__main_block_func_0,用fp表示,然后赋值给impl的FuncPtr属性,这也是block为什么需要调用的原因。

总结

1、block内部实现声明了一个函数__main_block_func_0执行具体的函数实现,通过调用blockFunPtr指针,调用block执行
*2、*非__block修饰的局部变量是值拷贝
*3、*外界变量通过__block生成__Block_byref_a_0结构体,结构体用来保存原始变量的指针和值。将变量生成的结构体对象的指针地址传递给block,然后在block内部就可以对外界变量进行操作

Block与外界变量

block所在的函数中,能捕获自动变量。但是不能修改它,不然就是“编译错误”。但是可以改变全局变量静态变量全局静态变量

  • 不能修改自动变量的值是因为:block捕获的是自动变量的const值,值拷贝,不能修改
  • 可以修改静态变量的值:静态变量属于类的不是某一个变量。由于block内部不用调用self指针。所以block可以调用 默认情况

对于block外的变量引用,block默认是将其复制到其数据结构中实现访问的,也就是说block的自动变量截获只针对block内部使用的自动变量,不使用则不截获。因为截获的自动变量会存储于block的结构体内部,会导致blcok的体积变大。默认情况block只能访问不能修改局部变量的值。
__weak修饰的变量捕获进去也是__weak修饰的,__strong同理,这也是为什么__weak修饰之后就不会被强引用了。

Block中修改截获的自动变量的值有两种方法

  • 使用静态变量,静态全局变量,全局变量 静态全局变量,全局变量从转换来看,并没有任何改变,静态变量是用指针来对其进行访问。
    为何静态变量的这种方法不适用于自动变量?
    因为静态变量在堆上,自动变量在栈上
  • 使用__block修饰符 对于__block修饰的外部变量引用,block是复制其引用地址来实现访问的。因此可以修改__block修饰的外部变量的值。
    为什么使用__block修饰的外部变量的值,就可以被block修改呢?
int main() {
  __block int val = 10;
  void (^blk)(void) = ^{val = 1;};
}
通过clang编译后提取的Block相关代码为:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
}

struct __Block_byref_val_0 {
    void *__isa;
    __Block_byref_val_0 * __forwarding;
    int __flags;
    int __size;
    int val;
}

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0 *Desc;
  __Block_byref_val_0 *val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0){
  impl.isa = &_NSConcreteStackBlock;
  impl.Flags = flags;
  impl.FuncPtr = fp;
  Desc = desc;
  }
}

一个变量加上__block修饰符后会变成一个_block_byref_val_0结构体类型的自动变量实例

block__main_block_impl_0结构体实例会持有一个指针,指向__block修饰的变量所转换的__block_byref_val_0结构体实例

__block_byref_val_0结构体通过成员变量__forwarding指针访问成员变量val

Block的copy操作

block的三种类型

  • 全局快(_NSConcreteGlobalBlock)
  • 栈块(_NSConcreteStackBlock)
  • 堆块(_NSConcreteMallocBlock) 三种block各自的存储域如下图
  • 全局快存在于全局内存中,相当于单例
  • 栈块存在于栈内存中,超出其作用域则马上被销毁
  • 堆块存在于堆内存中,是一个带引用计数的对象,需要自行管理其内存

(1)位于全局区:GlobalBlock

  • Block不访问外界变量(包括栈中和堆中的变量)
  • 在声明全局变量的地方使用Block语法,此时为全局块
@property(nonatomic, copy) void(^block)(void);
​
self.block = ^{
​
};
​
__NSGlobalBlock__

(2)位于栈内存:StackBlock

  • Block访问外界变量
  • MRC环境下:访问外界变量的Block默认存储栈中
  • ARC环境下:访问外界变量的Block默认存储在堆中(实际是放在栈区,然后ARC情况下自动有拷贝到堆区),自动释放
int a;
void(^ __weak weakBlock)(void) = ^{
  NSLog(@"=====%d",a);
};
​
__NSStackBlock__

ARC下,访问外界变量的Block为什么要自动从栈区拷贝到堆区呢?
栈上的Blcok,如果其所属的变量作用域结束,该Block就被废弃,如同一般的自动变量,Block中的__block变量也同时被废弃,如下图 为了解决栈块在其变量作用域结束之后被废弃的问题,我们需要把Block复制到堆中,延长其生命周期,开启ARC时,大多数情况下编译器会恰当地进行判断是否需要将Block从栈复制到堆。Block的复制操作执行是copy实例方法 Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法的效果如下 (3)位于堆内存:MallocBlock

int i = 10;
​
int(^block)(int count) = ^(int count) {
  return count * i;
};
__NSMallocBlock__

堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。由于block的拷贝最终都会调用_Block_copy_internal函数。

ARC下会默认把栈block被会直接拷贝生成到堆上。那么,什么时候栈上的Block会复制到堆上呢?

  • 调用Blockcopy实例方法时(前提是捕获了外部变量)
  • Block作为函数返回值返回时(前提是捕获了外部变量)
  • Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 将方法名中含有usingBlockCocoa框架方法或GCDAPI中传递Block时(前提是捕获了外部变量)

__block变量与__forwarding:

copy操作之后,既然__block变量也被copy到堆上去了,那么访问该变量是访问栈上的还是堆上的呢?

Block引起的循环引用:

一般来说我们总会在设置Block之后,在合适的时间回调Block,而不希望回调Block的时候Block已经被释放了,所以我们需要对Block进行copycopy到堆中,以便后用。

Block可能会导致循环引用问题,因为block在拷贝到堆上的时候,会retain其引用的外部变量,那么如果block中如果引用了他的宿主对象,那很有可能引起循环引用。

解决方案

1、__weak 和 __strong

1、__weak typeof(self) weakSelf = self;
self.block2 = ^{
  __strong typeof(self) strongSelf = weakSelf;
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"==========%@",strongSelf);
  });
};
self.block2();

2、中间变量

__block TestViewController * vc = self;
self.block2 = ^{
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"==========%@",vc);
    vc = nil;
  });
};
self.block2();

3、参数

self.block2 = ^(TestViewController *vc){
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"==========%@",vc);
  });
};
self.block2(self);

4、NSProxy

原理主要是通过自定义的NSProxy类的对象来代替self,并使用方法实现消息转发。
NSProxy其实是一个消息重定向封装的一个抽象类,类似一个代理人,中间件,可以通过继承它,并重写下面两个方法来实现消息转发到另一个实例

- (void)forwardInvocation:(NSInvocation *)invocation;
- (nullable NSMethodSignature *)methodSignatureForSelector:(SEL)sel

Block的类型判断依据:

1、GlobalBlock

  • 位于全局区
  • Block内部不使用外部变量,或者只使用静态变量和全局变量 2、MallocBlock
  • 位于堆区
  • Block内部使用局部变量或者OC属性,并且赋值给强引用或者Copy修饰的变量 3、StackBlock
  • 位于栈区
  • MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者Copy修饰的变量

问题先行解答

1、👇存在什么问题吗?能正常执行吗?
会发生crash,原因:由于捕获了变量,且通过__weak修饰,因此是__NSStackBlock__,作用域之后会被释放。
2、正常执行,原因:没有捕获变量。是__NSGlobalBlock__
3、正常执行,原因:捕获变量,且通过__strong修饰,是__NSMallocBlock__会被dispatch_after中的block强引用,执行完再释放
4block的底层是结构体,结构体的赋值只是成员变量的值赋值,weakBlock2__NSStackBlock__,作用域为test方法结束,因此正常执行
5block的底层是结构体,结构体的赋值只是成员变量的值赋值,weakBlock2__NSMallocBlock__,作用域为{}结束,因此会crash
6、不能正常释放,因为weakSelf在此处就等同于self(指向的是同一片内存空间),静态变量的作用域是跟随应用
7block1捕获变量,且通过copy修饰,是__NSMallocBlock__self强引用block2block2内部也强引用self,因此造成了循环引用
8block1虽然是block copy生产的,但是block并未捕获外部变量,因此是__NSGlobalBlock__
9

dealloc==========(null)

10

==========<TestViewController: 0x147506050>
dealloc