认识下OC中的Block,常见的一些误区

Block是什么:

我们可以认为是一段代码并可以将这段代码当做变量,通过block()的方式进行回调。这不免让我们想到在C函数中,我们可以定义一个指向函数的指针并且调用:

void (*task)(void);
void run(){
  NSLog(@"run");
}
{
   task = run;
   (*task)();
}

上面的函数指针可以直接通过 (*task)()的方式调用run这个函数,这样对比block跟似乎C语言的函数指针是一样的,但是两者仍然存在以下区别:

  • block的代码是内联的,效率高于函数调用
  • block对于外部变量默认是只读属性
  • block被Objective-C看成是对象处理

iOS Block定义

//直接定义
@property (nonatomic, copy) void(^block)(NSInteger);
//方法中定义
block:(void(^) (NSInteger index))block;

//其他定义
typedef void(^Block)(NSInteger index);


OC中Block是作为对象存在的,包含3个对象:

  1. GlobalBlock 位于全局区,在Block内部不能使用外部变量
  2. MallocBlock 位于堆区,在Block内部使用局部变量或OC属性,并且赋值给强引用或者copy修饰的变量
  3. StackBlock 位于栈区,与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者copy修饰的变量。
    例:
@interface Student : NSObject
@property (nonatomic, copy) void(^doWork)(void);
@end
Student *student = [Student new];
student.doWork = ^{
};

doWork这个block是什么类型,虽然有强引用,但是没有捕获变量,所以是GlobalBlock。
误区一block不是只能被copy修饰也可以用weak

@interface Student : NSObject
@property (nonatomic, weak) void(^doWork)(void);
@end
Student *student = [Student new];
student.doWork = ^{
};

下面这个block是什么类型?

@interface Student : NSObject
       @property (nonatomic, weak) void(^doWork)(void);
@end
Student *student = [Student new];
int a;
student.doWork = ^{
   a;
};

这个block没有被强引用,但是有捕获局部变量,所以是StackBlock.

我们稍微改下代码用copy修饰

@interface Student : NSObject
       @property (nonatomic, copy) void(^doWork)(void);
@end
Student *student = [Student new];
int a;
student.doWork = ^{
   a;
};

那么这个就是一个MallocBlock;

认识了Block的几种类型,我们看下Block底层是怎么实现,block的底层结构如图所示


image.png

为了保证block内部能够正常访问外部变量,block有个变量捕获机制


image.png

每一个类型的block调用了copy的结果
image.png

对于auto变量的处理

  • 当block内部访问了对象类型的auto变量时
    如果block是在栈上,将不会对auto变量产生强引用

  • 如果block被拷贝到堆上会调用block内部的copy函数copy函数内部会调用_Block_object_assign函数_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用

  • 如果block从堆上移除会调用block内部的dispose函数dispose函数内部会调用_Block_object_dispose函数_Block_object_dispose函数会自动释放引用的auto变量(release)


    image.png

如果想修改auto变量值可以通过__block修饰不能修饰全局变量、静态变量(static),底层实现上就是编译器会将__block变量包装成一个对象,然后就可以更改外部的值。


image.png
  • 当block在栈上时,并不会对__block变量产生强引用

  • 当block被copy到堆时会调用block内部的copy函数copy函数内部会调用_Block_object_assign函数_Block_object_assign函数会对__block变量形成强引用(retain)

  • 如果__block变量从堆上移除会调用__block变量内部的dispose函数
    dispose函数内部会调用_Block_object_dispose函数_Block_object_dispose函数会自动释放指向的对象(release)

解决循环引用问题 - ARC

image.png

你可能感兴趣的:(认识下OC中的Block,常见的一些误区)