Blocks Programming Topics

726 阅读19分钟

Blocks Programming Topics

Block对象是C语法的, 且是一个运行时特性, 它们类似于标准C函数, 但是除了可执行代码外它们还可能包含到自动(栈)或托管(堆)内存的变量绑定, 因此, 一个block可以维护一组状态(数据), 当执行Block时可以用来影响执行行为

可以使用block构成可以传递给API的函数表达式(block可以做接口参数), 用于存储数据, 可以在多线程中使用, block做为回调特别有用, 因为block既包含要在回调上执行的代码,又包含执行期间所需的数据

OS X v10.6 Xcode开发人员工具附带提供了GCC和Clang中的block, 可以在OS X v10.6和更高版本以及iOS 4.0和更高版本中使用block, block运行时是开源的可以在 LLVM’s compiler-rt subproject repository中找到. Block也作为 N1370: Apple’s Extensions to C已提交给C标准工作组, 由于Objective-C和C++都从C派生而来, 因此block被设计为可用于所有三种语言(以及Objective-C++)

Getting Started with Blocks

Declaring and Using a Block

使用^运算符声明一个block变量并指示block文字的开头, block的主体本身包含在{}

int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};

Blocks

注意block能够使用其定义范围内的变量

如果将block声明为变量则可以像使用函数一样使用它

int multiplier = 7;
int (^myBlock)(int) = ^(int num) {
    return num * multiplier;
};
 
printf("%d", myBlock(3));
// prints "21"

Using a Block Directly

在很多情况下, 不需要声明block变量, 取而代之的是只需在需要将其作为参数的位置编写内联代码block, 示例使用qsort_b函数, qsort_b与标准qsort_r函数相似但是将一个block作为其一个参数

char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
 
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
    char *left = *(char **)l;
    char *right = *(char **)r;
    return strncmp(left, right, 1);
});
 
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }

Blocks with Cocoa

Cocoa框架中的几种方法都以一个block作为参数, 通常做对对象集合执行操作,以及在操作完成后用作回调, 以下示例显示如何将block与NSArray方法sortedArrayUsingComparator:一起使用

NSArray *stringsArray = @[ @"string 1",
                           @"String 21",
                           @"string 12",
                           @"String 11",
                           @"String 02" ];
 
static NSStringCompareOptions comparisonOptions = NSCaseInsensitiveSearch | NSNumericSearch |
        NSWidthInsensitiveSearch | NSForcedOrderingSearch;
NSLocale *currentLocale = [NSLocale currentLocale];
 
NSComparator finderSortBlock = ^(id string1, id string2) {
 
    NSRange string1Range = NSMakeRange(0, [string1 length]);
    return [string1 compare:string2 options:comparisonOptions range:string1Range locale:currentLocale];
};
 
NSArray *finderSortArray = [stringsArray sortedArrayUsingComparator:finderSortBlock];
NSLog(@"finderSortArray: %@", finderSortArray);
 
/*
Output:
finderSortArray: (
    "string 1",
    "String 02",
    "String 11",
    "string 12",
    "String 21"
)
*/

__block Variables

block的强大功能是它可以在相同的词法范围内修改变量, 使用__block存储类型修饰符表示block可以修改变量, 沿用之前的示例, 可以使用一个block变量来计算比较相等的字符串数, 在这种情况下, 将直接使用该block, 并将currentLocale用作该block内的只读变量

NSArray *stringsArray = @[ @"string 1",
                          @"String 21", // <-
                          @"string 12",
                          @"String 11",
                          @"Strîng 21", // <-
                          @"Striñg 21", // <-
                          @"String 02" ];
 
NSLocale *currentLocale = [NSLocale currentLocale];
__block NSUInteger orderedSameCount = 0;
 
NSArray *diacriticInsensitiveSortArray = [stringsArray sortedArrayUsingComparator:^(id string1, id string2) {
 
    NSRange string1Range = NSMakeRange(0, [string1 length]);
    NSComparisonResult comparisonResult = [string1 compare:string2 options:NSDiacriticInsensitiveSearch range:string1Range locale:currentLocale];
 
    if (comparisonResult == NSOrderedSame) {
        orderedSameCount++;
    }
    return comparisonResult;
}];
 
NSLog(@"diacriticInsensitiveSortArray: %@", diacriticInsensitiveSortArray);
NSLog(@"orderedSameCount: %d", orderedSameCount);
 
/*
Output:
 
diacriticInsensitiveSortArray: (
    "String 02",
    "string 1",
    "String 11",
    "string 12",
    "String 21",
    "Str\U00eeng 21",
    "Stri\U00f1g 21"
)
orderedSameCount: 2
*/

Conceptual Overview

block对象提供了一种方法可以创建临时函数体, 以C以及基于C的语言(如Objective-C和C ++)的表达式进行创建. 在其他语言和环境中, block对象有时也称为"闭包(closure)"

Block Functionality

block是匿名内联代码的集合

  • 具有像函数一样的类型化参数列表
  • 具有推断或声明的返回类型
  • 可以从定义它的词汇范围(lexical scope)中捕获状态
  • 可以有选择地修改词法范围(lexical scope)的状态
  • 可以与相同词汇范围内定义的其他block共享修改的变量
  • 在销毁了词法范围(栈空间)后可以继续共享和修改在词法范围(栈空间)中定义的状态

可以复制一个block,而且还可以将其传递给其他线程以推迟执行(或在其自己的线程中传递给运行循环), 编译器和运行时可以确保block所引用的所有变量的生命周期, 尽管block可用于纯C和C++但是block也始终是Objective-C对象

Usage

block通常代表小的独立代码段. 因此它们特别有用, 可以封装可以同时执行的工作单元, 也可以封装集合中的项, 也可以封装其他操作完成后的回调

block可以作为传统的回调方案的替代有两个重要原因

  • 它们允许在调用点处编写代码, 该代码稍后在方法实现的上下文中执行, 因此block通常是框架中方法常用的参数
  • 它们允许访问局部变量

Declaring and Creating Blocks

Declaring a Block Reference

block变量保存了对block对象的引用, 使用类似于声明函数指针的语法来声明它们, 当然不同处是使用^而不是*, block类型可以与C类型系统的其余部分完全相互操作

void (^blockReturningVoidWithVoidArgument)(void);
int (^blockReturningIntWithIntAndCharArguments)(int, char);
void (^arrayOfTenBlocksReturningVoidWithIntArgument[10])(int);

block还支持可变参数(...), 不带参数的block必须在参数列表中指定void

block被设计为完全类型安全的, 通过为编译器提供一整套元数据以验证block的使用, 整套数据包含传递给block的参数以及返回值的分配. 可以将block引用强制转换为任意类型的指针, 反之亦然

但是不能通过指针引用运算符*取消对block的引用,因为无法在编译时计算block的大小

可以为block创建类型, 这样就可以在多个地方使用

typedef float (^MyBlockType)(float, float);
 
MyBlockType myFirstBlock = // ... ;
MyBlockType mySecondBlock = // ... ;

Creating a Block

可以使用^运算符来指示block文字表达式的开始, 它后面可以跟( )中包含的参数列表, block的主体包含在{}

示例

float (^oneFrom)(float);
 
oneFrom = ^(float aFloat) {
    float result = aFloat - 1.0;
    return result;
};

如果未明确声明block表达式的返回值, 它可以从block的内容中自动推断出来, 如果推断出返回类型与参数列表都为空void, 那么也可以省略void参数列表, 如果存在多个return语句则它们必须完全匹配(必要时使用强制类型转换)

Global Blocks

在文件级别可以将block用作全局变量

#import <stdio.h>
 
int GlobalInt = 0;
int (^getGlobalInt)(void) = ^{ return GlobalInt; };

Blocks and Variables

Types of Variable

在block对象的代码体内,可以用五种不同方式来处理变量

可以引用三种标准类型的变量,就像在函数中一样

  • 全局变量,包括静态局部变量
  • 全局函数(从技术上讲不是可变的)
  • 来自封闭范围的局部变量和参数

block还支持其他两种类型的变量

  • 在函数级别上的是__block变量, 它们在block(和封闭范围)内是可变的, 如果将任何引用block复制到堆中, 则将保留它们
  • const常量

最后,在方法实现中,block可以引用Objective-C实例变量

在block中使用的变量的规则

  • 全局变量是可访问的, 包括包含在词汇范围内的静态变量

  • 传递给block的参数是可访问的(就像函数的参数一样)

  • 处于封闭范围内的非静态局部变量被block捕获为const不可变的常量, 不可变

    • 程序中block表达式的位置会获取他们的值
    • 在嵌套block中从最近的封闭范围捕获值
  • __block存储修饰符声明的包围词法作用域的局部变量, 是可变的, 可以被修改

    • 任何更改都影响在词法范围内的关联的内容, 包括在同一封闭词法范围内定义的任何其他block
  • 在block的词法范围内声明的局部变量, 行为等同函数中的局部变量

使用局部非静态变量

int x = 123;
 
void (^printXAndY)(int) = ^(int y) {
 
    printf("%d %d\n", x, y);
};
 
printXAndY(456); // prints: 123 456

尝试在block内为x分配新值将导致错误

int x = 123;
 
void (^printXAndY)(int) = ^(int y) {
 
    x = x + y; // error
    printf("%d %d\n", x, y);
};

The __block Storage Type

通过应用__block存储类型修饰符, 可以指定导入的变量是可变的(即可读写), __block对于局变量类似于register, auto, static这些存储类型, 但是和它们是互斥的

__block变量存储在变量的词法范围与在该变量的词法范围内声明或创建的所有block和block副本之间共享的存储中, 因此, 在栈内声明的block的任何拷贝都会在在栈的尾都存活(例如,通过排队到某个地方以供以后执行), 则该存储将避免栈的销毁(Thus, the storage will survive the destruction of the stack frame if any copies of the blocks declared within the frame survive beyond the end of the frame (for example, by being enqueued somewhere for later execution). 这句理解有点难搞), 给定词法范围内的多个block可以同时使用共享变量

作为一个最优项, block一般都是用在栈中, 如果block使用Block_copy拷贝(或者Objective-C 中发送了copy消息), 拷贝的内容就在上了, 所以, __block修饰的变量的地址可以随时间变化

__block变量还有两个限制

  • 它们不能是可变长度数组
  • 不能是包含C99可变长度数组的结构

__block示例

__block int x = 123; //  x lives in block storage
 
void (^printXAndY)(int) = ^(int y) {
 
    x = x + y;
    printf("%d %d\n", x, y);
};
printXAndY(456); // prints: 579 456
// x is now 579

以下示例显示了具有多种类型变量的block

extern NSInteger CounterGlobal;
static NSInteger CounterStatic;
 
{
    NSInteger localCounter = 42;
    __block char localCharacter;
 
    void (^aBlock)(void) = ^(void) {
        ++CounterGlobal;
        ++CounterStatic;
        CounterGlobal = localCounter; // localCounter fixed at block creation
        localCharacter = 'a'; // sets localCharacter in enclosing scope
    };
 
    ++localCounter; // unseen by the block
    localCharacter = 'b';
 
    aBlock(); // execute the block
    // localCharacter now 'a'
}

Object and Block Variables

block作为变量可以提供对Objective-C和C++对象以及其他块的支持

Objective-C Objects

复制一个block时, 它将创建对该块内使用的对象变量的强引用, 如果在实现方法内使用block

  • 如果通过引用访问实例变量,则会对self进行强引用
  • **如果按值访问实例变量,则会对该变量进行强引用

以下示例说明了两种不同的情况

dispatch_async(queue, ^{
    // instanceVariable is used by reference, a strong reference is made to self
    doSomethingWithObject(instanceVariable);
});
 
 
id localVariable = instanceVariable;
dispatch_async(queue, ^{
    /*
      localVariable is used by value, a strong reference is made to localVariable
      (and not to self).
    */
    doSomethingWithObject(localVariable);
});

C++ Objects

通常可以在一个block中使用C++对象, 在成员函数中, 对成员变量和函数的引用是通过隐式导入的this指针进行的, 因此看起来是可变的, 如果拷贝block则有两个注意事项

  • 如果有一个__block存储类型的类来处理基于栈的C++对象, 通常复制构造函数将会被使用
  • 如果您在一个block中使用任何其他基于C++栈的对象, 它必须具有const复制构造函数, 然后使用该构造函数复制C++对象

Blocks

当拷贝一个block, 如有必要将复制该block中对其他block的任何引用, 如果有一个block变量将被复制, 而且这个block还引用了其他block, 那么引用的那个block也会被复制

Using Blocks

Invoking a Block

如果将块声明为变量则可以像使用函数一样使用它, 见示例

int (^oneFrom)(int) = ^(int anInt) {
    return anInt - 1;
};
 
printf("1 from 10 is %d", oneFrom(10));
// Prints "1 from 10 is 9"
 
float (^distanceTraveled)(float, float, float) =
                         ^(float startingSpeed, float acceleration, float time) {
 
    float distance = (startingSpeed * time) + (0.5 * acceleration * time * time);
    return distance;
};
 
float howFar = distanceTraveled(0.0, 9.8, 1.0);
// howFar = 4.9

通常将块作为参数传递给函数或方法, 在这种情况下, 通常会创建一个内联block

Using a Block as a Function Argument

在很多情况下, 不需要声明block变量, 取而代之的是只需在需要将其作为参数的位置编写内联代码block, 示例使用qsort_b函数, qsort_b与标准qsort_r函数相似但是将一个block作为其一个参数

char *myCharacters[3] = { "TomJohn", "George", "Charles Condomine" };
 
qsort_b(myCharacters, 3, sizeof(char *), ^(const void *l, const void *r) {
    char *left = *(char **)l;
    char *right = *(char **)r;
    return strncmp(left, right, 1);
});
 
// myCharacters is now { "Charles Condomine", "George", "TomJohn" }

下面的例子dispatch_apply函数所使用的的block

void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));

#include <dispatch/dispatch.h>
size_t count = 10;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
dispatch_apply(count, queue, ^(size_t i) {
    printf("%u\n", i);
});

Using a Block as a Method Argument

Cocoa提供了许多使用block的方法, 与其他任何参数一样可将block作为方法参数传递

NSArray *array = @[@"A", @"B", @"C", @"A", @"B", @"Z", @"G", @"are", @"Q"];
NSSet *filterSet = [NSSet setWithObjects: @"A", @"Z", @"Q", nil];
 
BOOL (^test)(id obj, NSUInteger idx, BOOL *stop);
 
test = ^(id obj, NSUInteger idx, BOOL *stop) {
 
    if (idx < 5) {
        if ([filterSet containsObject: obj]) {
            return YES;
        }
    }
    return NO;
};
 
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:test];
 
NSLog(@"indexes: %@", indexes);
 
/*
Output:
indexes: <NSIndexSet: 0x10236f0>[number of indexes: 2 (in 2 ranges), indexes: (0 3)]
*/

//============================================================================

__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta", @"Gamma", @"X", nil];
NSString *string = @"gamma";
 
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
    if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
        *stop = YES;
        found = YES;
    }
}];
 
// At this point, found == YES

Copying Blocks

通常不需要复制(或保留)block, 仅当希望该block在声明了该块的作用域销毁之后被使用时, 才需要进行复制, 复制的block会在堆上产生

可以使用C函数复制和释放block

Block_copy();
Block_release();

为避免内存泄漏须始终将Block_copy()Block_release()保持平衡

Patterns to Avoid

A block literal (that is, ^{ ... }) is the address of a stack-local data structure that represents the block. The scope of the stack-local data structure is therefore the enclosing compound statement, so you should avoid the patterns shown in the following examples:

block文字(即^ {...})是表示该block的堆栈本地数据结构的地址, 因此栈局部数据结构的范围是封闭的复合声明, 因此应该避免以下示例中显示的模式(翻的不通顺, 请看原句, 结合代码理解)

void dontDoThis() {
    void (^blockArray[3])(void);  // an array of 3 block references
 
    for (int i = 0; i < 3; ++i) {
        blockArray[i] = ^{ printf("hello, %d\n", i); };
        // WRONG: The block literal scope is the "for" loop.
    }
}
 
void dontDoThisEither() {
    void (^block)(void);
 
    int i = random():
    if (i > 1000) {
        block = ^{ printf("got i at: %d\n", i); };
        // WRONG: The block literal scope is the "then" clause.
    }
    // ...
}

Debugging

可以设置断点并单步执行, 并使用invoke-block从GDB会话中调用一个block

$ invoke-block myBlock 10 20

如果要传递C字符串则必须引用它

$ invoke-block doSomethingWithString "\"this string\""

Effictive Objective-C 2.0中Block介绍

Block内部结构

每个Objective C对象都占据着某个内存空间, 因为实例变量的个数及对象包含的关联数据互不相同, 所以每个对象所占的内存区域也有大有小. block本身也是对象, 在存放快对象的内存区域中, 首个变量是指向Class对象的指针, 该指针叫做isa, 其余内存里含有block对象正常运转所需的各种信息, 上图描述了block对象的内存布局

  • invoke变量, 这是个函数指针, 指向block的实现代码, 函数原型至少需要接收一个void *型的参数, 这个参数代表block本身
  • descriptor变量是指向结构体的指针, 每个block里都包含此结构体, 其中声明了block对象的总体大小, 还声明了copydispose这两个辅助函数所对应的函数指针, 辅助函数在拷贝及丢弃block对象时运行, 其中会执行一些操作, 比如: copy要保留捕获的对象, 而dispose则将之释放

block还会把它所捕获的所有变量都拷贝一份, 这些拷贝会放在descriptor变量后面, 捕获了多少变量, 就要占多少内存空间, 请注意, 拷贝的并不是对象本身, 而是指向这些对象的指针变量, 在执行block对象的时候, 就会从内存中把这些捕获到的变量全部读取出来

栈block

  • 定义block的时候, 其所占的内存区域是分配在栈中的, block只在定义定义它的范围内有效
  • 编译器会给每个block分配好栈内存, 然后等离开了相应的范围后, 编译器有可能会把分配给block的内存复写掉

堆block

  • 给栈block执行copy操作, 就可以把block从栈上复制到堆上, 拷贝的block可以在定义它的那个范围之外使用
  • 一旦block复制到堆上, block就成了带引用计数的对象了, 后续的copy操作都不会真的执行复制, 只是递增block对象的引用计数
  • 如果不再使用堆block, 应将其释放, 而手动管理引用计数则需要自行调用release方法, 引用计数为0时, 内存空间被系统回收

全局block

  • 不会捕获任何状态(捕获外部变量), 运行时也无需有状态来参与的block
  • block所使用的的整个内存区域, 在编译器已经完全确定, 因此, 全局block可以声明在全局内存里, 不需要在每次用到的时候在栈中创建
  • 全局block的copy操作是个空操作, 因为全局block不会被系统回收, 相当于单例

全局block示例

void (^GlobalBlock)(void) {
    NSLog(@"This is a global block");
}

Objective-C高级编程 iOS与OS X多线程和内存管理中Block介绍

其他程序语言中Block的名称

程序语言 Block名称
C + Blocks Block
Smalltalk Block
Ruby Block
LISP Lambda
Python Lambda
C++11 Lambda
Javascript Anonymous function

截获自动变量(书中写的是自动变量--就是局部变量)

截获自动变量值的实例如下

int main() {
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt, val);};

    val = 2;
    fmt = "These values were changed. val = %d\n";

    blk();
    // 打印 val = 10

    return 0;
}

在上面的源码中, Block语法的表达式使用的是它之前声明的自动变量fmt和val

  • Block表达式截获所使用的的自动变量的值, 及保存该自动变量的瞬间值
  • 在执行Block语法表达式之后(Block定义之后), 改写了Block中使用的自动变量的值, 也不会影响Block执行时自动变量的值, 执行结果是执行Block语法是自动变量的瞬间值

__block修饰符

实际上, 自动变量值截获的只能保存执行Block语法瞬间的值, 保存后就不能改写该值, 如果要在block内部修改捕获的变量的值, 需要通过添加__block修饰符

Block的实现

查看block的C++源码实现, 可以使用clang编译器指令

clang -rewrite-objc 源代码文件
int global_val = 1;
static int static_global_val = 2;

int main() {
   int val = 10;
   static int static_val = 3;
   __block int block_val = 4;
   __block NSObject *instance_val = [[NSObject alloc] init];
   void (^blk)(void) = ^{
       global_val *= 1; // 全局变量
       static_global_val *= 2; // 全局静态变量
       static_val *= 3; // 局部静态变量
       block_val = 1; // __block修饰变量
       instance_val = [[NSObject alloc] init]; // 对象
       printf("val: %d, block_val: %d\n", val, block_val);
   };

   val = 2;
   block_val = 3;

   blk();
   return 0;
}

C++源码

int main() {
   int val = 10;
   static int static_val = 3;
   __attribute__((__blocks__(byref))) __Block_byref_block_val_0 
   block_val = {(void*)0,
                (__Block_byref_block_val_0 *)&block_val, 
                0, 
                sizeof(__Block_byref_block_val_0), 
                4};
                
   __attribute__((__blocks__(byref))) __Block_byref_instance_val_1 
   instance_val = {(void*)0,
                    (__Block_byref_instance_val_1 *)&instance_val, 
                    33554432, 
                    sizeof(__Block_byref_instance_val_1),
                    __Block_byref_id_object_copy_131,
                    __Block_byref_id_object_dispose_131, 
                    ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};
                    
                    
   void (*blk)(void) = ((void (*)())&__main_block_impl_0((void*)__main_block_func_0, 
                                                          &__main_block_desc_0_DATA, 
                                                          &static_val, 
                                                          val, 
                                                          (__Block_byref_block_val_0*)&block_val, 
                                                          (__Block_byref_instance_val_1 *)&instance_val, 
                                                          570425344));

   val = 2;
   (block_val.__forwarding->block_val) = 3;

   ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

__main_block_impl_0结构

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;

  // 局部静态变量 指针形式引用
  int *static_val;

  // 一般局部变量
  int val;

  // __block的变量
  __Block_byref_block_val_0 *block_val; // by ref
  // __block的对象
  __Block_byref_instance_val_1 *instance_val; // by ref

  // 初始化构造函数
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int _val, __Block_byref_block_val_0 *_block_val, __Block_byref_instance_val_1 *_instance_val, int flags=0) : static_val(_static_val), val(_val), block_val(_block_val->__forwarding), instance_val(_instance_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock; // isa指向所属的类型, 此处表明block是对象
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

__main_block_func_0函数

// block执行时候调用的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_block_val_0 *block_val = __cself->block_val; // bound by ref
  __Block_byref_instance_val_1 *instance_val = __cself->instance_val; // bound by ref
  int *static_val = __cself->static_val; // bound by copy
  int val = __cself->val; // bound by copy
       
       // 全局变量
       global_val *= 1;

       // 全局静态变量
       static_global_val *= 2;

       // 局部静态变量
       (*static_val) *= 3;

       // __block变量
       (block_val->__forwarding->block_val) = 1;

       // __block对象
       (instance_val->__forwarding->instance_val) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
       printf("val: %d, block_val: %d\n", val, (block_val->__forwarding->block_val));
   }

__main_block_desc_0_DATA结构体

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
                               };

注意 添加了__block的变量都变成了结构体

__Block_byref_block_val_0结构

// block_val生成的结构体
struct __Block_byref_block_val_0 {
  void *__isa; // 说明也变成了对象
__Block_byref_block_val_0 *__forwarding;
 int __flags;
 int __size;
 int block_val;
};

__Block_byref_instance_val_1结构

// __block修饰的instance_val
struct __Block_byref_instance_val_1 {
  void *__isa;
__Block_byref_instance_val_1 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 NSObject *instance_val;
};

__block变量__Block_byref_block_val_0,__Block_byref_instance_val_1结构体并不在Block的__main_block_impl_0结构体中, 这样做是为了可以在多个Block中使用__block变量

截获变量类型

  • 局部变量

    • 基本数据类型
      • 直接截获其值, 不可修改
    • 对象类型
      • 连同对象所有权修饰符一起截获
  • 静态局部变量

    • 以指针形式截获, block内部可以直接改写
  • 全局变量

    • 不截获, block内部可以直接改写
  • 静态全局变量

    • 不截获, block内部可以直接改写
  • 静态局部变量,全局变量,静态全局变量不需要使用__block修饰符

  • __block修饰的基本数据类型的变量都会变成对象(见__Block_byref_block_val_0,__Block_byref_instance_val_1)

什么时候栈上的Block会复制到堆上

  • 调用Block的copy方法时
  • Block作为函数返回值时
  • 将Block复制给附有__strong修饰符id类型的类或Block类型的成员变量
  • 在方法命中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API传递Block时

在多个Block中使用__block变量时, 因为最先会将所有的Block配置在栈上,所以__block变量也会配置在栈上, 在任何一个Block从栈复制到堆时, __block变量也会一并从栈复制到堆并被该Block所持有, 当剩下的Block从栈复制到堆时, 被复制的Block持有__block变量, 并增加__block变量的引用计数

__forwarding指针

  • 栈上的block的__block变量结构体的__forwarding指针指向自己
  • 栈Block进行copy操作后, 生成堆Block, 栈Block的__block变量结构体的__forwarding指针指向堆Block__block变量的结构体,堆上的block的__block变量结构体的__forwarding指针指向自己
    • 通过该功能, 无论是在Block语法中, Block语法外使用__block变量, 还是__block变量配置在栈上或堆上, 都可以顺利地访问到同一个__block变量

理解如有错误 望指正 转载请说明出处