Blocks的實現(xiàn)

Blocks是“帶有自動變量值的匿名函數(shù)”僚饭。本文通過Blocks的實現(xiàn)來理解Blocks呛谜。

本文目錄

  1. Blocks的實質(zhì)
  2. 截獲自動變量
  3. 修改Block外部變量的兩種方式
  4. Block存儲域
  5. 截獲對象
  6. __block變量和對象
  7. Block循環(huán)引用
  8. copy/release

使用工具:clang(LLVM編譯器)將OC代碼轉(zhuǎn)換成可讀的源代碼。

clang -rewrite-objc 源代碼文件名

Block的實質(zhì)

Block的實質(zhì)就是OC對象傍衡,Block函數(shù)代碼實際上被作為簡單的C語言函數(shù)來處理辛慰。

首先寫一段最簡單的Blocks代碼

int main(int argc, const char * argv[]) {
    //聲明并定義一個block對象
    void (^blk)(void) = ^{
        printf("Hello,world!\n");
    };
    //調(diào)用block對象
    blk();
    return 0;
}

轉(zhuǎn)換成C代碼之后是這樣区匠,Block實際上是由結(jié)構(gòu)體聲明的。

struct __block_impl {
  void *isa;//這里與OC中類對象一樣,指針指向的是類對象
  int Flags;//標(biāo)志
  int Reserved;//版本升級所需要的區(qū)域
  void *FuncPtr;//函數(shù)指針
};

static struct __main_block_desc_0 {
  size_t reserved;//保留區(qū)域
  size_t Block_size;//Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

//結(jié)構(gòu)體__block_impl和__main_block_desc_0組成了最簡單的block結(jié)構(gòu)體__main_block_impl_0
struct __main_block_impl_0 {

  struct __block_impl impl;
  struct __main_block_desc_0* Desc;

//構(gòu)造函數(shù)驰弄,第一個參數(shù)是函數(shù)指針麻汰,第二個參數(shù)是描述信息結(jié)構(gòu)體
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;//定義block的類型,總共有三種戚篙,全局的靜態(tài)block五鲫,棧中的block,堆中的block岔擂。
    impl.Flags = flags;//初始化flag
    impl.FuncPtr = fp;//傳遞函數(shù)地址
    Desc = desc;//初始化__main_block_desc_0結(jié)構(gòu)體
  }
};

//block中的函數(shù)聲明
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        printf("Hello,world!\n");
    }

//main函數(shù)
int main(int argc, const char * argv[]) {
    //定義一個void *的blk指針位喂,等號右邊是使用__main_block_impl_0的構(gòu)造函數(shù)進行初始化,傳入的第一個參數(shù)是函數(shù)指針乱灵,第二個參數(shù)是描述信息結(jié)構(gòu)體
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

    //調(diào)用函數(shù)指針塑崖,執(zhí)行函數(shù)
    //(void (*)(__block_impl *))這部分是將FuncPtr轉(zhuǎn)換成該類型的函數(shù)指針,返回值為void *痛倚,傳參為void规婆,編譯器會在傳參前添加一個傳遞結(jié)構(gòu)體自身的指針
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

通過上面的源代碼,可以很清晰的了解到Block是如何實現(xiàn)的蝉稳,而在Block的結(jié)構(gòu)體__block_impl中聋呢,發(fā)現(xiàn)了isa指針,與基于objc_object結(jié)構(gòu)體的OC類對象結(jié)構(gòu)體一樣颠区,所以削锰,Block其實就是一個OC對象。

截獲自動變量

再來看稍微復(fù)雜一點的代碼

int main(int argc, const char * argv[]) {
    
    int dmy = 256;//沒有被block使用到的變量
    int val = 10;//被block捕獲的變量
    const char *fmt = "val = %d\n";//被block捕獲的變量
    void (^blk)(void) = ^{
        printf(fmt, val);//使用fmt字符串打印val變量
    };
    //定義完block對象后毕莱,首先修改val變量器贩,看修改后block區(qū)塊里捕獲的對象是否也修改
    val = 2;
    //同樣修改fmt指針指向的常量字符串
    fmt = "These values were changed. val = %d\n";
    //調(diào)用block函數(shù)blk()
    blk();

    return 0;
}

轉(zhuǎn)換之后的C代碼

struct __main_block_impl_0 {
  struct __block_impl impl;//block基本信息結(jié)構(gòu)體
  struct __main_block_desc_0* Desc;//描述結(jié)構(gòu)體
  //這里可以看到兩個函數(shù)內(nèi)的局部變量,被聲明到了block的結(jié)構(gòu)體中
  const char *fmt;
  int val;
  //構(gòu)造函數(shù)朋截,其中構(gòu)造函數(shù)也初始化了fmt和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函數(shù)定義
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    const char *fmt = __cself->fmt; //拷貝block結(jié)構(gòu)體里的fmt變量
    int val = __cself->val; //拷貝val變量

    printf(fmt, val);
    }

int main(int argc, const char * argv[]) {
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    //block對象blk的聲明和定義蛹稍,傳入函數(shù)指針和描述結(jié)構(gòu)體,以及fmt和val兩個變量部服,這里的傳值是拷貝
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));

    val = 2;

    printf("val = %d\n", val);
    fmt = "These values were changed. val = %d\n";
    //調(diào)用blk函數(shù)
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    return 0;
}

通過上面的代碼唆姐,可以發(fā)現(xiàn),Block在函數(shù)內(nèi)捕獲的fmt和val兩個自動變量均在Block結(jié)構(gòu)體內(nèi)重新聲明了相同類型和名稱的變量廓八,并且在構(gòu)造函數(shù)中通過拷貝的方式將值傳遞進來奉芦,也就是說,在定義blk對象的這條語句時剧蹂,就已經(jīng)將fmt和val的值傳遞進了Block結(jié)構(gòu)體實例對象內(nèi)声功,此時Block對象中保存的值是此時此刻fmt和val的值的拷貝,之后無論如何修改main函數(shù)中fmt和val的內(nèi)容宠叼,都不會影響到block中的保存的fmt和val的副本先巴。

當(dāng)然,若在函數(shù)內(nèi)創(chuàng)建一個指針變量,例如在上邊的代碼添加

int *p = &val;//將val的地址賦值給指針p

此時伸蚯,Block的結(jié)構(gòu)體內(nèi)也會申請一個int指針p摩渺,在構(gòu)造函數(shù)的參數(shù)列表,傳遞的也是指針類型剂邮,所以在Block對象里修改p的地址對應(yīng)的內(nèi)容時摇幻,main函數(shù)中的指針p和val的值也會被修改。

在Blocks中抗斤,截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲囚企,使用指針可以解決該問題丈咐。
const char text[] = "Hello";
改成 const char *text = "Hello";

修改Block外部變量的兩種方式

Block類型變量

  • 自動變量
  • 函數(shù)參數(shù)
  • 靜態(tài)變量
  • 靜態(tài)全局變量
  • 全局變量

如果想修改一個外部變量瑞眼,有兩種方式可以實現(xiàn)。

方法一:靜態(tài)變量棵逊、靜態(tài)全局變量伤疙、全局變量

這三種變量,靜態(tài)全局變量和全局變量的訪問方式?jīng)]有任何改變辆影,Blocks可以直接使用徒像。靜態(tài)變量則是通過指針的方式傳遞。

寫一段包括這三種變量的代碼

int g_val = 1;//全局變量
static int gs_val = 2;//全局靜態(tài)變量

int main(int argc, const char * argv[]) {
    //靜態(tài)變量
    static int s_val = 3;
    //block代碼塊蛙讥,截獲這三種變量
    void (^blk)(void) = ^{
        g_val *= 1;
        gs_val *= 2;
        s_val *= 3;
    };

轉(zhuǎn)換后的代碼如下:


int g_val = 1;
static int gs_val = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *s_val;//對于靜態(tài)變量s_val锯蛀,使用s_val的指針對其進行訪問

  //在構(gòu)造函數(shù)里初始化s_val
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_s_val, int flags=0) : s_val(_s_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *s_val = __cself->s_val; // bound by copy

        g_val *= 1;//對全局變量和全局靜態(tài)變量的訪問與轉(zhuǎn)換前沒有任何區(qū)別
        gs_val *= 2;//...
        (*s_val) *= 3;//使用指針的方式訪問s_val
    }

int main(int argc, const char * argv[]) {
    
    static int s_val = 3;
    
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &s_val));
    return 0;
}

對于靜態(tài)變量s_val,使用指針對其訪問次慢,保存靜態(tài)變量s_val的指針旁涤,傳遞給__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)并保存。這是超出作用域使用變量的最簡單方法迫像。

而對于自動變量來說劈愚,如果使用指針的方式進行訪問,當(dāng)變量作用域結(jié)束的同時闻妓,原來的自動變量被廢棄菌羽,此時Block中超過變量作用域的變量則不能通過指針訪問,訪問結(jié)果是未定義的由缆。解決這個問題注祖,則可以使用__block說明符。

方法二:__block存儲域類說明符(__block storage-class-specifier)

C語言中有一下存儲域類說明符:

  • typedef
  • extern
  • static
  • auto
  • register

__block說明符類似于static均唉、auto和register說明符氓轰,它們用于指定將變量值設(shè)置到哪個存儲域中。如浸卦,auto表示作為自動變量存儲在棧中署鸡,static表示作為靜態(tài)變量存儲在數(shù)據(jù)區(qū)域中。

下面來寫一段__block說明符的代碼。

int main(int argc, const char * argv[]) {
    //為val變量添加__block說明符
    __block int val = 10;
    //現(xiàn)在可以在Block代碼塊中為val正確賦值
    void (^blk)(void) = ^{
        val = 1;
    };
    return 0;
}

轉(zhuǎn)換后的源代碼如下:

//新的結(jié)構(gòu)體靴庆,用來保存用__block修飾的對象
struct __Block_byref_val_0 {
  void *__isa;//結(jié)構(gòu)體對象
__Block_byref_val_0 *__forwarding;//指向自身
 int __flags;
 int __size;
 int val;//保存被__block修飾的val變量的值
};
//Block的結(jié)構(gòu)體
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_val_0 *val; //保存了用__block修飾的變量的結(jié)構(gòu)體
  //構(gòu)造函數(shù)时捌,使用_val->__forwarding來初始化val,這個設(shè)計的用意在后邊說明
  __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;
  }
};

//Block代碼塊的內(nèi)容炉抒,該例為對val進行賦值
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(int argc, const char * argv[]) {
    //使用__Block_byref_val_0結(jié)構(gòu)體來創(chuàng)建val對象奢讨,并對每一個值進行初始化
    __attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};
    //定義Block對象
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
    return 0;
}

上面的代碼可以看到,使用__block說明符修飾的自動變量焰薄,在源代碼中實際上是一個__Block_byref_val_0的結(jié)構(gòu)體實例拿诸,在__main_block_impl_0中以指針的形式持有,對該變量的修改實際上是通過結(jié)構(gòu)體中的__forwarding指針指向的結(jié)構(gòu)體實例塞茅,對val值進行修改亩码。

Block存儲域

在之前的代碼中,可以看到在__main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)中野瘦,將結(jié)構(gòu)體中的isa變量賦值為_NSConcreteStackBlock描沟,而與之類似的還有_NSConcreteGlobalBlock和_NSConcreteMallocBlock類。

  • _NSConcreteStackBlock:該類的對象設(shè)置在棧上
  • _NSConcreteGlobalBlock:該類對象設(shè)置在程序的數(shù)據(jù)區(qū)域(.data區(qū))中
  • _NSConcreteMallocBlock:該類對象則設(shè)置在由malloc函數(shù)分配的內(nèi)存塊(堆)中

其中鞭光,當(dāng)全局變量區(qū)域定義Block代碼塊和Block語法的表達式不使用截獲的自動變量時吏廉,Block即是_NSConcreteGlobalBlock對象。

雖然通過clang轉(zhuǎn)換的源代碼通常是_NSConcreteStackBlock對象惰许,但實現(xiàn)上卻有不同席覆。

將Block從棧復(fù)制到堆上

當(dāng)Block語法記述的變量作用域結(jié)束時,棧上的Block和__block變量都會被廢棄汹买,此時通過Blocks提供的復(fù)制方法佩伤,將Block和__block變量從棧上復(fù)制到堆上,那么即使記述Block變量的作用域結(jié)束卦睹,堆上的Block還可以繼續(xù)存在畦戒。

復(fù)制到堆上的Block將_NSConcreteMallocBlock類對象寫入Block用結(jié)構(gòu)體實例的成員變量isa。

首先看ARC有效時结序,自動生成的將Block從棧復(fù)制到堆上的代碼障斋。

typedef int (^blk_t)(int);

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

轉(zhuǎn)換過后的源代碼:

blk_t func(int rate){

    blk_t tmp = &__func_block_impl_0(__func_block_func_0, &__func_block_desc_0_DATA, rate);
    
    //objc_retainBlock函數(shù)實際上就是_Block_copy函數(shù),該函數(shù)將棧上的Block復(fù)制到堆上徐鹤,復(fù)制后垃环,將堆上的地址作為指針賦值給變量tmp。
    //這里tmp是typedef int (^blk_t)(int)返敬,它的源代碼實際上是typedef int (*blk_t)(int)函數(shù)指針遂庄,所以可以存儲指針。
    tmp = objc_retainBlock(tmp);
    
    //將堆上的Block作為Objective-C對象劲赠,注冊到autoreleasepool中涛目,然后返回該對象秸谢。
    return objc_autoreleaseReturnValue(tmp);
}

上面是編譯器自動生成的代碼,而編譯器在以下情況無法自動生成復(fù)制到堆上的代碼

  • 向方法或函數(shù)的參數(shù)傳遞Block時

以下方法或函數(shù)不用手動賦值

  • Cocoa框架的方法且方法名中含有usingBlock等時霹肝,如NSArray類的enumerateObjectsUsingBlock實例方法以及dispatch_async函數(shù)時估蹄,但在NSArray的initWithObjects方法中不能自動生成。
  • Grand Central Dispath的API

程序員可以手動調(diào)用Block的copy方法沫换。

typedef int (^blk_t)(int);

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

blk = [blk copy];

對于在不同區(qū)域的Block調(diào)用copy方法所進行的動作如下表:

Block的類 副本源的配置存儲域 復(fù)制效果
_NSConcreteStackBlock 從棧復(fù)制到堆
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域 什么也不做
_NSConcreteMallocBlock 引用計數(shù)增加

不管Block配置在何處臭蚁,用copy方法復(fù)制都不會引起任何問題。在不確定時調(diào)用copy方法即可讯赏。

__block變量存儲域與__forwarding指針

前面提到垮兑,__block對象會被定義為__Block_byref_val_0的結(jié)構(gòu)體實例,并成為__main_block_impl_0結(jié)構(gòu)體實例的一個成員變量漱挎,所以當(dāng)Block被從棧復(fù)制到堆上時系枪,__block變量也會受到影響∈队#總結(jié)如下表:

__block變量的配置存儲域 Block從棧復(fù)制到堆時的影響
從棧復(fù)制到堆并被Block持有
被Block持有

在一個Block中使用__block變量嗤无,當(dāng)該Block從棧復(fù)制到堆時震束,這些__block變量也全部從棧復(fù)制到堆怜庸。此時,Block持有__block變量垢村。

__forwarding指針

在__block的結(jié)構(gòu)體中割疾,有一個__forwarding成員變量,在轉(zhuǎn)換成C的源代碼中可以看到嘉栓,所有對__block變量的操作宏榕,均是通過__forwarding變量來操作,使用該變量侵佃,不管__block變量配置在棧上還是堆上麻昼,都能夠正確地訪問該變量。

最初建__block變量時:

  • __forwarding指針指向的是該結(jié)構(gòu)體實例自身馋辈。

當(dāng)Block從棧復(fù)制到堆上時抚芦, __block變量也從棧復(fù)制到堆上,此時:

  • 會將成員變量__forwarding的值替換為堆上的__block變量的結(jié)構(gòu)體實例的地址迈螟。
  • Block語法外的__block變量的__forwarding指針也會指向復(fù)制到堆中的__block變量的結(jié)構(gòu)體實例地址叉抡。

整個過程如下圖所示:

復(fù)制__block變量.png

通過該功能,無論是在Block語法中答毫、Block語法外使用__block變量褥民,還是__block變量配置在棧上或堆上,都可以順利地訪問同一個__block變量洗搂。

截獲對象

在Block語法中調(diào)用語法外的對象消返,如下代碼:

    blk_t blk;
    {
        id array = [[NSMutableArray alloc] init];
        blk = [^(id obj) {
            [array addObject:obj];
            NSLog(@"array count = %ld", [array count]);
        } copy];
    
    }
    
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);
    blk([[NSObject alloc] init]);

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

array count = 1
array count = 2
array count = 3

在執(zhí)行最后三行代碼的時候载弄,array已經(jīng)跑出了變量作用域,此時array對象被廢棄撵颊,但結(jié)果運行正常侦锯。

轉(zhuǎn)換后的源代碼如下,這里僅放了部分源代碼:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  id array;//強持有
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}

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

實際上在轉(zhuǎn)換后的源代碼的Block用結(jié)構(gòu)體中秦驯,聲明了一個id array成員對象尺碰,默認是__strong修飾,該成員對象以強持有的方式持有Block語法外的array變量译隘,這就保證了在array變量被廢棄時亲桥,Block的成員對象array仍然持有著NSMutableArray變量,所以代碼可以正常運行固耘。

copy和dispose

上面的源代碼中新增了兩個函數(shù)题篷,__main_block_copy_0和__main_block_dispose_0,runtime通過這兩個函數(shù)在運行過程中將Block從棧復(fù)制到堆上以及將堆上的Block廢棄厅目。

static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) 
{
    _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
    _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

這里邊的_Block_object_assign函數(shù)相當(dāng)于retain函數(shù)番枚,將對象賦值在對象類型的結(jié)構(gòu)體成員變量中。
_Block_object_dispose相當(dāng)于release函數(shù)损敷,釋放賦值在對象類型的結(jié)構(gòu)體成員變量中的對象葫笼。

copy和dispose函數(shù)的調(diào)用時機
函數(shù) 調(diào)用時機
copy函數(shù) 棧上的Block復(fù)制到堆時
dispose函數(shù) 堆上的Block被廢棄時
Block從棧復(fù)制到堆的時機
  1. 調(diào)用Block的copy實例方法時
  2. Block作為函數(shù)返回值返回時
  3. 將Block賦值給附有__strong修飾符id類型的類或Block類型成員變量時
  4. 在方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時

在ARC有效時,上述的2與3情況拗馒,編譯器會自動地將對象的Block作為參數(shù)并調(diào)用_Block_copy函數(shù)路星,這與調(diào)用Block的copy實例方法效果相同。在4的情況下诱桂,在調(diào)用的方法內(nèi)部會對傳遞來的Block調(diào)用Block的copy實例方法或者_Block_copy函數(shù)洋丐。

這里的第3條,在ARC有效的情況下挥等,如執(zhí)行這樣的語句

void (^blk)(void) = ^{
    ...
};
NSLog(@"%@", blk);

上面定義的blk對象友绝,實際上就是附有__strong修飾符的id類型,所以NSLog語句執(zhí)行的結(jié)果就是blk是一個NSConcreteMallocBLock肝劲。

BLOCK_FIELD_IS_OBJECT

在上面兩個函數(shù)中迁客,可以注意到一個參數(shù)BLOCK_FIELD_IS_OBJECT,在前面使用__block的例子中生成的源代碼里涡相,也出現(xiàn)了類似的參數(shù)BLOCK_FIELD_IS_BYREF哲泊。

  • BLOCK_FIELD_IS_OBJECT --> 對象
  • BLOCK_FIELD_IS_BYREF --> __block變量

編譯器通過該參數(shù)區(qū)分copy函數(shù)和dispose函數(shù)的對象類型是對象還是__block變量。

這里通過runtime的源代碼中可以查看到枚舉的全部定義

enum {
    // see function implementation for a more complete description of these fields and combinations
    BLOCK_FIELD_IS_OBJECT   =  3,  // id, NSObject, __attribute__((NSObject)), block, ...
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};

在ARC有效的情況下催蝗,編譯器也會有不自動調(diào)用copy的情況切威,除了以下幾種情況外,推薦手動調(diào)用copy實例方法丙号。

  • Block作為函數(shù)返回值返回時
  • 將Block賦值給類的附有__strong修飾符的id類型或Block類型成員變量時
  • 向方法名中含有usingBlock的Cocoa框架方法或Grand Central Dispatch的API中傳遞Block時

__block變量和對象

__block說明符可指定任何類型的自動變量先朦。如下例子所示:

__block id __strong obj = [[NSObject alloc] init];

轉(zhuǎn)換后的源代碼:

struct __Block_byref_obj_0 {
  void *__isa;
__Block_byref_obj_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
 id obj;
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    //dst的地址加40,包括4個指針(isa缰冤,__forwarding,兩個函數(shù)指針)32個字節(jié)喳魏,兩個int為8個字節(jié)棉浸,將指針移動到obj的起始地址
    //131是上面枚舉中BLOCK_FIELD_IS_OBJECT和BLOCK_BYREF_CALLER取或的結(jié)果
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

int main(int argc, const char * argv[]) {
    __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {
        (void*)0,
        (__Block_byref_obj_0 *)&obj, 
        33554432, 
        sizeof(__Block_byref_obj_0), 
        __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"))};
    return 0;
}

在__block變量為附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那樾蜗聲l(fā)生與在Block中使用附有__strong修飾符的id類型或?qū)ο箢愋妥詣幼兞康那闆r下相同的過程。當(dāng)__block變量從棧復(fù)制到堆時刺彩,使用_Block_object_assign函數(shù)迷郑,廢棄時使用_Block_object_dispose函數(shù)。

使用__weak說明符的__block變量

當(dāng)使用__weak說明符修飾的變量在作用域結(jié)束后创倔,即是Block代碼塊中使用了該變量嗡害,也會被釋放、廢棄畦攘,變量會變成nil霸妹。

__unsafe_retained說明符

__unsafe_retained說明符表明不歸編譯器管理對象,被它修飾的變量不會像__strong或__weak修飾符那樣處理知押,注意不要通過懸垂指針訪問已被廢棄的對象叹螟。

不能同時使用__autoreleasing和__block

Block循環(huán)引用

這個很容易理解,在類中的Block語法中直接調(diào)用self或者調(diào)用類的成員對象台盯,會導(dǎo)致Block強持有類罢绽,但同時類也強持有Block對象,導(dǎo)致循環(huán)引用爷恳。

解決方法1
在Block語法外聲明一個self的弱引用有缆,該方法只在ARC有效的情況下游泳

__weak typeof(self) weakSelf = self;

上述方法讓Block以weak的方式持有self象踊,這樣就不會引起循環(huán)引用温亲,導(dǎo)致內(nèi)存泄漏。
而這樣會引發(fā)另一個問題杯矩,Block中的代碼不是立即執(zhí)行栈虚,在執(zhí)行的時候可能該weak指針已經(jīng)被銷毀了,所以self會變成nil史隆,這樣需要在Block語法內(nèi)部再添加一局

__strong typeof(weakSelf) strongSelf = weakSelf;

在Block語法內(nèi)部使用strongSelf來獲取self的相關(guān)屬性和方法魂务,當(dāng)外部self被release后粘姜,strongSelf在block的語法局部還持有該self孤紧,當(dāng)Block語法執(zhí)行完畢后,strongSelf的生命周期結(jié)束被release拒秘,此時self的引用計數(shù)為0号显,對象被銷毀臭猜。

解決方法2
使用__block蔑歌,在成員方法內(nèi)

__block id tmp = self;
blk_ = ^ {
    NSLog(@"self = %@", tmp);
    tmp = nil;
};

但該方法有局限性,必須保證Block對象的代碼執(zhí)行一次。

使用__block變量的優(yōu)點如下:

  • 通過__block變量可控制對象的持有期間
  • 在不能使用__weak修飾符的環(huán)境中不使用__unsafe_unretained修飾符即可

copy/release

在ARC無效時,需要手動將Block從棧復(fù)制到堆上绊汹,同樣需要手動釋放Block。

推薦使用copy實例方法代替retain實例方法。
使用release方法釋放Block浑此。

同樣可以使用C語言的Block_copy函數(shù)和Block_release函數(shù),它與copy和release方法效果相同滞详。

ARC無效時的__block

在ARC無效時凛俱,__block說明符被用來避免Block中的循環(huán)引用。當(dāng)Block從棧復(fù)制到堆時料饥,若Block使用的變量為附有__block說明符的id類型或?qū)ο箢愋偷淖詣幼兞科讶粫籸etain(不會被retain則意味著不會導(dǎo)致循環(huán)引用),而若沒有被__block說明符修飾岸啡,則會被retain原叮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市巡蘸,隨后出現(xiàn)的幾起案子奋隶,更是在濱河造成了極大的恐慌,老刑警劉巖悦荒,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件唯欣,死亡現(xiàn)場離奇詭異,居然都是意外死亡搬味,警方通過查閱死者的電腦和手機境氢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碰纬,“玉大人萍聊,你說我怎么就攤上這事≡梦觯” “怎么了寿桨?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長她按。 經(jīng)常有香客問我牛隅,道長炕柔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任媒佣,我火速辦了婚禮匕累,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘默伍。我一直安慰自己欢嘿,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布也糊。 她就那樣靜靜地躺著炼蹦,像睡著了一般。 火紅的嫁衣襯著肌膚如雪狸剃。 梳的紋絲不亂的頭發(fā)上掐隐,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音钞馁,去河邊找鬼虑省。 笑死,一個胖子當(dāng)著我的面吹牛僧凰,可吹牛的內(nèi)容都是我干的探颈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼训措,長吁一口氣:“原來是場噩夢啊……” “哼伪节!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起绩鸣,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤怀大,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后全闷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叉寂,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年总珠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勘纯。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡局服,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驳遵,到底是詐尸還是另有隱情淫奔,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布堤结,位于F島的核電站唆迁,受9級特大地震影響鸭丛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唐责,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一鳞溉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鼠哥,春花似錦熟菲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至于颖,卻和暖如春呆贿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背森渐。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工榨崩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人章母。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓母蛛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親乳怎。 傳聞我的和親對象是個殘疾皇子彩郊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

推薦閱讀更多精彩內(nèi)容