iOS筆記之Block

一:Block是什么?

block是基于C語言的擴(kuò)展功能。block有一個比較常見的說法,叫做:帶有自動變量的匿名函數(shù),首先匿名函數(shù),即為沒有名字的函數(shù),我們嘗試用函數(shù)指針來實現(xiàn)一下:

int function(int a) {
  print(a);
}
int (* functionPointer) (int) = &function;
int result = (*function)(19);

注意看最后一行,我們通過函數(shù)指針去調(diào)用了函數(shù),并不知道函數(shù)名。接著還有關(guān)鍵字是帶有自動變量,這里的自動變量也就是局部變量,到這里,我們來看看block語法:

^ 返回值類型 參數(shù)列表 表達(dá)式

舉例來說:

int (^blk) (int) = ^ int (int a) { return a * a; };
blk(19);

通過對比賦值匿名函數(shù)和賦值Block類型變量可以發(fā)現(xiàn),兩者的寫法即為相似,區(qū)別在于block中為^符號,而函數(shù)為*符號。

二:Block本質(zhì)

通過源碼來究其本質(zhì)

int main(int argc, const char * argv[]) {

    void(^blk)(void) = ^ {
        printf("%d", 19);
    };
    blk();
    return 0;
}

我們使用clang -rewrite-objc指令來將以上代碼解析為C++源代碼:

struct __block_impl {
  void *isa;  //指向類的指針
  int Flags;
  int Reserved;
  void *FuncPtr;  //函數(shù)指針,將__main_block_func_0(一個靜態(tài)函數(shù))賦值給了它
};
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("%d", 19);
}

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

我們將main函數(shù)中類型轉(zhuǎn)換的操作去掉,簡化后的代碼如下:

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

進(jìn)一步分解為:

int main(int argc, const char * argv[]) {
    struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &_main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = temp;
}

小結(jié):

  • __block_impl:這是一個結(jié)構(gòu)體,也是C面向?qū)ο蟮捏w現(xiàn),可以理解為block的基類;
  • __main_block_impl_0: 可以理解為block變量;
  • __main_block_func_0: 可以理解為匿名函數(shù);
  • __main_block_desc_0:block的描述, Block_size;
  • 聲明block:創(chuàng)建了一個__main_block_imp_0類型的結(jié)構(gòu)體,并用一個該類型的指針指向這個結(jié)構(gòu)體
  • 使用block:調(diào)用了結(jié)構(gòu)體中的成員__block_impl的FuncPtr方法

從上面可以理解為,編譯之后的block是結(jié)構(gòu)體類型的,聲明的blk是一個指向結(jié)構(gòu)體類型block的指針。
Block本質(zhì)是指針結(jié)構(gòu)體

三:Block注意事項

1、截獲局部變量
int value = 10;
void (^blk) (void) = ^{ printf("%d", value); };
value = 19;
blk();  //輸出10

以上輸出應(yīng)該是10,而不是19,在表面上大家就可以理解為block截獲了變量的瞬間值

2、使用__block才能修改外部局部變量的值

int main(int argc, const char * argv[]) {
    __block int value = 3;
    void(^blk)(void) = ^ {
        value = 19;
    };
    blk();
    printf("%d", value);  //輸出19
        
    return 0;
}

3、全局變量的獲取與修改

獲取值:
// 聲明全局變量global
int global = 100;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^myBlock)(void) = ^{
            NSLog(@"global = %d", global);
        };
        global = 101;
        // 調(diào)用后控制臺輸出"global = 101"
        myBlock();
    }
    return 0;
}
修改值:
// 聲明全局變量global
int global = 100;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void(^myBlock)(void) = ^{
            global ++;
            NSLog(@"global = %d", global);
        };
        // 調(diào)用后控制臺輸出"global = 101"
        myBlock();
    }
    return 0;
}

4、Block訪問與修改靜態(tài)變量

// 聲明靜態(tài)變量global
static int global = 100;
void(^myBlock)() = ^{
    NSLog(@"global = %d", global);
};
global = 101;
// 調(diào)用后控制臺輸出"global = 101"
myBlock();
// 聲明靜態(tài)變量global
static int global = 100;
void(^myBlock)() = ^{
    global ++;
    NSLog(@"global = %d", global);
};
// 調(diào)用后控制臺輸出"global = 101"
myBlock();

5、循環(huán)引用

@implementation MyViewController {
   void (^_cycleReferenceBlock)(void);  //vc引用block
}

- (void)viewDidLoad {
    [super viewDidLoad];
    _cycleReferenceBlock = ^{ 
        NSLog(@"%@", self);   //block引用vc
    };
}

@end

解決辦法:在MRC下用_block,在ARC下使用__weak;

- (void)viewDidLoad {
    [super viewDidLoad];
    __weak typeof(self) weakSelf = self;
    _cycleReferenceBlock = ^{ 
        __strong __typeof(weakSelf)strongSelf = weakSelf;
        NSLog(@"%@", strongSelf);   //解決循環(huán)引用
    };
}

6、內(nèi)存管理

Block在MRC下的內(nèi)存管理
  • 默認(rèn)情況下,Block的內(nèi)存存儲在棧中,不需要開發(fā)人員對其進(jìn)行內(nèi)存管理
  • 在Block的內(nèi)存存儲在棧中時,如果在Block中引用了外面的對象,不會對所引用的對象進(jìn)行任何操作
  • 如果對Block進(jìn)行一次copy操作,那么Block的內(nèi)存會被移動到堆中,這時需要開發(fā)人員對其進(jìn)行release操作來管理內(nèi)存
  • 如果對Block進(jìn)行一次copy操作,那么Block的內(nèi)存會被移動到堆中,在Block的內(nèi)存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進(jìn)行一次retain操作,即使在Block自身調(diào)用了release操作之后,Block也不會對所引用的對象進(jìn)行一次release操作,這時會造成內(nèi)存泄漏
  • 如果對Block進(jìn)行一次copy操作,那么Block的內(nèi)存會被移動到堆中,在Block的內(nèi)存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進(jìn)行一次retain操作,為了不對所引用的對象進(jìn)行一次retain操作,可以在對象的前面使用下劃線下劃線block來修飾
  • 如果對象內(nèi)部有一個Block屬性,而在Block內(nèi)部又訪問了該對象,那么會造成循環(huán)引用
  • 如果對象內(nèi)部有一個Block屬性,而在Block內(nèi)部又訪問了該對象,那么會造成循環(huán)引用,解決循環(huán)引用的辦法是在對象的前面使用下劃線下劃線block來修飾,以避免Block對對象進(jìn)行retain操作
Block在ARC下的內(nèi)存管理
  • 在ARC默認(rèn)情況下,Block的內(nèi)存存儲在堆中,ARC會自動進(jìn)行內(nèi)存管理,程序員只需要避免循環(huán)引用即可
  • 在Block的內(nèi)存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進(jìn)行強(qiáng)引用,但是在Block被釋放時會自動去掉對該對象的強(qiáng)引用,所以不會造成內(nèi)存泄漏
  • 如果對象內(nèi)部有一個Block屬性,而在Block內(nèi)部又訪問了該對象,那么會造成循環(huán)引用
  • 如果對象內(nèi)部有一個Block屬性,而在Block內(nèi)部又訪問了該對象,那么會造成循環(huán)引用,解決循環(huán)引用的辦法是使用一個弱引用的指針指向該對象,然后在Block內(nèi)部使用該弱引用指針來進(jìn)行操作,這樣避免了Block對對象進(jìn)行強(qiáng)引用

查看示例

7、__block的作用

1)、__block在MRC下有兩個作用
    1. 允許在Block中訪問和修改局部變量
    1. 禁止Block對所引用的對象進(jìn)行隱式retain操作,ARC下的__weak功能
2)、__block在ARC下只有一個作用
    1. 允許在Block中訪問和修改局部變量
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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