Block

Block的本質(zhì)

Block的本質(zhì)是一個OC對象,它內(nèi)部也有一個isa指針,
Block是封裝了函數(shù)調(diào)用和函數(shù)調(diào)用環(huán)境的OC對象

Block的底層結(jié)構(gòu)

image1.png
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_5c_f1wlx5wn2nv1zl04kg_35prm0000gn_T_main_54fbf7_mi_0);
        }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

Block底層數(shù)據(jù)結(jié)構(gòu)中有一個struct __block_impl implstruct __main_block_desc_0* Desc

  • __block_impl 中isa指針和void *FuncPtr封裝函數(shù)
  • __main_block_desc_0中是Block的描述信息

Block捕獲變量

為了保證block內(nèi)部能夠正常訪問外部的變量,Block內(nèi)部有個變量捕獲機制

image2.png

image3.png

上圖顯示的是三種變量的捕獲,大家看到局部變量a在block中無法被修改,但是用static修飾的b和全局變量c可以直接修改,這是為什么呢???
我們使用clang編譯器編譯一下block的底層看看


image4.png

變量block內(nèi)部竟然直接引用的是變量的a的值和變量b的指針。

現(xiàn)在從堆棧上解釋一下為什么:

  • block內(nèi)部封裝了一個函數(shù),而變量a是屬于main函數(shù)的局部變量,如果在block中更改a的值的話,在棧中是沒法調(diào)用另一個函數(shù)的局部變量
  • 使用static修飾的變量b,由于是指針引用,完全可以通過這個指針找到b,
  • c是屬于全局變量,block是完全可以找到的

Block的類型

Block有3中類型,可以通過class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型,直到NSObject。

  • NSGlobalBlock
  • NSStackBlock
  • NSMallocBlock
    image5.png

    image6.png

    image7.png
image8.png

在ARC中編譯器會根據(jù)集中情況,自動將棧上的block復(fù)制到堆上

  • block作為函數(shù)返回值時候
  • 將block賦值給__strong指針時候
  • block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時
  • block作為GCD API的方法參數(shù)時

Block捕獲對象類型

當(dāng)block內(nèi)部訪問了對象類型的auto變量時

如果block在棧上,將不會對auto變量產(chǎn)生強引用

如果block被copy到堆上
  • 會調(diào)用block內(nèi)部的copy函數(shù),
  • _Block_object_assign函數(shù)會根據(jù)auto變量的修飾符(__strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強引用(retain)或者弱引用
    #######如果block從堆上移除
  • 會調(diào)用block內(nèi)部的dispose函數(shù)
  • dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
  • _Block_object_dispose函數(shù)會自動釋放引用的auto變量(release)


    image8.png
在使用clang轉(zhuǎn)換OC為C++代碼時,可能會遇到以下問題
cannot create __weak reference in file using manual reference
解決方案:支持ARC、指定運行時系統(tǒng)版本,比如
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

__block修飾符

  • __block可以用于解決block內(nèi)部無法修改auto變量值的問題
  • __block不能修飾全局變量、靜態(tài)變量(static)
  • 編譯器會將__block變量包裝成一個對象
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
image9.png

__block的內(nèi)存管理

  • 當(dāng)block在棧上時,并不會對__block變量產(chǎn)生強引用
  • 當(dāng)block被copy到堆時
    會調(diào)用block內(nèi)部的copy函數(shù)
    copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
    _Block_object_assign函數(shù)會對__block變量形成強引用(retain)
  • 當(dāng)block從堆中移除時
    會調(diào)用block內(nèi)部的dispose函數(shù)
    dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    _Block_object_dispose函數(shù)會自動釋放引用的__block變量(release)


    image10.png

    image11.png

__block的__forwarding指針

image12.png

對象類型的auto變量、__block變量

  • 當(dāng)block在棧上時,對它們都不會產(chǎn)生強引用
  • 當(dāng)block拷貝到堆上時,都會通過copy函數(shù)來處理它們
    __block變量(假設(shè)變量名叫做a)
    _Block_object_assign((void)&dst->a, (void)src->a, 8/BLOCK_FIELD_IS_BYREF/);
  • 對象類型的auto變量(假設(shè)變量名叫做p)
    _Block_object_assign((void)&dst->p, (void)src->p, 3/BLOCK_FIELD_IS_OBJECT/);
  • 當(dāng)block從堆上移除時,都會通過dispose函數(shù)來釋放它們
    __block變量(假設(shè)變量名叫做a)
    _Block_object_dispose((void)src->a, 8/BLOCK_FIELD_IS_BYREF*/);
  • 對象類型的auto變量(假設(shè)變量名叫做p)
    _Block_object_dispose((void)src->p, 3/BLOCK_FIELD_IS_OBJECT*/);

被__block修飾的對象類型

  • 當(dāng)__block變量在棧上時,不會對指向的對象產(chǎn)生強引用
  • 當(dāng)__block變量被copy到堆時
    會調(diào)用__block變量內(nèi)部的copy函數(shù)
    copy函數(shù)內(nèi)部會調(diào)用_Block_object_assign函數(shù)
    _Block_object_assign函數(shù)會根據(jù)所指向?qū)ο蟮男揎椃╛_strong、__weak、__unsafe_unretained)做出相應(yīng)的操作,形成強引用(retain)或者弱引用(注意:這里僅限于ARC時會retain,MRC時不會retain)
  • 如果__block變量從堆上移除
    會調(diào)用__block變量內(nèi)部的dispose函數(shù)
    dispose函數(shù)內(nèi)部會調(diào)用_Block_object_dispose函數(shù)
    _Block_object_dispose函數(shù)會自動釋放指向的對象(release)

循環(huán)引用

image13.png

image14.png
解決循環(huán)引用問題 - MRC
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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