一:Block是什么?
block是基于C語言的擴(kuò)展功能垛孔。block有一個比較常見的說法藕甩,叫做:帶有自動變量的匿名函數(shù),首先匿名函數(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注意事項(xiàng)
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下有兩個作用
- 允許在Block中訪問和修改局部變量
- 禁止Block對所引用的對象進(jìn)行隱式retain操作煮甥,ARC下的__weak功能
2)、__block在ARC下只有一個作用
- 允許在Block中訪問和修改局部變量