Blocks Programming Topics

Blocks Programming Topicshtml

Block對象是C語法的, 且是一個運行時特性, 它們相似於標準C函數, 可是除了可執行代碼外它們還可能包含到自動(棧)或託管(堆)內存的變量綁定, 所以, 一個block能夠維護一組狀態(數據), 當執行Block時能夠用來影響執行行爲c++

可使用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可以使用其定義範圍內的變量bash

若是將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內的只讀變量app

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變量

理解若有錯誤 望指正 轉載請說明出處

相關文章
相關標籤/搜索