iOS-Block的實(shí)現(xiàn)

Block是C語言的擴(kuò)充功能,是帶有自動變量的匿名函數(shù)。block 將同一邏輯的代碼放在一個塊,使代碼更簡潔緊湊,易于閱讀,比函數(shù)使用更方便,代碼更美觀,開發(fā)中受到廣泛的使用。

block 的底層實(shí)現(xiàn)

將main.m中的代碼通過clang編譯成main.cpp代碼:

int main(int argc, const char * argv[]) {
    // insert code here...
    void (^blk)(void) = ^{ printf("FlyElephant---Block\n"); };
    blk();
    return 0;
}

main.cpp代碼:

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("FlyElephant---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(int argc, const char * argv[]) {

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

block通過__main_block_impl_0初始化,后序工作通過 __block_impl ,__main_block_desc_0實(shí)現(xiàn)。

__block_impl

__block_impl結(jié)構(gòu)體代碼如下:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • isa 指向?qū)嵗龑ο螅琤lock 本身也是一個 Objective-C 對象。block 的三種類型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,即當(dāng)代碼執(zhí)行時,isa 有三種取值 :
impl.isa = &_NSConcreteStackBlock; 
impl.isa = &_NSConcreteMallocBlock; 
impl.isa = &_NSConcreteGlobalBlock;
  • Flags 按位承載 block 的附加信息;
  • Reserved 保留變量;
  • FuncPtr 函數(shù)指針,指向 Block 要執(zhí)行的函數(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;
  }

其中FuncPtr指針指向的block函數(shù):

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

__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;
  }
};
  • impl block 實(shí)現(xiàn)的結(jié)構(gòu)體變量;
  • Desc 描述 block 的結(jié)構(gòu)體變量;
  • __main_block_impl_0 結(jié)構(gòu)體的構(gòu)造函數(shù),初始化結(jié)構(gòu)體變量 impl、Desc;

__main_block_desc_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)};
  • reserved 結(jié)構(gòu)體信息保留字段
  • Block_size 結(jié)構(gòu)體大小

整個實(shí)現(xiàn)過程就是初始化__main_block_impl_0,返回impl,執(zhí)行impl->FuncPtr.

block 捕獲外部變量

int main(int argc, const char * argv[]) {
    // insert code here...
    int localValue = 1;
    void (^blk)(void) = ^{ printf("FlyElephant = %d\n", localValue); };
    blk();
    return 0;
}

block捕獲外部變量編譯之后的cpp代碼如下:

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int localValue;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _localValue, int flags=0) : localValue(_localValue) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int localValue = __cself->localValue; // bound by copy
 printf("FlyElephant = %d\n", localValue); }

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[]) {

    int localValue = 1;
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, localValue));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

__block_impl定義不變:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

block通過參數(shù)傳遞獲取到了localValue,保存到結(jié)構(gòu)體中的同名變量中。賦值的時候通過__cself來賦值:

 int localValue = __cself->localValue; // bound by copy

不過暫時還不能還不能修改值,如果修改localValue會報錯:

Variable is not assignable (missing __block type specifier)

內(nèi)存區(qū)域

討論block的存儲區(qū)域我們先了解一下C/C++編譯的程序在內(nèi)存中的分布情況 :

1.棧區(qū)(stack)— 程序運(yùn)行時由編譯器自動分配,存放函數(shù)的參數(shù)值,局部變量的值等。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。程序結(jié)束時由編譯器自動釋放。

2.堆區(qū)(heap) — 在內(nèi)存開辟另一塊存儲區(qū)域。一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是不一樣,分配方式類似于鏈表。用malloc, calloc, realloc等分配內(nèi)存的函數(shù)分配得到的就是在堆上。

3.全局區(qū)(靜態(tài)區(qū))(static)—編譯器編譯時即分配內(nèi)存。全局變量和靜態(tài)變量的存儲是放在一塊的。對于C語言初始化的全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。而C++則沒有這個區(qū)別 - 程序結(jié)束后由系統(tǒng)釋放。

4.文字常量區(qū) —常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放

5.程序代碼區(qū)—存放函數(shù)體的二進(jìn)制代碼。

block訪問變量有兩種方式一種是靜態(tài)變量,全局變量和__block形式。

靜態(tài)變量,全局變量

block使用靜態(tài)變量,全局變量,全局靜態(tài)變量代碼:

int global_val = 1;
static int static_global_val = 2;
int main(int argc, const char * argv[]) {
    // insert code here...
    static int static_val = 3;
    void(^blk)(void) = ^ {
        global_val = 2;
        static_global_val = 3;
        static_val = 4;
    };
    return 0;
}

編譯之后的代碼:

int global_val = 1;
static int static_global_val = 2;

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_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 *static_val = __cself->static_val; // bound by copy

        global_val = 2;
        static_global_val = 3;
        (*static_val) = 4;
    }

對靜態(tài)全局變量和全局變量訪問和轉(zhuǎn)換之前一樣,對靜態(tài)變量的方式是將指針保存了起來。block對于其自動變量而言沒有將指針保存起來,是因?yàn)樽詣幼兞?局部變量)超出其作用域之后就會被廢棄。

__block

通過__block看下代碼:

    __block int localValue = 0;
    void (^blk)(void) = ^{
        localValue = 1;
    };

編譯之后代碼:

struct __Block_byref_localValue_0 {
  void *__isa;
__Block_byref_localValue_0 *__forwarding;
 int __flags;
 int __size;
 int localValue;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_localValue_0 *localValue; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_localValue_0 *_localValue, int flags=0) : localValue(_localValue->__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_localValue_0 *localValue = __cself->localValue; // bound by ref

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

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->localValue, 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[]) {

    __attribute__((__blocks__(byref))) __Block_byref_localValue_0 localValue = {(void*)0,(__Block_byref_localValue_0 *)&localValue, 0, sizeof(__Block_byref_localValue_0), 0};
    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_localValue_0 *)&localValue, 570425344));
    return 0;
}

相對之前的代碼多了不少代碼,主要新增代碼:

  • __Block_byref_localValue_0 結(jié)構(gòu)體:用于封裝 __block 修飾的外部變量。
  • _Block_object_assign 函數(shù):當(dāng) block 從棧拷貝到堆時,調(diào)用此函數(shù)。
  • _Block_object_dispose 函數(shù):當(dāng) block 從堆內(nèi)存釋放時,調(diào)用此函數(shù)。

OC源碼中的 __block localValue 翻譯后變成了 __Block_byref_intValue_0 結(jié)構(gòu)體指針變量 intValue,通過指針傳遞到 block 內(nèi),與靜態(tài)變量的指針傳遞是一致的。__Block_byref_intValue_0 這個結(jié)構(gòu)體的字段需要注意:

struct __Block_byref_localValue_0 {
  void *__isa;
__Block_byref_localValue_0 *__forwarding;
 int __flags;
 int __size;
 int localValue;
};
__block變量結(jié)構(gòu)體.png

結(jié)構(gòu)體里面還多了個 __forwarding 指向自己的指針變量,這與block的類型有關(guān)系。

block類型

block 有三種類型 NSConcreteGlobalBlock,NSConcreteStackBlock和NSConcreteMallocBlock。

NSConcreteGlobalBlock

_NSConcreteGlobalBlock 類型的 block 處于內(nèi)存的 ROData 段,不捕獲局部變量,運(yùn)行不依賴上下文,內(nèi)存管理比較簡單。

*block 字面量寫在全局作用域時,編譯之后也是_NSConcreteGlobalBlock類型。

void (^blk)(void) = ^{ printf("Global Block\n"); };
int main(int argc, const char * argv[]) {
    blk();
    return 0;
}
struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
 printf("Global Block\n"); }

NSConcreteStackBlock

NSConcreteStackBlock 類型的 block 處于內(nèi)存的棧區(qū)。global block 由于處在 data 段,可以通過指針安全訪問,但 stack block 處在內(nèi)存棧區(qū),如果其變量作用域結(jié)束,這個 block 就被廢棄,block 上的 __block 變量也同樣會被廢棄。

棧上的Block與__block變量.png

為了解決這個問題,block 提供了 copy 的功能,將 block 和 __block 變量從棧拷貝到堆,也就是 _NSConcreteMallocBlock。

_NSConcreteMallocBlock

當(dāng) block 從棧拷貝到堆后,當(dāng)棧上變量作用域結(jié)束時,仍然可以繼續(xù)使用 block,在開啟 ARC 時,大部分情況下編譯器通常會將創(chuàng)建在棧上的 block 自動拷貝到堆上,例如:

  • block 作為函數(shù)返回值返回時,編譯器自動將 block 作為 _Block_copy 函數(shù),效果等同于 block 直接調(diào)用 copy 方法;
  • block 被賦值給 __strong id 類型的對象或 block 的成員變量時,編譯器自動將 block 作為 _Block_copy 函數(shù),效果等同于 block 直接調(diào)用 copy 方法;
  • block 作為參數(shù)被傳入方法名帶有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 時。這些方法會在內(nèi)部對傳遞進(jìn)來的 block 調(diào)用 copy 或 _Block_copy 進(jìn)行拷貝;

編譯器不會自動調(diào)用 copy 方法:

  • block 作為方法或函數(shù)的參數(shù)傳遞時;
typedef int (^blk_t)(int);
blk_t func(int rate)
{
    return ^(int count){return rate * count;};
}

上面的 block 獲取了外部變量,所以是創(chuàng)建在棧上,當(dāng) func 函數(shù)返回給調(diào)用者時,脫離了局部變量 rate 的作用范圍,如果調(diào)用者使用這個 block 就會出問題。那 ARC 開啟的情況呢?運(yùn)行這個 block 一切正常,編譯器編譯之后的代碼:

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_autoreleaseReturnValue(tmp); 
}

objc_retainBlock本質(zhì)上調(diào)用的是_Block_copy函數(shù):

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

objc_autoreleaseReturnValue 本質(zhì)上調(diào)用的是objc_autorelease函數(shù):

id 
objc_autoreleaseReturnValue(id obj)
{
    if (prepareOptimizedReturn(ReturnAtPlus1)) return obj;

    return objc_autorelease(obj);
}

block類型拷貝:

Block類型 源拷貝區(qū)域 Copy結(jié)果
_NSConcreteStackBlock 從棧復(fù)制到堆
_NSConcreteGlobalBlock 程序的數(shù)據(jù)區(qū)域 什么都不做
_NSConcreteMallocBlock 引用計數(shù)加1

block 內(nèi)存管理

當(dāng) block 從棧內(nèi)存被拷貝到堆內(nèi)存時,__block 變量的變化如下圖。需要說明的是,當(dāng)棧上的 block 被拷貝到堆上,堆上的 block 再次被拷貝時,對 __block 變量已經(jīng)沒有影響了。

block拷貝.png
多個Block使用__block變量.png
block廢棄與變量釋放.png

__forwarding

block 從棧被拷貝到堆時,__forwarding 指針變量也會指向堆區(qū)的結(jié)構(gòu)體。

block 獲取局部變量,當(dāng)要在其他地方(超出局部變量作用范圍)使用這個 block 的時候,由于訪問局部變量異常,導(dǎo)致程序崩潰,因此需要將block從棧區(qū)拷貝到堆區(qū)。

將 block 拷貝到堆上的同時,將 __forwarding 指針指向堆上結(jié)構(gòu)體。后面如果要想使用 __block 變量,只要通過 __forwarding 訪問堆上變量,就不會出現(xiàn)程序崩潰了。

簡單講就是“不管__block變量配置在棧上還是堆上,都能正確的訪問該變量。”

    __block int val = 0;
    void (^blk)(void) = [^{++val;} copy];
    ++val;
    blk();
    NSLog(@"%d", val);

??代碼中 ^{++val;} 和 ++val; 都會被轉(zhuǎn)換成 ++(val.__forwarding->val);,堆上的 val 被加了兩次,最后打印堆上的 val 為 2。

block 循環(huán)引用

如果在Block使用附有__strong修飾符的對象類型自動變量,那么當(dāng)Block從棧復(fù)制到堆上時,該對象為Block所持有。

經(jīng)典的循環(huán)引用是self與block之間的相互引用:

typedef void (^blk_t)(void);

@interface User()
{
    blk_t blk_;
}
@end

@implementation User

- (id)init
{
    self = [super init];
    blk_ = ^{NSLog(@"self = %@", self);};
    return self;
}

- (void)dealloc
{
    NSLog(@"dealloc");
}

@end

編譯器會提示警告:

Capturing 'self' strongly in this block is likely to lead to a retain cycle

如果block中使用self中的屬性或者成員變量:

@interface User()
{
    blk_t blk_;
}

@property (assign, nonatomic) NSInteger age;

@end

@implementation User

- (id)init
{
    self = [super init];
    blk_ = ^{
        NSLog(@"FlyElephant--%ld", (long)self.age);
    };
    return self;
}

- (void)dealloc
{
    NSLog(@"dealloc");
}

@end

編譯會警告提示循環(huán)引用:

Capturing 'self' strongly in this block is likely to lead to a retain cycle

__weak修飾符解決循環(huán)引用問題:

    __weak typeof(User) *weakself = self;
    blk_ = ^{
        NSLog(@"FlyElephant--%ld", (long)weakself.age);
    };
    return self;

__block避免循環(huán)引用:

typedef void (^blk_t)(void);
@interface User : NSObject
{
    blk_t blk_;
}
@end

@implementation User

- (id)init
{
    self = [super init];
    __block id tmp = self;
    blk_ = ^{
        NSLog(@"self = %@", tmp);
        tmp = nil;
    };
    return self;
}

- (void)execBlock
{
    blk_();
}

- (void)dealloc
{
    NSLog(@"dealloc");
}

@end

int main()
{
    id object = [[User alloc] init];
    [object execBlock];
    return 0;
}

該代碼沒有引起循環(huán)引用。但是如果不執(zhí)行execBlock實(shí)例方式,即不執(zhí)行復(fù)試給成員變量blk_的Block,會循環(huán)引用并引起內(nèi)存泄漏。

參考鏈接

http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-1/

http://www.galloway.me.uk/2012/10/a-look-inside-blocks-episode-2/

https://www.zybuluo.com/MicroCai/note/51116

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 《Objective-C高級編程》這本書就講了三個東西:自動引用計數(shù)、block、GCD,偏向于從原理上對這些內(nèi)容...
    WeiHing閱讀 9,889評論 10 69
  • Block概要 Block:帶有自動變量的匿名函數(shù)。 匿名函數(shù):沒有函數(shù)名的函數(shù),一對{}包裹的內(nèi)容是匿名函數(shù)的作...
    zweic閱讀 511評論 0 2
  • Block基礎(chǔ)回顧 1.什么是Block? 帶有局部變量的匿名函數(shù)(名字不重要,知道怎么用就行),差不多就與C語言...
    Bugfix閱讀 6,793評論 5 61
  • 深冬的傍晚時分,蒙蒙細(xì)雨籠罩著整座城市。一位上了年紀(jì)的老婦人獨(dú)自走進(jìn)一家電影院,在第五排正當(dāng)中的位置坐了下來。她衣...
    晴空月閱讀 544評論 9 2
  • 誰的低語 行走在大山的胸膛上 精讀 嶙峋山石 一頁頁風(fēng)干的 歷史 山...
    望斷天涯路_poet閱讀 279評論 0 4