Block詳解

__block說(shuō)明符

Block只能保存局部變量瞬間的值,所以當(dāng)我們嘗試修改截獲的自動(dòng)變量值,就會(huì)報(bào)錯(cuò)。例如:

int val = 0;
void (^blk)(void) = ^(val = 1);
blk();
printf("val = %d\n", val);

該源代碼會(huì)產(chǎn)生編譯錯(cuò)誤:

error: variable is not assignable (missing __block type specifier)

因此,若想修改截獲的局部變量值,就必須用__block修飾

__block int val = 0;
void (^blk)(void) = ^(val = 1);
blk();
printf("val = %d\n", val);

執(zhí)行結(jié)果為:

val = 1;

下面我們?cè)倏匆粋€(gè)例子:

id array = [[NSMutableArray alloc] init];
    void (^blk)(void) = ^{
        id obj = [[NSObject alloc] init];
        [array addObject:obj];
    };

這是沒(méi)問(wèn)題的。如果向截獲的變量array賦值則會(huì)產(chǎn)生編譯錯(cuò)誤。

id array = [[NSMutableArray alloc] init];
    void (^blk)(void) = ^{
        array = [[NSMutableArray alloc] init];
    };

同樣,使用__block修飾array就行了。

另外在使用c語(yǔ)言數(shù)組時(shí),必須小心使用其指針。

const char text[] = "hello";
    void (^blk)(void) = ^{
        printf("%c\n", text[2]);
    };

看似沒(méi)有向截獲的自動(dòng)變量賦值,只是使用了字符串?dāng)?shù)組。但是Block并沒(méi)有實(shí)現(xiàn)截獲c語(yǔ)言數(shù)組。此時(shí)可以使用指針解決問(wèn)題

const char *text = "hello";
    void (^blk)(void) = ^{
        printf("%c\n", text[2]);
    };

Block的實(shí)質(zhì)

我們通過(guò)一個(gè)實(shí)例來(lái)看看block的實(shí)質(zhì)

void (^blk)(void) = ^{
        printf("Block\n");
 };

通過(guò)clang來(lái)轉(zhuǎn)換為c++的代碼

clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk main.m

我們看一下轉(zhuǎn)換后的代碼:

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) {
        printf("Block\n");
 }

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() {
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    //使用block
    (void (*) (struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk)
}

我們可以看到Block的匿名函數(shù)轉(zhuǎn)化為c語(yǔ)言函數(shù)來(lái)處理:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("Block\n");
 }

__cself是指向?qū)嵗陨淼淖兞?code>self。
__main_block_impl_0結(jié)構(gòu)體聲明如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  //這是__main_block_impl_0的構(gòu)造函數(shù)
  __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;
  }
};

__main_block_impl_0結(jié)構(gòu)體由__block_impl和__main_block_desc_0兩個(gè)成員變量和一個(gè)構(gòu)造函數(shù)組成。
其中__block_impl結(jié)構(gòu)體聲明如下:

struct __block_impl {
  void *isa;
  int Flags;//標(biāo)志
  int Reserved;//今后版本升級(jí)所需的區(qū)域
  void *FuncPtr;//函數(shù)指針
};

第二個(gè)成員變量為Desc指針,聲明如下:

struct __main_block_desc_0 {
  unsigned long reserved;//今后版本升級(jí)所需的區(qū)域
  unsigned long Block_size//Block的大小
}

因此,如果展開(kāi)__main_block_impl_0,可記述如下形式:

struct __main_block_impl_0 {
  void *isa;
  int Flags;//標(biāo)志
  int Reserved;//今后版本升級(jí)所需的區(qū)域
  void *FuncPtr;//函數(shù)指針
  struct __main_block_desc_0 * Desc
};

接下來(lái)看看__main_block_impl_0的構(gòu)造函數(shù):

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

_NSConcreteStackBlock用來(lái)初始化block_impl結(jié)構(gòu)體的isa成員變量
flags用于初始化block_impl結(jié)構(gòu)體的flags
fp用于初始化block_impl結(jié)構(gòu)體的FuncPtr

接下來(lái)我們?cè)倏匆幌略瓉?lái)構(gòu)造函數(shù)的調(diào)用:

void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

該源代碼將__main_block_impl_0結(jié)構(gòu)體類型的變量(即棧上生成的__main_block_impl_0結(jié)構(gòu)體實(shí)例的指針)賦值給blk。

然后再看看是如何使用block的

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

去掉轉(zhuǎn)換部分:

(*blk->impl.FuncPtr)(blk);

就是通過(guò)函數(shù)指針FuncPtr來(lái)調(diào)用blk本身。
這也證明了__main_block_func_0的參數(shù)__cself指向block值。

直到現(xiàn)在我們明白了block的本質(zhì):

  • block本質(zhì)就是一個(gè)__main_block_impl_0。
  • block通過(guò)內(nèi)部的函數(shù)指針FuncPtr來(lái)調(diào)用它本身。

還有一點(diǎn)剛才沒(méi)有說(shuō)明,剛才在初始化__block_impl 的isa成員變量的_NSConcreteStackBlock又是什么呢???
要理解_NSConcreteStackBlock,我們結(jié)合objc_object,它也有一個(gè)isa指針,用于指向該對(duì)象所屬的類。
同理:在將Block作為對(duì)象處理時(shí),__block_impl的isa指針指向的類的信息保存在_NSConcreteStackBlock上。即它是在棧上生成的__block_impl結(jié)構(gòu)體實(shí)例。



截獲自動(dòng)變量值

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

clang之后:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy

        printf(fmt, val);
}

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 dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

我們先看一下不同之處:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  const char *fmt;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

Block將所使用的局部變量作為成員變量追加到了__main_block_impl_0結(jié)構(gòu)體中。
注意:未使用的的變量將不會(huì)追加。

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val)

將追加的局部變量作為參數(shù)來(lái)初始化結(jié)構(gòu)體。
__main_block_impl_0展開(kāi)代碼如下:

    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = 0;
    impl.FuncPtr = _main_block_func_0;
    Desc =&__main_block_desc_0_DATA;
    fmt = "val = %d\n"

由此可見(jiàn),在__main_block_impl_0實(shí)例中,自動(dòng)變量被截獲。

我們?cè)賮?lái)看看Block的實(shí)現(xiàn):

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fmt = __cself->fmt; // bound by copy
  int val = __cself->val; // bound by copy

        printf(fmt, val);
}

因?yàn)開(kāi)_main_block_impl_0截獲了fmt和val變量,所以就可以直接執(zhí)行了。

總的來(lái)說(shuō),所謂“截獲自動(dòng)變量值”意味著在執(zhí)行block語(yǔ)法時(shí),block語(yǔ)法所使用的局部變量被保存到block的結(jié)構(gòu)體實(shí)例中。


__block說(shuō)明符

__block說(shuō)明符類似于static、auto,他們用于指定將變量值設(shè)置到哪個(gè)存儲(chǔ)域中。auto表示作為局部變量存儲(chǔ)在棧中,static表示作為靜態(tài)變量存儲(chǔ)在數(shù)據(jù)區(qū)中。

__block int val = 10;
    void (^blk)(void) = ^{
        val = 1;
    };

該代碼編譯后如下:

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val) = 1;
 }

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}

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};
int main() {
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
}

這個(gè)__block變量是怎樣轉(zhuǎn)換過(guò)來(lái)的呢?

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};

__block居然變成了結(jié)構(gòu)體類型的局部變量,即棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例。
它包含原自動(dòng)變量的成員變量val

我們?cè)賮?lái)看看__main_block_impl_0這個(gè)結(jié)構(gòu)體有什么不同

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

新增了一個(gè)成員變量__Block_byref_val_0,將這個(gè)成員變量作為參數(shù)來(lái)初始化這個(gè)結(jié)構(gòu)體
__block賦值的代碼又如何呢?

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_val_0 *val = __cself->val; // bound by ref

        (val->__forwarding->val) = 1;
 }

__block變量賦值比較復(fù)雜,__main_block_impl_0結(jié)構(gòu)體實(shí)例持有指向__block變量的__Block_byref_val_0結(jié)構(gòu)體實(shí)例的指針。
__Block_byref_val_0結(jié)構(gòu)體實(shí)例的成員變量__forwarding持有該實(shí)例自身的指針。通過(guò)成員變量__forwarding訪問(wèn)成員變量val。

至此為止,我們只是大概了解了__block類型,但是還有兩個(gè)問(wèn)題沒(méi)有解決:
*Block超出變量作用域可存在的理由

  • __block結(jié)構(gòu)體中__forwording成員變量是干嘛的。

我們繼續(xù)分析。

Block存儲(chǔ)域

由前面的講解,我們知道和
Block的本質(zhì):棧上生成的__main_block_impl_0結(jié)構(gòu)體實(shí)例
__block的本質(zhì):棧上生成的__Block_byref_val_0結(jié)構(gòu)體實(shí)例

我們之前說(shuō)過(guò)Block其實(shí)也是一個(gè)OC對(duì)象,它所屬的類是_NSConcretStackBlock,除了這個(gè),還有其他兩個(gè):

  • _NSConcretStackBlock:設(shè)置在棧上
  • _NSConcretGlobalBlock:設(shè)置在程序的數(shù)據(jù)區(qū)域(.data)中
  • _NSConcretMallocBlock:設(shè)置在堆上

配置在全局變量的block,從變量作用外也可以通過(guò)指針安全的使用。
配置在棧上的block,如果其所屬的變量作用域結(jié)束,該block就會(huì)被廢棄。由于__block變量也配置在棧上,如果其所屬的變量作用域也結(jié)束了,__block變量也會(huì)被廢棄。

什么時(shí)候可以設(shè)置在程序的數(shù)據(jù)區(qū)域中呢?

  • 使用的block是全局的block
  • 即使不是全局的block,如果不截獲的自動(dòng)變量,也會(huì)設(shè)置在.data區(qū)。

除以上這兩種情況,Block都是_NSConcretStackBlock類對(duì)象,設(shè)置在棧上。

現(xiàn)在我們回答第一個(gè)問(wèn)題:Block超出變量作用域可存在的理由??
因?yàn)锽locks提供了將Block和__block變量從棧上復(fù)制到對(duì)上的辦法來(lái)解決這個(gè)問(wèn)題。將配置在棧上的Block復(fù)制到堆上,這樣即使Block變量作用域結(jié)束,堆上的Block也可以繼續(xù)存在。

復(fù)制到堆上的Block就是_NSConcretMallocBlock類對(duì)象。

impl.isa = &_NSConcretMallocBlock

接下來(lái)我們重點(diǎn)看看Blocks提供的復(fù)制方法:
實(shí)際上在ARC下,大多數(shù)情形下編譯器會(huì)自動(dòng)的判斷,自動(dòng)生成將Block從棧上復(fù)制到堆上的代碼。我們看一下將Block作為函數(shù)值返回的代碼。

typedef int (^blk_t) (int);

blk_t func(int rate) {
    return ^(int count) {
        return rate * count;
    };
}

該源代碼返回配置在棧上的Block,當(dāng)變量作用域結(jié)束時(shí),這個(gè)Block就會(huì)被廢棄。雖然如此,但該源代碼通過(guò)編輯器可轉(zhuǎn)換如下:

blk_t func(int rate) {
  blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
  tmp = objc_retainBlock(tmp);
  return objc_autoreleaseReturnValur(tmp);
}

objc_retainBlock就是_Block_copy函數(shù)。
因此當(dāng)Block作為函數(shù)值返回時(shí),編譯器會(huì)自動(dòng)生成復(fù)制到堆上的代碼。

如果編譯器不能生成復(fù)制到堆上的代碼,就需要手動(dòng)調(diào)用alloc/new/copy/mutableCopy的任意一個(gè)代碼。

哪些情況編譯器會(huì)自動(dòng)將block從棧復(fù)制到堆上:

  • 使用Block的copy實(shí)例方法。
  • Block作為函數(shù)返回值返回時(shí)。
  • 將Block賦值給附有__strong修飾符的id類型的類或者Block類型的實(shí)例變量時(shí)。
  • 在方法名中使用usingBlock或者使用GCD的API中傳遞Block時(shí)

第二個(gè)問(wèn)題:__block結(jié)構(gòu)體中__forwording成員變量是干嘛的??
它可以實(shí)現(xiàn)無(wú)論__block變量配置在棧上還是堆上都能夠正確訪問(wèn)__block變量。
有時(shí)__block變量配置在堆上,也可以訪問(wèn)棧上的__block變量,這是因?yàn)闂I系慕Y(jié)構(gòu)體成員變量__forwarding指向堆上的結(jié)構(gòu)體成員變量。

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

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

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