Block

1、Block

struct Block_layout {
    void *isa;//指向所屬類的指針,也就是block的類型 NSStackBlock NSGlobalBlock  NSMallocBlock   
    int flags;//標志變量,在實現(xiàn)block的內(nèi)部操作時會用到
    int reserved;//保留變量
    void (*invoke)(void *, ...);//執(zhí)行時調(diào)用的函數(shù)指針,block內(nèi)部的執(zhí)行代碼都在這個函數(shù)中
    struct Block_descriptor *descriptor;//block的詳細描述,包含 copy/dispose 函數(shù),處理block引用外部變量時使用
    /* Imported variables. */
//variables: block范圍外的變量,如果block沒有調(diào)用任何外部變量,該變量就不存在
};

struct Block_descriptor {
    unsigned long int reserved;//保留變量
    unsigned long int size;//block的內(nèi)存大小
    void (*copy)(void *dst, void *src);// 拷貝block中被 __block 修飾的外部變量
    void (*dispose)(void *);//和 copy 方法配置應用,用來釋放資源
};

2、Block語法

@property(nonatomic, copy)  void (^NormalBlock)(void);

typedef void (^NormalBlock)(void);

@property(nonatomic, copy)  NormalBlock block;
^【返回值類型】【參數(shù)列表】【表達式】
exp. ^int (int count) {return count + 1;}
注意:【返回值類型】和【參數(shù)列表】可省略

void (^blockName) (int parameter);
//void代表返回值類型,后面一次是block名,參數(shù)

3、Block有哪幾種類型

NSStackBlock存儲于棧區(qū)

block 內(nèi)部引用外部變量,retain、release 操作無效,存儲于棧區(qū),變量作用域結(jié)束時,其被系統(tǒng)自動釋放銷毀。
MRC 環(huán)境下,[[mutableAarry addObject: blockA],(在arc中不用擔心此問題,因為arc中會默認將實例化的block拷貝到堆上)在其所在作用域結(jié)束也就是函數(shù)出棧后,從mutableAarry中取到的blockA已經(jīng)被回收,變成了野指針。正確的做法是先將blockA copy到堆上,然后加入數(shù)組。支持copy,copy之后生成新的NSMallocBlock類型對象。

NSGlobalBlock 存儲于程序數(shù)據(jù)區(qū)

block 內(nèi)部沒有引用外部變量的 Block 類型都是 NSGlobalBlock 類型,存儲于全局數(shù)據(jù)區(qū),由系統(tǒng)管理其內(nèi)存,retain、copy、release操作都無效。引用static也為globalBlock

NSMallocBlock 存儲于堆區(qū)

如上例中的_block,[blockA copy]操作后變量類型變?yōu)?NSMallocBlock,支持retain、release,雖然 retainCount 始終是 1,但內(nèi)存管理器中仍然會增加、減少計數(shù),當引用計數(shù)為零的時候釋放(可多次retain、release 操作驗證)。copy之后不會生成新的對象,只是增加了一次引用,類似retain,盡量不要對Block使用retain操作。

在ARC環(huán)境下,Block也是存在__NSStackBlock的時候的,平時見到最多的是_NSMallocBlock,是因為我們會對Block有賦值操作,所以ARC下,block 類型通過=進行傳遞時,會導致調(diào)用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈。并導致 NSStackBlock 類型的 block 轉(zhuǎn)換為 NSMallocBlock 類型

4、Block的結(jié)構(gòu)(Clang后的對應)

原代碼

-(void)blockDemo{
    void (^block)(void) = ^{   
};
}

clang后:

struct __block_impl {  
void *isa;//指向所屬類的指針,也就是block的類型
int Flags;  //標志變量,在實現(xiàn)block的內(nèi)部操作時會用到
int Reserved;  //保留變量
void *FuncPtr;//block執(zhí)行時調(diào)用的函數(shù)指針
};
//block內(nèi)部實現(xiàn) func0
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
 }

static struct __ViewController__blockDemo_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
 __ViewController__blockDemo_block_desc_0_DATA = { 0, sizeof(struct __ViewController__blockDemo_block_impl_0)};


static void _I_ViewController_blockDemo(ViewController * self, SEL _cmd) {
    void (*block)(void) = ((void (*)())&__ViewController__blockDemo_block_impl_0((void *)__ViewController__blockDemo_block_func_0, &__ViewController__blockDemo_block_desc_0_DATA));
}

捕獲變量 __block 源代碼

-(void)blockDemo{
    
    __block int a = 100;
    void (^block)(void) = ^{
        a++;
    };
    
    block();
}

clang后

struct __Block_byref_a_0 {
  void *__isa; //指向所屬類的指針,被初始化為 (void*)0
__Block_byref_a_0 *__forwarding;//指向?qū)ο笤诙阎械目截? int __flags;//標志變量,在實現(xiàn)block的內(nèi)部操作時會用到
 int __size;//對象的內(nèi)存大小
 int a;//原始類型的變量
};
static void __ViewController__blockDemo_block_func_0(struct __ViewController__blockDemo_block_impl_0 *__cself) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref


        (a->__forwarding->a)++;

    }

可以看到 多了一個結(jié)構(gòu)體 被__block修飾的變量被封裝成了一個對象,類型為__Block_byref_a_0,然后把&a作為參數(shù)傳給了block。
其中,isa、__flags 和 __size 的含義和之前類似,而 __forwarding 是用來指向?qū)ο笤诙阎械目截?,runtime.c 里有源碼說明:

static void _Block_byref_assign_copy(void *dest, const void *arg, const int flags) {
    ...
    struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
    copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
    // 堆中拷貝的forwarding指向它自己
    copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
    // 棧中的forwarding指向堆中的拷貝
    src->forwarding = copy;  // patch stack to point to heap copy
    copy->size = src->size;
    ...
}

這樣做是為了保證在 block內(nèi) 或 block 變量后面對變量a的訪問,都是直接訪問堆內(nèi)的對象,而不上棧上的變量。同時,在 block 拷貝到堆內(nèi)時,它所捕獲的由 __block 修飾的局部基本類型也會被拷貝到堆內(nèi)(拷貝的是封裝后的對象),從而會有 copy 和 dispose處理函數(shù)。

5、 Block copy的過程

Block經(jīng)過copy之后會在desc里生成的2個函數(shù)

  • copy函數(shù)
    調(diào)用時機 棧上的Block復制到堆時
  • dispose函數(shù)
    調(diào)用時機 堆上的Block被廢棄時

Block內(nèi)部訪問了帶有__block修飾符的對象類型的auto變量時

  • block在棧上時,并不會對__block變量產(chǎn)生強引用
  • blockcopy到堆時
    • 會調(diào)用block內(nèi)部的copy函數(shù)
    • copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
    • _Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃?code>__strong, __weak, __unsafe_unretained)做出相應的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain

6、__block的作用

  • __block可以用于解決block內(nèi)部無法修改auto變量值的問題
  • __block不能修飾全局變量、靜態(tài)變量(static
    編譯器會將__block變量包裝成一個對象
  • __block修改變量:age->__forwarding->age
  • __Block_byref_age_0結(jié)構(gòu)體內(nèi)部地址和外部變量age是同一地址

7、__block的結(jié)構(gòu)(Clang后的對應)

編譯器會將 __block變量包裝成一個對象

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;//age的地址
 int __flags;
 int __size;
 int age;//age 的值
};

8、__forwarding指針的作用

__forwarding, 它是結(jié)構(gòu)體__Block_byref_abc_0的組成部分, 且它的類型是__Block_byref_abc_0 *

  • 棧上__block__forwarding指向本身
  • 棧上__block復制到堆上后,棧上block__forwarding指向堆上的block,堆上block__forwarding指向本身

__forwarding指針存在的意義就是,無論在任何內(nèi)存位置,都可以順利地訪問同一個__block 變量。

9、Block 釋放的過程

block從堆中移除時

1、會調(diào)用block內(nèi)部的dispose函數(shù)
2、 dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
3、_Block_object_dispose函數(shù)會自動釋放引用的__block變量(release)

block從堆上移除時,都會通過dispose函數(shù)來釋放它們

10、copy_dispose

11、Block循環(huán)引用

三種方式:__weak、__unsafe_unretained、__block 解決循環(huán)引用
1、__weak:不會產(chǎn)生強引用,指向的對象銷毀時,會自動讓指針置為nil
2、__unsafe_unretained:不會產(chǎn)生強引用,不安全,指向的對象銷毀時,指針存儲的地址值不變
3、__block:必須把引用對象置位nil,并且要調(diào)用該block

12、Block捕獲(多重嵌套情況)

block內(nèi)部會專門新增一個成員來外面變量的值,這個操作稱之為捕獲

13、Block hook的幾種實現(xiàn)

Hook Block 交換block的實現(xiàn)
BlockHookfishhook,BlockHookDemoYSBlockHook...

  • 1、交換block的實現(xiàn) aspect 原理就是在運行期間動態(tài)交換兩個方法所指向的IMP指針,那么換作Block也是一樣的道理。只要將invoke指針指向我們自定義的函數(shù)地址,就可以交換block的實現(xiàn)
  • 2、
  • 3、

14、Block為何會有Private Data

15、如果獲取Block參數(shù)的個數(shù)及其類型

16、關于BLOCK_HAS_EXTENDED_LAYOUT的一些內(nèi)容

17、Block 常見題

1、Block的原理是怎樣的?本質(zhì)是什么?
2、__block的作用是什么?有什么使用注意點?
3、Block的屬性修飾詞為什么是copy?使用Block有哪些使用注意?
4、Block在修改NSMutableArray,需不需要添加__block?

注:

clang 用法:clang -fobjc-arc -framework Foundation HelloWord.m -o HelloWord

  • -fobjc-arc表示編譯器需要支持ARC特性
  • -framework Foundation表示引用Foundation框架
  • HelloWord.m為需要進行編譯的源代碼文件
  • -o HelloWord表示輸出的可執(zhí)行文件的文件名
  • 1、clang 生成C++
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk ViewController.m
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容