Objective-C Block

Block 是“带有自动变量值的匿名函数”

WHAT

本文还未整理完

用法

  • 局部变量

    1
    returnType (^blockName)(parameterTypes) = ^returnType(parameters) {...};
  • 属性

    1
    @property (nonatomic, copy, nullability) returnType (^blockName)(parameterTypes);
  • 方法参数

    1
    - (void)someMethodThatTakesABlock:(returnType (^nullability)(parameterTypes))blockName;
  • 方法调用参数

    1
    [someObject someMethodThatTakesABlock:^returnType (parameters) {...}];
  • 关键字

    1
    2
    typedef returnType (^TypeName)(parameterTypes);
    TypeName blockName = ^returnType(parameters) {...};

    类型

  • _NSConcreteGlobalBlock (全局)

    • Block 内部只是用了全局变量
    • Block 内部没有使用任何外部的局部变量
  • _NSConcreteStackBlock (栈)

    • MRC 下,默认分配在栈上
  • _NSConcreteMallocBlock (堆)

    • 手动对 block 执行 copy 操作
    • ARC 下,大部分情况会将 block 从栈上复制到堆上

修饰符

  • __block
    MRC ARC 下都可以使用,可以修饰对象,也可以修饰基本数据类型。
    会将简单类型转化为较大的 struct,会给内存、调用带来额外的开销

  • __strong
    MRC 可以避免循环引用,ARC 下不可以

  • __weak
    只能在 ARC 下使用,解决循环引用问题

  • __unsafe_unretained
    类似 __weak 修饰符,但不会自动置 nil

注意事项

循环引用

  • 非 ARC
    使用 __block 来修饰变量,它不会被 Block 所 __retain

  • __ARC 下
    iOS 5 前使用 __unsafe_unretained,缺点是指针释放后不会置空
    iOS 5 后使用 __weak

  • Block 执行完毕后置为 nil

不同类型变量读取

  • 局部自动变量
    Block 中只读,Block 定义时 copy 变量的值,在 Block 中作为常量使用,所以即使变量的值在 Block 外改变,也不影响它在 Block 中的值。

  • static 变量、全局变量
    Block 可以进行读写操作。因为全局变量或静态变量在内存中的地址是固定的,Block 在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。

  • Block 变量
    等效于全局变量或静态变量

Block 的 copy、retain、release 操作

  • Block_copy与copy等效,Block_release与release等效
  • retain、copy、release都不会改变引用计数,retainCount 始终是1
  • NSConcreteGlobalBlock:retain、copy、release 操作都无效
  • NSConcreteStackBlock:retain、release 操作无效
  • NSConcreteMallocBlock支持 retain、release,虽然 retainCount 始终是1,但内存管理器中仍然会增加、减少计数。copy 之后不会生成新的对象,只是增加了一次引用,类似 retain

ARC 下 Block 的自动拷贝和手动拷贝

ARC下,以下几种情况,系统会将block从栈上自动复制到堆上

  • 当 block 作为函数返回值返回时;
  • 当 block 被赋值给 __strong 修饰的 id 类型的对象或 block 对象时;
  • 当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时(比如使用NSArray 的 enumerateObjectsUsingBlock 和 GCD 的 dispatch_async 方法时,其 block 不需要我们手动执行copy操作)
    注:系统方法内部对 block 进行了 copy 操作

因为在 ARC 下,对象默认是用 __strong 修饰的,所以大部分情况下编译器都会将 block 从栈自动复制到堆上,除了以下情况

  • block 作为方法或函数的参数传递时,编译器不会自动调用 copy 方法
  • block 作为临时变量,没有赋值给其它 block

How Do I Declare A Block in Objective-C?
Objective-C Blocks Quiz
正确使用Block避免Cycle Retain和Crash