前言:Block 是開發(fā)過程中常用便捷的回調(diào)方式,本文簡單介紹 Block
一、Block 簡介
Block 對象是 C 級別的語法和運行時特性,和標準的 C 函數(shù)類似,除了可執(zhí)行代碼外,還可能包含變量自動綁定(棧)和內(nèi)存托管(堆)。一個 Block 維護一個狀態(tài)集。
閉包 = 一個函數(shù)「或指向函數(shù)的指針」+ 該函數(shù)執(zhí)行的外部的上下文變量「也就是自由變量」;Block 是 Objective-C 對于閉包的實現(xiàn)。
- 可以嵌套定義,定義 Block 方法和定義函數(shù)方法相似
- Block 可以定義在方法內(nèi)部或外部
- 只有調(diào)用 Block 時候,才會執(zhí)行其{}體內(nèi)的代碼
- 本質(zhì)是對象,使代碼高聚合
使用 clang 將 OC 代碼轉(zhuǎn)換為 C++ 文件查看 block 的方法:
- 在命令行輸入代碼 clang -rewrite-objc 需要編譯的OC文件.m
- 這時查看當前的文件夾里 多了一個相同的名稱的 .cpp 文件,在命令行輸入 open main.cpp 查看文件
1.1 定義和使用
1.無參數(shù)無返回值
void (^ MyBlockOne)(void) = ^(void){
NSLog(@"無參數(shù),無返回值");
};
MyBlockOne();//block的調(diào)用
2.有參數(shù)無返回值
void(^MyblockTwo)(int a) = ^(int a){
NSLog(@"@ = %d我就是block,有參數(shù),無返回值",a);
};
MyblockTwo(100);
3.有參數(shù)有返回值
int(^MyBlockThree)(int,int) = ^(int a,int b){
NSLog(@"%d我就是block,有參數(shù),有返回值",a + b);
return a + b;
};
MyBlockThree(12,56);
4.無參數(shù)有返回值
int(^MyblockFour)(void) = ^{
NSLog(@"無參數(shù),有返回值");
return45;
};
MyblockFour();
5.定義聲明
聲明
typedef void (^Block)();
typedef int (^MyBlock)(int , int);
typedef void(^ConfirmBlock)(BOOL isOK);
typedef void(^AlertBlock)(NSInteger alertTag);
定義屬性
@property (nonatomic,copy) MyBlock myBlockOne;
使用
self.myBlockOne = ^int (int ,int){
//TODO
}
1.2 Block與外界變量
1、截獲自動變量(局部變量)值
(1)默認情況
對于 block 外的變量引用,block 默認是將其復制到其數(shù)據(jù)結構中來實現(xiàn)訪問的。也就是說block的自動變量截獲只針對block內(nèi)部使用的自動變量, 不使用則不截獲, 因為截獲的自動變量會存儲于block的結構體內(nèi)部, 會導致block體積變大。特別要注意的是默認情況下block只能訪問不能修改局部變量的值。
int age = 10;
myBlock block = ^{
NSLog(@"age = %d", age);
};
age = 18;
block();
輸出結果:
age = 10
(2) __block 修飾的外部變量
對于用 __block 修飾的外部變量引用,block 是復制其引用地址來實現(xiàn)訪問的。block可以修改__block 修飾的外部變量的值。
__block int age = 10;
myBlock block = ^{
NSLog(@"age = %d", age);
};
age = 18;
block();
輸出為:
age = 18
2、__block 修飾的外部變量的值就可以被block修改
我們使用 clang 將 OC 代碼轉(zhuǎn)換為 C++ 文件:
clang -rewrite-objc 源代碼文件名
__block int val = 10;
轉(zhuǎn)換成
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};
會發(fā)現(xiàn)一個局部變量加上__block修飾符后竟然跟block一樣變成了一個__Block_byref_val_0
結構體類型的自動變量實例。
此時我們在block內(nèi)部訪問val變量則需要通過一個叫__forwarding
的成員變量來間接訪問val變量。
1.3 Block 存儲
1、Block的存儲域及copy操作
由C/C++/OBJC編譯的程序占用內(nèi)存分布的結構:
block有三種類型:
- 全局塊(_NSConcreteGlobalBlock)
- 棧塊(_NSConcreteStackBlock)
- 堆塊(_NSConcreteMallocBlock)
三種block各自的存儲域:
- 全局塊存在于全局內(nèi)存中, 相當于單例.
- 棧塊存在于棧內(nèi)存中, 超出其作用域則馬上被銷毀
- 堆塊存在于堆內(nèi)存中, 是一個帶引用計數(shù)的對象, 需要自行管理其內(nèi)存
簡而言之,存儲在棧中的Block就是棧塊、存儲在堆中的就是堆塊、既不在棧中也不在堆中的塊就是全局塊。
1.4 判斷Block的存儲位置
(1)Block不訪問外界變量(包括棧中和堆中的變量)
Block 既不在棧又不在堆中,在代碼段中,ARC和MRC下都是如此。此時為全局塊。
(2)Block訪問外界變量
MRC 環(huán)境下:訪問外界變量的 Block 默認存儲棧中。
ARC 環(huán)境下:訪問外界變量的 Block 默認存儲在堆中(實際是放在棧區(qū),然后ARC情況下自動又拷貝到堆區(qū)),自動釋放。
ARC下,訪問外界變量的 Block為什么要自動從棧區(qū)拷貝到堆區(qū)呢?
棧上的Block,如果其所屬的變量作用域結束,該Block就被廢棄,如同一般的自動變量。當然,Block中的__block變量也同時被廢棄。
為了解決棧塊在其變量作用域結束之后被廢棄(釋放)的問題,我們需要把Block復制到堆中,延長其生命周期。開啟ARC時,大多數(shù)情況下編譯器會恰當?shù)剡M行判斷是否有需要將Block從棧復制到堆,如果有,自動生成將Block從棧上復制到堆上的代碼。Block的復制操作執(zhí)行的是copy實例方法。Block只要調(diào)用了copy方法,棧塊就會變成堆塊。
- 在ARC的Block是配置在棧上的,所以返回函數(shù)調(diào)用方時,Block變量作用域就結束了,Block會被廢棄。種情況編譯器會自動完成復制。
- 在非ARC情況下則需要開發(fā)者調(diào)用copy方法手動復制。
- 將Block從棧上復制到堆上相當消耗CPU,所以當Block設置在棧上也能夠使用時,就不要復制了,因為此時的復制只是在浪費CPU資源。
Block的復制操作執(zhí)行的是copy實例方法。不同類型的Block使用copy方法的效果如下表:
根據(jù)表得知,Block在堆中copy會造成引用計數(shù)增加,這與其他Objective-C對象是一樣的。雖然Block在棧中也是以對象的身份存在,但是棧塊沒有引用計數(shù),因為不需要,我們都知道棧區(qū)的內(nèi)存由編譯器自動分配釋放。
不管Block存儲域在何處,用copy方法復制都不會引起任何問題。在不確定時調(diào)用copy方法即可。在ARC有效時,多次調(diào)用copy方法完全沒有問題:
blk = [[[[blk copy] copy] copy] copy];
// 經(jīng)過多次復制,變量blk仍然持有Block的強引用,該Block不會被廢棄。
1.5 __block變量與__forwarding
在copy操作之后,既然__block變量也被copy到堆上去了, 那么訪問該變量是訪問棧上的還是堆上的呢?__forwarding 終于要閃亮登場了。通過__forwarding, 無論是在block中還是 block外訪問__block變量, 也不管該變量在棧上或堆上, 都能順利地訪問同一個__block變量。
1.6 Block 循環(huán)引用
Block 循環(huán)引用的情況:
某個類將 block 作為自己的屬性變量,然后該類在 block 的方法體里面又使用了該類本身。
self.someBlock = ^(Type var){
[self dosomething];
};
解決辦法:
(1)ARC 下:使用 __weak
__weak typeof(self) weakSelf = self;
self.someBlock = ^(Type var){
[weakSelf dosomething];
};
(2)MRC 下:使用 __block
__block typeof(self) blockSelf = self;
self.someBlock = ^(Type var){
[blockSelf dosomething];
};
解決辦法:
//1.使用__weak ClassName
__block XXViewController* weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf);
};
//2.使用__weak typeof(self)
__weak typeof(self) weakSelf = self;
self.blk = ^{
NSLog(@"In Block : %@",weakSelf);
};
//3.Reactive Cocoa中的@weakify和@strongify
@weakify(self);
self.blk = ^{
@strongify(self);
NSLog(@"In Block : %@",self);
};
二、Block 應用
2.1 Block 應用
1、Block作為變量(Xcode快捷鍵:inlineBlock)
int (^sum) (int, int); // 定義一個 Block 變量 sum
// 給 Block 變量賦值
// 一般 返回值省略:sum = ^(int a,int b)…
sum = ^int (int a,int b){
return a+b;
}; // 賦值語句最后有 分號
int a = sum(10,20); // 調(diào)用 Block 變量
2、Block作為屬性(Xcode 快捷鍵:typedefBlock)
// 1. 給 Calculate 類型 sum變量 賦值「下定義」
typedef int (^Calculate)(int, int); // calculate就是類型名
Calculate sum = ^(int a,int b){
return a+b;
};
int a = sum(10,20); // 調(diào)用 sum變量
// 2. 作為對象的屬性聲明,copy 后 block 會轉(zhuǎn)移到堆中和對象一起
@property (nonatomic, copy) Calculate sum; // 使用 typedef
@property (nonatomic, copy) int (^sum)(int, int); // 不使用 typedef
// 聲明,類外
self.sum = ^(int a,int b){
return a+b;
};
// 調(diào)用,類內(nèi)
int a = self.sum(10,20);
3、作為 OC 中的方法參數(shù)
// ---- 無參數(shù)傳遞的 Block ---------------------------
// 實現(xiàn)
- (CGFloat)testTimeConsume:(void(^)())middleBlock {
// 執(zhí)行前記錄下當前的時間
CFTimeInterval startTime = CACurrentMediaTime();
middleBlock();
// 執(zhí)行后記錄下當前的時間
CFTimeInterval endTime = CACurrentMediaTime();
return endTime - startTime;
}
// 調(diào)用
[self testTimeConsume:^{
// 放入 block 中的代碼
}];
// ---- 有參數(shù)傳遞的 Block ---------------------------
// 實現(xiàn)
- (CGFloat)testTimeConsume:(void(^)(NSString * name))middleBlock {
// 執(zhí)行前記錄下當前的時間
CFTimeInterval startTime = CACurrentMediaTime();
NSString *name = @"有參數(shù)";
middleBlock(name);
// 執(zhí)行后記錄下當前的時間
CFTimeInterval endTime = CACurrentMediaTime();
return endTime - startTime;
}
// 調(diào)用
[self testTimeConsume:^(NSString *name) {
// 放入 block 中的代碼,可以使用參數(shù) name
// 參數(shù) name 是實現(xiàn)代碼中傳入的,在調(diào)用時只能使用,不能傳值
}];
4、Block回調(diào)
Block回調(diào)是關于Block最常用的內(nèi)容,比如網(wǎng)絡下載,我們可以用Block實現(xiàn)下載成功與失敗的反饋。block使用簡單,邏輯清晰,靈活。
2.2 Block 幾種類型演算
{
NSLog(@"\n--------------------block調(diào)用 基本數(shù)據(jù)類型---------------------\n");
int a = 10;
NSLog(@"block定義前a地址=%p", &a);
void (^aBlock)() = ^(){
NSLog(@"block定義內(nèi)部a地址=%p", &a);
};
NSLog(@"block定義后a地址=%p", &a);
aBlock();
}
/*
結果:
block定義前a地址=0x7fff5bdcea8c
block定義后a地址=0x7fff5bdcea8c
block定義內(nèi)部a地址=0x7fa87150b850
*/
/*
流程:
1. block定義前:a在棧區(qū)
2. block定義內(nèi)部:里面的a是根據(jù)外面的a拷貝到堆中的,不是一個a
3. block定義后:a在棧區(qū)
*/
{
NSLog(@"\n--------------------block調(diào)用 __block修飾的基本數(shù)據(jù)類型---------------------\n");
__block int b = 10;
NSLog(@"block定義前b地址=%p", &b);
void (^bBlock)() = ^(){
b = 20;
NSLog(@"block定義內(nèi)部b地址=%p", &b);
};
NSLog(@"block定義后b地址=%p", &b);
NSLog(@"調(diào)用block前 b=%d", b);
bBlock();
NSLog(@"調(diào)用block后 b=%d", b);
}
/*
結果:
block定義前b地址=0x7fff5bdcea50
block定義后b地址=0x7fa873b016d8
調(diào)用block前 b=10
block定義內(nèi)部b地址=0x7fa873b016d8
調(diào)用block后 b=20
*/
/*
流程:
1. 聲明 b 為 __block (__block 所起到的作用就是只要觀察到該變量被 block 所持有,就將“外部變量”在棧中的內(nèi)存地址放到了堆中。)
2. block定義前:b在棧中。
3. block定義內(nèi)部: 將外面的b拷貝到堆中,并且使外面的b和里面的b是一個。
4. block定義后:外面的b和里面的b是一個。
5. block調(diào)用前:b的值還未被修改。
6. block調(diào)用后:b的值在block內(nèi)部被修改。
*/
{
NSLog(@"\n--------------------block調(diào)用 指針---------------------\n");
NSString *c = @"ccc";
NSLog(@"block定義前:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
void (^cBlock)() = ^{
NSLog(@"block定義內(nèi)部:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
};
NSLog(@"block定義后:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
cBlock();
NSLog(@"block調(diào)用后:c=%@, c指向的地址=%p, c本身的地址=%p", c, c, &c);
}
/* 輸出結果
block定義前:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
block定義后:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
block定義內(nèi)部:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x6000002542a0
block調(diào)用后:c=ccc, c指向的地址=0x1068aac68, c本身的地址=0x7ffee93a7ab8
c指針本身在block定義中和外面不是一個,但是c指向的地址一直保持不變。
1. block定義前:c指向的地址在堆中, c指針本身的地址在棧中。
2. block定義內(nèi)部:c指向的地址在堆中, c指針本身的地址在堆中(c指針本身和外面的不是一個,但是指向的地址和外面指向的地址是一樣的)。
3. block定義后:c不變,c指向的地址在堆中, c指針本身的地址在棧中。
4. block調(diào)用后:c不變,c指向的地址在堆中, c指針本身的地址在棧中。
*/
{
NSLog(@"\n--------------------block調(diào)用 指針并修改值---------------------\n");
NSMutableString *d = [NSMutableString stringWithFormat:@"ddd"];
NSLog(@"block定義前:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
void (^dBlock)() = ^{
NSLog(@"block定義內(nèi)部:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
d.string = @"dddddd";
};
NSLog(@"block定義后:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
dBlock();
NSLog(@"block調(diào)用后:d=%@, d指向的地址=%p, d本身的地址=%p", d, d, &d);
}
/*輸出結果
block定義前:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
block定義后:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
block定義內(nèi)部:d=ddd, d指向的地址=0x600000440300, d本身的地址=0x604000253940
block調(diào)用后:d=dddddd, d指向的地址=0x600000440300, d本身的地址=0x7ffee9b2dab8
d指針本身在block定義中和外面不是一個,但是d指向的地址一直保持不變。
在block調(diào)用后,d指向的堆中存儲的值發(fā)生了變化。
*/
{
NSLog(@"\n--------------------block調(diào)用 __block修飾的指針---------------------\n");
__block NSMutableString *e = [NSMutableString stringWithFormat:@"eee"];
NSLog(@"block定義前:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
void (^eBlock)() = ^{
NSLog(@"block定義內(nèi)部:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
e = [NSMutableString stringWithFormat:@"new-eeeeee"];
};
NSLog(@"block定義后:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
eBlock();
NSLog(@"block調(diào)用后:e=%@, e指向的地址=%p, e本身的地址=%p", e, e, &e);
}
/*
從block定義內(nèi)部使用__block修飾的e指針開始,e指針本身的地址由棧中改變到堆中,即使出了block,也在堆中。
在block調(diào)用后,e在block內(nèi)部重新指向一個新對象,e指向的堆中的地址發(fā)生了變化。
*/
{
NSLog(@"\n--------------------block調(diào)用 retain cycle---------------------\n");
View *v = [[View alloc] init];
v.tag = 1;
v.frame = CGRectMake(100, 100, 100, 100);
[self.view addSubview:v]; //self->view->v
void (^block)() = ^{
v.backgroundColor = [UIColor orangeColor]; //定義內(nèi)部:block->v
};
v.block = block; //v->block
block();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//預計3秒后釋放v對象。
[v removeFromSuperview];
});
}
/*
結果:
不會輸出 dealloc.
*/
/*
流程:
1. self->view->v
2. block定義內(nèi)部:block->v 因為block定義里面調(diào)用了v
3. v->block
結論:
引起循環(huán)引用的是block->v->block,切斷其中一個線即可解決循環(huán)引用,跟self->view->v這根線無關
*/
{
NSLog(@"\n--------------------block調(diào)用self---------------------\n");
View *v = [[View alloc] init];
v.tag = 2;
v.frame = CGRectMake(100, 220, 100, 100);
[self.view addSubview:v]; //self->view->v
void (^block)() = ^{
self.view.backgroundColor = [UIColor redColor]; //定義內(nèi)部:block->self
_count ++; //調(diào)用self的實例變量,也會讓block強引用self。
};
v.block = block; //v->block
block();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//預計3秒后釋放self這個對象。
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;
appDelegate.window.rootViewController = nil;
});
}
/*
結果:
不會輸出 dealloc.
*/
/*
流程:
1. self->view->v
2. v->block
3. block->self 因為block定義里面調(diào)用了self
結論:
在block內(nèi)引用實例變量,該實例變量會被block強引用。
引起循環(huán)引用的是self->view->v->block->self,切斷一個線即可解決循環(huán)引用。
*/
2.3 Block 存儲域
1. 在全局數(shù)據(jù)區(qū)的Block對象
NSGlobalBlock 靜態(tài)block,釋放有兩種不同的時機:
- 1、如果這個block引用了外部變量后是棧block,則在定義此block的函數(shù)出棧時,block釋放。
- 2、如果這個blcok引用了外部變量之后是堆block,則其宿主target釋放的時候此block才釋放。
{
NSLog(@"\n--------------------block的存儲域 全局塊---------------------\n");
void (^blk)(void) = ^{
NSLog(@"Global Block");
};
blk();
NSLog(@"%@", [blk class]);
}
/*
結果:輸出 __NSGlobalBlock__
*/
/*
結論:
全局塊:這種塊不會捕捉任何狀態(tài)(外部的變量),運行時也無須有狀態(tài)來參與。塊所使用的整個內(nèi)存區(qū)域,在編譯期就已經(jīng)確定。
全局塊一般聲明在全局作用域中。但注意有種特殊情況,在函數(shù)棧上創(chuàng)建的block,如果沒有捕捉外部變量,block的實例還是會被設置在程序的全局數(shù)據(jù)區(qū),而非棧上。
*/
2.在堆上創(chuàng)建的Block對象
NSMallocBlock 堆區(qū)block
堆區(qū)是內(nèi)存的常駐區(qū)域,也叫永久存儲區(qū),block一般在函數(shù)中定義,最多是個棧block。在MRC時代你需要使用Block_copy()方法,才可以將blcok復制到堆中。
復制到堆中有何用處呢?
- 首先,作為一個對象,把它復制到堆中,想要使用它肯定要有一個指針指向它,而指向它的指針是作為property或靜態(tài)變量出現(xiàn)的(如果不被引用也就沒有了常駐于堆區(qū)的意義),而實際開發(fā)中多使用poperty引用。
- 在MRC中,如果一個類的block屬性是使用copy修飾的,則不需要手動調(diào)用Block_copy將其復制到堆中。如果是用strong修飾的,則必須使用Block_copy()將其復制到堆中,并在釋放時調(diào)用Block_release()方法將其釋放,否則會因野指針導致程序崩潰。
{
NSLog(@"\n--------------------block的存儲域 堆塊---------------------\n");
int i = 1;
void (^blk)(void) = ^{
NSLog(@"Malloc Block, %d", i);
};
blk();
NSLog(@"%@", [blk class]);
}
/*
結果:輸出 __NSMallocBlock__
*/
/*
結論:
堆塊:解決塊在棧上會被覆寫的問題,可以給塊對象發(fā)送copy消息將它拷貝到堆上。復制到堆上后,塊就成了帶引用計數(shù)的對象了。
在ARC中,以下幾種情況棧上的Block會自動復制到堆上:
- 調(diào)用Block的copy方法
- 將Block作為函數(shù)返回值時(MRC時此條無效,需手動調(diào)用copy)
- 將Block賦值給__strong修飾的變量時(MRC時此條無效)
- 向Cocoa框架含有usingBlock的方法或者GCD的API傳遞Block參數(shù)時
上述代碼就是在ARC中,block賦值給__strong修飾的變量,并且捕獲了外部變量,block就會自動復制到堆上。
*/
3.在棧上創(chuàng)建的Block對象
NSStackBlock 棧區(qū)block
- 函數(shù)只有入棧后才能執(zhí)行,出棧后就釋放了。
- 棧block一般在函數(shù)內(nèi)部定義,并在函數(shù)內(nèi)部調(diào)用;或者在函數(shù)外部定義,作為函數(shù)的一個參數(shù)在函數(shù)內(nèi)部調(diào)用。函數(shù)出棧時和其他變量或參數(shù)一起釋放。
{
NSLog(@"\n--------------------block的存儲域 棧塊---------------------\n");
int i = 1;
__weak void (^blk)(void) = ^{
NSLog(@"Stack Block, %d", i);
};
blk();
NSLog(@"%@", [blk class]);
}
/*
結果:輸出 __NSStackBlock__
*/
/*
結論:
棧塊:塊所占內(nèi)存區(qū)域分配在棧中,編譯器有可能把分配給塊的內(nèi)存覆寫掉。
在ARC中,除了上面四種情況,并且不在global上,block是在棧中。
*/
三、Block 原理
Block 優(yōu)缺點
優(yōu)點:
- 捕獲外部變量
- 降低代碼分散程度
缺點:
- 循環(huán)引用引起內(nèi)存泄露
Block 總結
- 在block內(nèi)部使用的是將外部變量的拷貝到堆中的(基本數(shù)據(jù)類型直接拷貝一份到堆中,對象類型只將在棧中的指針拷貝到堆中并且指針所指向的地址不變)。
- __block修飾符的作用:是將block中用到的變量,拷貝到堆中,并且外部的變量本身地址也改變到堆中。
- __block不能解決循環(huán)引用,需要在block執(zhí)行尾部將變量設置成nil
- __weak可以解決循環(huán)引用,block在捕獲weakObj時,會對weakObj指向的對象進行弱引用。
- 使用__weak時,可在block開始用局部__strong變量持有,以免block執(zhí)行期間對象被釋放。
- 全局塊不引用外部變量,所以不用考慮。
- 堆塊引用的外部變量,不是原始的外部變量,是拷貝到堆中的副本。
- 棧塊本身就在棧中,引用外部變量不會拷貝到堆中。
- __weak 本身是可以避免循環(huán)引用的問題的,但是其會導致外部對象釋放了之后,block 內(nèi)部也訪問不到這個對象的問題,我們可以通過在 block 內(nèi)部聲明一個 __strong 的變量來指向 weakObj,使外部對象既能在 block 內(nèi)部保持住,又能避免循環(huán)引用的問題。
- __block 本身無法避免循環(huán)引用的問題,但是我們可以通過在 block 內(nèi)部手動把 blockObj 賦值為 nil 的方式來避免循環(huán)引用的問題。另外一點就是 __block 修飾的變量在 block 內(nèi)外都是唯一的,要注意這個特性可能帶來的隱患。
- block的實現(xiàn)原理是C語言的函數(shù)指針。函數(shù)指針即函數(shù)在內(nèi)存中的地址,通過這個地址可以達到調(diào)用函數(shù)的目的。
Q:什么是Block?
A:Block是將函數(shù)
及其執(zhí)行上下文
封裝起來的對象
struct __block_impl{
void *isa;//Block 是對象的標志
int Flags;
int Reserved;
void *FuncPtr;//函數(shù)指針
};
Q:什么是Block調(diào)用?
A:Block調(diào)用是函數(shù)調(diào)用
Q:Block 如何截獲變量?
A:1.基本數(shù)據(jù)
類型的局部變量
截獲其值
2.對象
類型的局部變量連同所有權修飾符
一起截獲
3.局部靜態(tài)變量
以指針
形式截獲
4.不截獲
全局變量、靜態(tài)全局變量
Q:什么情況使用__block修飾符?
A:一般情況下,對被截獲變量進行賦值
操作需要添加__block修飾符
賦值 != 使用
賦值:
賦值操作需要使用 __block
修飾
__block NSMutableArray *arrM = nil;
void (^testBlock)(void) = ^{
arrM = [NSMutableArray array];
};
testBlock();
使用:如下代碼不需要使用__block
,因為是對數(shù)組的操作而不是數(shù)組的賦值。
NSMutableArray *arrM = [NSMutableArray array];
void (^testBlock)(void) = ^{
[arrM addObject:@"addObj"];
};
testBlock();
Q:__block做了什么
A:__block修飾變量變成了對象
Q:Block的Copy操作效果
A:如圖
Q:棧上Block的銷毀
A:如圖
Q:棧上Block的Copy操作
A:如圖
Q:棧上Block的Copy,MRC是否會引起內(nèi)存泄漏
A:會的
Q:棧上__block變量的Copy操作
A:如圖,修改__block變量值,修改的都是堆上的值
Q:__forwarding存在的意義
A:無論在棧還是堆上,__forwarding都可以順利訪問到同一個__block變量
Q:Block外部定義__weak修飾變量可以解決循環(huán)引用?
A:Block截獲對象
是連同所有權修飾符
一起截獲的,如在外部對變量進行__weak
修飾,結構體里持有的對象類型也是weak
Q:Block的引用循環(huán),如圖代碼出現(xiàn)什么問題?
A:MRC下,不會產(chǎn)生循環(huán)引用;ARC下回產(chǎn)生循環(huán)引用,引起內(nèi)存泄漏
ARC下的引用循環(huán)
ARC下的引用循環(huán)解決方案
上述代碼:ARC下解決方案
弊端:如果該Block長時間不被引用,該斷環(huán)處一直存在,循環(huán)引用無法解除
Question1:__weak修飾對象,當外部對象釋放了之后,block 內(nèi)部也訪問不到這個對象,怎么辦?
Answer:通過在 block 內(nèi)部聲明一個 __strong 的變量來指向 weakObj,使外部對象既能在 block 內(nèi)部保持住,又能避免循環(huán)引用的問題。
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf print];
};
我們以 AFNetworking 中 AFNetworkReachabilityManager.m 的一段代碼舉例:
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
Question2:__strong修飾對象,會不會引起循環(huán)引用?
Answer:不會!
詳解:
__weak修飾的對象被Block引用,不會影響對象的釋放,而__strong在Block內(nèi)部修飾的對象,會保證,在使用這個對象在scope內(nèi),這個對象都不會被釋放,出了scope,引用計數(shù)就會-1。
self是一個指向?qū)嵗龑ο蟮闹羔?,它的生命周期至少是伴隨著當前的實例對象的,一旦它和對象之間有循環(huán)引用是無法被自動打破的;strongSelf是block內(nèi)部的一個局部變量,變量的作用域僅限于局部代碼,而程序一旦跳出作用域,strongSelf就會被釋放,這個臨時產(chǎn)生的“循環(huán)引用”就會被自動打破,代碼的執(zhí)行事實上也是這樣子的。