iOS中block的一些總結以及__block是怎么實現的

#Block

Block概述

Block它是C語言級別和運行時方面的一個特征。Block封裝了一段代碼邏輯,也用{}括起,和標準C語言中的函數/函數指針很相似,此外就是blokc能夠對定義環境中的變量可以引用到。這一點和其它各種語言中所說的“閉包”是非常類似的概念。在iOS中,block有很多應用場景,比如對代碼封裝作為參數傳遞。這在使用dispatch并發(Operation中也有BlockOperation)和completion異步回調等處都廣泛應用。

  • Block是蘋果官方特別推薦使用的數據類型,使用場景比較廣泛
  • 動畫
  • 多線程
  • 集合遍歷
  • 網絡請求回調
  • Block的作用
  • 用來保存某一段代碼,可以在恰當時候再去出來調用
  • 功能類似于函數和方法
  1. Block的基本使用
聲明:(返回類型)(^聲明的block名稱)(參數列表);

實現:^(參數列表){代碼塊}

返回值類型 (^block變量名)(參數列表) = ^(形參列表) {
};

賦值的例子:

int sum(int value1, int value2)
{
    return value1 + value2;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int (*sumP)(int, int);
        sumP = sum;
        NSLog(@"sum = %i",sumP(10,20));
        int (^sumBlock) (int value1, int value2) = ^(int value1,int value2) {
            return value2 + value1;
        };
        
        NSLog(@"sum----%i",sumBlock(10,30));
    }
    return 0;
}

其實Block和C語言中的指向函數的指針差不多
其實我們完全可以定義C語言的函數,如下所示

int sum(int value1, int value2)
{
    return value1 + value2;
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int (*sumP)(int, int);
        sumP = sum;
        NSLog(@"sum = %i",sumP(10,20));
        
    }
    return 0;
}

有時為了方便,用typedef給出定義,在蘋果的官方文檔里,建議出現多次的block使用typedef定義。

而block的調用,則十分簡單:block名稱(參數列表);

  1. 使用中的注意點
  • block中可以定義和外界同名的變量,并且如果在block中定義了和外界同名的變量,在block中訪問的是block內部的變量
  • 默認情況下,在block內部不能改變外面變量的值
  • 因為block中變量和外面的變量并不是同一個變量,可以通過打打印變量的地址看出來
  • 如果block中訪問到了外界的變量,block會將外界變量拷貝一份到堆內存中
  • 因為block中使用的外面變量的值是拷貝過來的即值拷貝,所以在調用之前修改外界變量的值,不會影響到block中拷貝的值
  • 如果想在block中修改外界變量的值,必須在外界變量前面加上__block
  • 如果在block中修改了外界變量的值,會影響到外界變量的值,

我們可以把.m反編譯成c++文件,看看其內部是怎么實現__block這個關鍵詞可以修改外部變量的

int sum(int value1, int value2)
{
    return value1 + value2;
}

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 int __main_block_func_0(struct __main_block_impl_0 *__cself, int value1, int value2) {

            return value2 + value1;
        }

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; 
        int (*sumP)(int, int);
        sumP = sum;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8b_skbhgyln78718lwjds4398k80000gn_T_main_185299_mi_0,sumP(10,20));
        int (*sumBlock) (int value1, int value2) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8b_skbhgyln78718lwjds4398k80000gn_T_main_185299_mi_1,((int (*)(__block_impl *, int, int))((__block_impl *)sumBlock)->FuncPtr)((__block_impl *)sumBlock, 10, 30));
    }

    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

加上__block

int sum(int value1, int value2)
{
    return value1 + value2;
}

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;
  }
};
static int __main_block_func_0(struct __main_block_impl_0 *__cself, int value1, int value2) {
  __Block_byref_a_0 *a = __cself->a; // bound by ref

            (a->__forwarding->a) = 20;
            return value2 + value1;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int (*sumP)(int, int);
        sumP = sum;
        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8b_skbhgyln78718lwjds4398k80000gn_T_main_9f1f9e_mi_0,sumP(10,20));
        __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
        int (*sumBlock) (int value1, int value2) = ((int (*)(int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_8b_skbhgyln78718lwjds4398k80000gn_T_main_9f1f9e_mi_1,((int (*)(__block_impl *, int, int))((__block_impl *)sumBlock)->FuncPtr)((__block_impl *)sumBlock, 10, 30));
    }

    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

很明顯加上了__block之后傳入的是&這樣的,是把地址傳到block內部去了,所以可以修改外部變量了

  • block是存儲在堆中還是棧中呢
  • 默認情況下block是存儲在棧中的,如果對block進行一個copy操作,block會轉移到堆中
  • 如果block在棧中 ,block中訪問了外界的對象,那么不會對對象進行retain操作
  • 但是如果block在堆中,block中訪問了外面的對象,那么會對外界的對象進行一次retain操作
  • 如果block中訪問了外界的對象,一定要給對象加上__block,只要加上__block,哪怕block在堆中,也不會對外界的對象進行retain
  • 想把block弄到堆中可以使用Clock_copy()

有如下一些注意點:

對定義環境的變量使用。默認是以const的方式使用,這有點像函數的const參數傳遞,如過需要block內修改可變,則使用__block,這樣做存儲就實現了共享,包括塊中的遞歸應用和定義環境上下文中的多個block使用。block通常定義在棧幀當中,而當所處的棧幀被銷毀的時候,block以及引用到的__block變量將會依然有效。

引用類型問題。block中的引用默認都是強引用,必要的時候需要使用__weak,同delegate使用的注意一樣,避免循環引用。此外,蘋果文檔中還給出了對instanceVariable和對localVariable引用不同的例子,注意體會下。

copy。在類屬性中,要使用copy。此外,對block進行copy要使用Block_copy()/Block_release()。

蘋果文檔中幾種需要避免使用的方式。《Blocks Programming Topics》中Using Blocks中的例子,實際上就是要注意block定義的位置與其上下文的關系。

有關Block的地址/引用。注意這篇文章中最后例子中的問題:http://www.cnblogs.com/kesalin/archive/2013/04/30/ios_block.html我的理解是Block的地址發生了變化,最終的問題是對Block引用地址釋放時的野指針錯誤。

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

推薦閱讀更多精彩內容