Block學(xué)習(xí)筆記

什么是Block

Block是對(duì)C語(yǔ)言的擴(kuò)展,可以簡(jiǎn)單總結(jié)為“帶有局部變量的匿名函數(shù)”,它類似于js中的閉包,是一種不需要定義名字、可以在使用的時(shí)候臨時(shí)定義、并且能夠訪問不是在Block內(nèi)部定義的全局/局部/靜態(tài)變量的"函數(shù)"。目前Block已經(jīng)廣泛應(yīng)用于iOS開發(fā)中,常用于GCD、動(dòng)畫及各類回調(diào)。

Block的聲明、賦值與調(diào)用

Block的聲明

// Block聲明的一般格式為:返回值類型(^Block名)(參數(shù)列表);
// eg: 聲明一個(gè)沒有返回值類型,有一個(gè)int類型參數(shù)叫做block_1的block

void (^block_1)(int a);

// 其中形參名字可以省略
void (^block_1)(int);

Block的賦值

// Block賦值的一般格式為 xxBlock = ^(參數(shù)列表){ Block體};

// eg: 給一個(gè)沒有返回值類型,有一個(gè)int類型參數(shù)叫做block_1的block賦值

block_1 = ^(int a) {
  NSLog(@"%d", a);
};

// 這里一般會(huì)將返回值類型省略,編譯器可以從block體中確定返回值類型

// 我們可以在聲明一個(gè)block的時(shí)候同時(shí)給它賦值

// eg: 聲明一個(gè)沒有返回值類型,有一個(gè)int類型參數(shù)叫做block_1的block并給它賦值

void (^block_1) (int a) = ^(int a) {
  NSLog(@"%d", a);
};

Block的調(diào)用

// Block調(diào)用的一般格式為 xxBlock(參數(shù)列表);

// eg: 調(diào)用一個(gè)沒有返回值類型,有一個(gè)int類型參數(shù)叫做block_1的block

block_1(20);

使用typedef定義Block類型

如果想要聲明多個(gè)具有相同返回值類型、相同參數(shù)列表的block,按照上面的聲明方式來(lái)做的話就要寫很多繁瑣的代碼,這時(shí)我們可以使用typdef來(lái)定義block類型。

typedef void(^commonBlock)(int a);

// 通過(guò)commonBlock這個(gè)別名來(lái)聲明一系列相似的block

commonBlock block_2;
commonBlock block_3;

// 相當(dāng)于
void(^block_2)(int a);
void(^block_3)(int a);

Block作為函數(shù)的參數(shù)

Block作為C函數(shù)的參數(shù)

// 1.定義一個(gè)形參為Block的C函數(shù)
void useBlockForC(int(^aBlock)(int, int))
{
    NSLog(@"result = %d", aBlock(10,10));
}

// 2.聲明并賦值定義一個(gè)Block變量
int(^addBlock)(int, int) = ^(int x, int y){
    return x + y;
};

// 3.以Block作為函數(shù)參數(shù),把Block像對(duì)象一樣傳遞
useBlockForC(addBlock);

// 集合一下2、3兩點(diǎn)
useBlockForC(^(int x, int y) {
    return x + y;
});

// 最終結(jié)果 打印輸出 result = 20

Block作為OC函數(shù)的參數(shù)

// 1.定義一個(gè)形參為Block的OC函數(shù)
- (void)useBlockForOC:(int(^aBlock)(int, int))aBlock
{
    NSLog(@"result = %d", aBlock(10,10));
}

// 2.聲明并賦值定義一個(gè)Block變量
int(^addBlock)(int, int) = ^(int x, int y){
    return x + y;
}; 

// 3.以Block作為函數(shù)參數(shù),把Block像對(duì)象一樣傳遞
[self useBlockForOC:addBlock];

// 集合一下2、3兩點(diǎn)
[self useBlockForOC:^(int x, int y){
    return x + y;
}];

// 最終結(jié)果 打印輸出 result = 20

Block內(nèi)訪問外部變量

Block內(nèi)訪問局部變量

int tempValue = 10;
void (^block_1) (void) = ^{
  NSLog(@"in block tempValue is %d", tempValue);
};
block_1();

// 打印輸出 in block tempValue is 10
        

原理解析

我們進(jìn)入到main.m所在文件的目錄,用Clang命令clang -rewrite-objc main.m可以將.m文件重新轉(zhuǎn)成.cpp文件,轉(zhuǎn)換后的main.cpp文件大概有近10W行樣子,將光標(biāo)移到最后往上找可以看到一個(gè)main函數(shù)

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int tempValue = 10;
        void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempValue));

        ((void (*)(__block_impl *))((__block_impl *)block_1)->FuncPtr)((__block_impl *)block_1);

    }
        return 0;
}

對(duì)照原來(lái)OC代碼

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int tempValue = 10;
        void (^block_1) (void) = ^{
            NSLog(@"in block tempValue is %d", tempValue);
        };
        
        block_1();

    }
        return 0;
}

我們可以看到block的定義轉(zhuǎn)換成C++代碼后變成了

void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, tempValue));

可以看到Block實(shí)際上就是一個(gè)指向結(jié)構(gòu)體__main_block_impl_0的指針,其中第三個(gè)元素是局部變量tempValue的值,在main.cpp文件中全局搜索__main_block_impl_0可以看到__main_block_impl_0結(jié)構(gòu)如下

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int tempValue;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _tempValue, int flags=0) : tempValue(_tempValue) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

調(diào)用block_1的代碼如下

((void (*)(__block_impl *))((__block_impl *)block_1)->FuncPtr)((__block_impl *)block_1);

可以看到block的調(diào)用實(shí)際上是指向結(jié)構(gòu)體的指針block_1訪問其FuncPtr元素,在定義block時(shí)為FuncPtr元素傳進(jìn)去的是__main_block_func_0方法,我們?cè)谒阉鬟@個(gè)方法

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int tempValue = __cself->tempValue; // bound by copy
  NSLog((NSString *)&__NSConstantStringImpl__var_folders_f0_sl9z3g2x12l7pns1wx_0yyx00000gn_T_main_191180_mi_0, tempValue);
}

可以看到在block定義的時(shí)候就將局部變量tempValue的值傳了進(jìn)去,所以在當(dāng)tempValue在調(diào)用block之前改變并不會(huì)影響到block內(nèi)部的值,并且在block內(nèi)部是無(wú)法對(duì)其進(jìn)行修改的。

總結(jié)

在Block定義時(shí)便將局部變量的值傳給Block所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)局部變量進(jìn)行修改并不會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是不可修改的

Block內(nèi)訪問__block修飾的局部變量

  • 在局部變量前使用__block修飾,在聲明Block之后、調(diào)用Block之前對(duì)局部變量進(jìn)行修改,此時(shí)在調(diào)用Block局部變量的值是修改后新的值
__block int tempValue = 10;
void (^block_1) (void) = ^{
  NSLog(@"in block tempValue is %d", tempValue);
};
block_1();

// 打印輸出 in block tempValue is 20
        

原理解析

同樣我們?cè)俅斡肅lang命令將main.m文件轉(zhuǎn)成main.cpp文件,找到main函數(shù)

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        __attribute__((__blocks__(byref))) __Block_byref_tempValue_0 tempValue = {(void*)0,(__Block_byref_tempValue_0 *)&tempValue, 0, sizeof(__Block_byref_tempValue_0), 10};
        void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_tempValue_0 *)&tempValue, 570425344));

        (tempValue.__forwarding->tempValue) = 20;
        ((void (*)(__block_impl *))((__block_impl *)block_1)->FuncPtr)((__block_impl *)block_1);

    }
        return 0;
}

我們發(fā)現(xiàn)用__block修飾的局部變量的定義變成了__attribute__((__blocks__(byref))) __Block_byref_tempValue_0 tempValue = {(void*)0,(__Block_byref_tempValue_0 *)&tempValue, 0, sizeof(__Block_byref_tempValue_0), 10};這一串代碼,全局搜索__Block_byref_tempValue_0可以看到是一個(gè)如下的結(jié)構(gòu)體

struct __Block_byref_tempValue_0 {
  void *__isa;
__Block_byref_tempValue_0 *__forwarding;
 int __flags;
 int __size;
 int tempValue;
};

再來(lái)看block的定義發(fā)現(xiàn),在block定義的時(shí)候我們傳入的不再是tempValue這個(gè)局部變量的值,而是一個(gè)指向tempValue的一個(gè)指針,所以在block內(nèi)部是可以修改這個(gè)局部變量的值并且當(dāng)block外部tempValue的值改變block內(nèi)部也會(huì)跟著改變。

總結(jié)

使用__block修飾局部變量,在Block定義時(shí)便將局部變量的指針傳給Block所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)局部變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)Block內(nèi)部的值也是可以修改的

Block內(nèi)訪問全局變量

  • 在block中可以訪問全局變量
int tempValue = 10;
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^block_1) (void) = ^{
            NSLog(@"in block tempValue is %d", tempValue);
        };
        
        tempValue = 20;
        block_1();

    }
        return 0;
}

// 打印輸出 in block tempValue is 20

原理解析

同樣我們?cè)俅斡肅lang命令將main.m文件轉(zhuǎn)成main.cpp文件,找到main函數(shù)

int tempValue = 10;

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        tempValue = 20;
        ((void (*)(__block_impl *))((__block_impl *)block_1)->FuncPtr)((__block_impl *)block_1);

    }
        return 0;
}

觀察上述代碼我們可以發(fā)現(xiàn)在block定義的時(shí)候并沒有將全局變量的值或則會(huì)指針傳入進(jìn)去我們?cè)賮?lái)觀察下__main_block_func_0方法

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f0_sl9z3g2x12l7pns1wx_0yyx00000gn_T_main_08df78_mi_0, tempValue);
        }

可以看到輸出打印的是全局變量tempValue的值。

總結(jié)

全局變量所占用的內(nèi)存只有一份,供所有函數(shù)共同調(diào)用,在Block定義時(shí)并未將全局變量的值或者指針傳給Block所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)局部變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是可以修改的

Block內(nèi)訪靜態(tài)變量

  • 在Block中可以訪問靜態(tài)變量
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        static int tempValue = 10;
        void (^block_1) (void) = ^{
            NSLog(@"in block tempValue is %d", tempValue);
        };
        
        tempValue = 20;
        block_1();

    }
        return 0;
}

// 打印輸出 in block tempValue is 20

原理解析

同樣我們?cè)俅斡肅lang命令將main.m文件轉(zhuǎn)成main.cpp文件,找到main函數(shù)

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        static int tempValue = 10;
        void (*block_1) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &tempValue));

        tempValue = 20;
        ((void (*)(__block_impl *))((__block_impl *)block_1)->FuncPtr)((__block_impl *)block_1);

    }
        return 0;
}

我們可以看到在block定義的時(shí)候傳入的是tempValue的地址,調(diào)用block實(shí)際上是指向結(jié)構(gòu)體的指針block_1訪問其FuncPtr元素,我們接著看__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *tempValue = __cself->tempValue; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_f0_sl9z3g2x12l7pns1wx_0yyx00000gn_T_main_6fa3db_mi_0, (*tempValue));
        }

可以看到NSLog的tempValue正是定義Block時(shí)為結(jié)構(gòu)體傳進(jìn)去的靜態(tài)變量tempValue的指針。

總結(jié)

在Block定義時(shí)便將靜態(tài)變量的指針傳給Block所指向的結(jié)構(gòu)體,因此在調(diào)用Block之前對(duì)靜態(tài)變量進(jìn)行修改會(huì)影響B(tài)lock內(nèi)部的值,同時(shí)內(nèi)部的值也是可以修改的

Block的內(nèi)存管理

  • 由于現(xiàn)在工程主要都是在ARC環(huán)境下,所以主要討論ARC環(huán)境下Block的內(nèi)存管理
  • 在ARC默認(rèn)情況下,Block的內(nèi)存存儲(chǔ)在堆中,ARC會(huì)自動(dòng)進(jìn)行內(nèi)存管理,程序員只需要避免循環(huán)引用即可
  • 當(dāng)Block變量出了作用域,Block的內(nèi)存會(huì)被自動(dòng)釋放
void(^block_1)() = ^{
    NSLog(@"block called");
};
block_1();

// 當(dāng)block_1執(zhí)行完成后系統(tǒng)會(huì)自動(dòng)釋放其內(nèi)存
  • 在Block的內(nèi)存存儲(chǔ)在堆中時(shí),如果在Block中引用了外面的對(duì)象,會(huì)對(duì)所引用的對(duì)象進(jìn)行強(qiáng)引用,但是在Block被釋放時(shí)會(huì)自動(dòng)去掉對(duì)該對(duì)象的強(qiáng)引用,所以不會(huì)造成內(nèi)存泄漏(循環(huán)引用)
Person *p = [[Person alloc] init];
        
void(^ block_1)() = ^{
    NSLog(@"%@", p);
};
block_1();
        
// Person對(duì)象在這里可以正常被釋放
  • 如果對(duì)象內(nèi)部有一個(gè)Block屬性,而在Block內(nèi)部又訪問了該對(duì)象,那么會(huì)造成循環(huán)引用
@interface Person: NSObject

@property (nonatomic, copy) void(^block_1)();

@end

@implementation Person

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

@end


Person *p = [[Person alloc] init];
        
p.block_1 = ^{
    NSLog(@"%@", p);
};
p.block_1();
        
// 因?yàn)閎lock_1作為Person的屬性,采用copy修飾符修飾(保證Block在堆里面,以免Block在棧中被系統(tǒng)釋放),所以Block會(huì)對(duì)Person對(duì)象進(jìn)行一次強(qiáng)引用,導(dǎo)致循環(huán)引用無(wú)法釋放

循環(huán)引用解決方案

Person *person = [[Person alloc] init];

// 這里還可以使用__weak typeof(Person) *weakPerson = person;
__weak Person *weakPerson = person;

person.block_1 =  ^{
    NSLog(@"%@", weakPerson);
};

person.block_1();

這樣Person對(duì)象就可以正常被釋放

?著作權(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)容

  • Block基礎(chǔ)回顧 1.什么是Block? 帶有局部變量的匿名函數(shù)(名字不重要,知道怎么用就行),差不多就與C語(yǔ)言...
    Bugfix閱讀 6,904評(píng)論 5 61
  • 前言 Blocks是C語(yǔ)言的擴(kuò)充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了這...
    小人不才閱讀 3,852評(píng)論 0 23
  • block.png iOS代碼塊Block 概述 代碼塊Block是蘋果在iOS4開始引入的對(duì)C語(yǔ)言的擴(kuò)展,用來(lái)實(shí)...
    全棧農(nóng)民工閱讀 638評(píng)論 0 1
  • 米大王是我對(duì)媽媽的特殊昵稱 在我家,爸爸叫大豆(因?yàn)閐ad是D開頭),媽媽叫大米(因?yàn)閙um是M開頭) 而我自己是...
    毛嘟子閱讀 705評(píng)論 0 2
  • 爸,你知道嗎?我是多么的想你現(xiàn)在是個(gè)健健康康的人,能快快樂樂的活著。我知道你對(duì)這個(gè)世界還很眷戀?,F(xiàn)在是該輪到你享福...
    笨笨的成長(zhǎng)之路閱讀 376評(píng)論 4 1

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