一:Block是什么?
block是基于C語言的擴展功能。block有一個比較常見的說法,叫做:帶有自動變量的匿名函數,首先匿名函數,即為沒有名字的函數,我們嘗試用函數指針來實現一下:
int function(int a) {
print(a);
}
int (* functionPointer) (int) = &function;
int result = (*function)(19);
注意看最后一行,我們通過函數指針去調用了函數,并不知道函數名。接著還有關鍵字是帶有自動變量,這里的自動變量也就是局部變量,到這里,我們來看看block語法:
^ 返回值類型 參數列表 表達式
舉例來說:
int (^blk) (int) = ^ int (int a) { return a * a; };
blk(19);
通過對比賦值匿名函數和賦值Block類型變量可以發現,兩者的寫法即為相似,區別在于block中為^符號,而函數為*符號。
二:Block本質
通過源碼來究其本質
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; //函數指針,將__main_block_func_0(一個靜態函數)賦值給了它
};
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函數中類型轉換的操作去掉,簡化后的代碼如下:
int main(int argc, const char * argv[]) {
void(*blk)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
}
進一步分解為:
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;
}
小結:
- __block_impl:這是一個結構體,也是C面向對象的體現,可以理解為block的基類;
- __main_block_impl_0: 可以理解為block變量;
- __main_block_func_0: 可以理解為匿名函數;
- __main_block_desc_0:block的描述, Block_size;
- 聲明block:創建了一個__main_block_imp_0類型的結構體,并用一個該類型的指針指向這個結構體
- 使用block:調用了結構體中的成員__block_impl的FuncPtr方法
從上面可以理解為,編譯之后的block是結構體類型的,聲明的blk是一個指向結構體類型block的指針。
Block本質是指針結構體。
三:Block注意事項
1、截獲局部變量
int value = 10;
void (^blk) (void) = ^{ printf("%d", value); };
value = 19;
blk(); //輸出10
以上輸出應該是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;
// 調用后控制臺輸出"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);
};
// 調用后控制臺輸出"global = 101"
myBlock();
}
return 0;
}
4、Block訪問與修改靜態變量
// 聲明靜態變量global
static int global = 100;
void(^myBlock)() = ^{
NSLog(@"global = %d", global);
};
global = 101;
// 調用后控制臺輸出"global = 101"
myBlock();
// 聲明靜態變量global
static int global = 100;
void(^myBlock)() = ^{
global ++;
NSLog(@"global = %d", global);
};
// 調用后控制臺輸出"global = 101"
myBlock();
5、循環引用
@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); //解決循環引用
};
}
6、內存管理
Block在MRC下的內存管理
- 默認情況下,Block的內存存儲在棧中,不需要開發人員對其進行內存管理
- 在Block的內存存儲在棧中時,如果在Block中引用了外面的對象,不會對所引用的對象進行任何操作
- 如果對Block進行一次copy操作,那么Block的內存會被移動到堆中,這時需要開發人員對其進行release操作來管理內存
- 如果對Block進行一次copy操作,那么Block的內存會被移動到堆中,在Block的內存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進行一次retain操作,即使在Block自身調用了release操作之后,Block也不會對所引用的對象進行一次release操作,這時會造成內存泄漏
- 如果對Block進行一次copy操作,那么Block的內存會被移動到堆中,在Block的內存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進行一次retain操作,為了不對所引用的對象進行一次retain操作,可以在對象的前面使用下劃線下劃線block來修飾
- 如果對象內部有一個Block屬性,而在Block內部又訪問了該對象,那么會造成循環引用
- 如果對象內部有一個Block屬性,而在Block內部又訪問了該對象,那么會造成循環引用,解決循環引用的辦法是在對象的前面使用下劃線下劃線block來修飾,以避免Block對對象進行retain操作
Block在ARC下的內存管理
- 在ARC默認情況下,Block的內存存儲在堆中,ARC會自動進行內存管理,程序員只需要避免循環引用即可
- 在Block的內存存儲在堆中時,如果在Block中引用了外面的對象,會對所引用的對象進行強引用,但是在Block被釋放時會自動去掉對該對象的強引用,所以不會造成內存泄漏
- 如果對象內部有一個Block屬性,而在Block內部又訪問了該對象,那么會造成循環引用
- 如果對象內部有一個Block屬性,而在Block內部又訪問了該對象,那么會造成循環引用,解決循環引用的辦法是使用一個弱引用的指針指向該對象,然后在Block內部使用該弱引用指針來進行操作,這樣避免了Block對對象進行強引用
7、__block的作用
1)、__block在MRC下有兩個作用
- 允許在Block中訪問和修改局部變量
- 禁止Block對所引用的對象進行隱式retain操作,ARC下的__weak功能
2)、__block在ARC下只有一個作用
- 允許在Block中訪問和修改局部變量