在介紹Block之前通過一個簡單的應(yīng)用場景認識下Block
場景描述如下:TableView上面有多個CustomTableViewCell沫换,cell上面顯示的是文字信息和一個詳情Button沦寂,點擊button后push到一個新的頁面。
對于這個簡單的需求: 點擊button以實現(xiàn)頁面跳轉(zhuǎn)枕面。button添加在CustomTableViewCell上锰镀,若直接在cell中實現(xiàn)跳轉(zhuǎn)邏輯超棺,就違背MVC的計模式。項目小還好侦讨,隨著功能的擴展,你會發(fā)現(xiàn)越寫越難寫苟翻;還有一種情況韵卤,就是這件事情做不到,只能委托給其他對象來做了崇猫,此時使用delegate
委托是個很好的選擇沈条。
下面先用delegate
實現(xiàn),代碼如下:
CustomTableViewCell
類中
@protocol CustomTableViewCellDelegate <NSObject>
- (void) pushToNewView ; //代理要實現(xiàn)的方法
@end
@interface CustomTableViewCell : UITableViewCell
@property(nonatomic,strong) UIButton *detailButton;
@property(nonatomic, weak) id<CustomCellDelegate> delegate;
@end
為CustomTableViewCell
添加協(xié)議诅炉,并聲明要代理要實現(xiàn)的方法蜡歹。
接下來在CustomTableViewCell.m
中為button添加點擊事件,
button的 click 事件如下:
- (void)buttonClick:(UIButton *)button {
if (_delegate && [_delegate respondsToSelector:@selector(pushToNewView)]) {
[_delegate pushToNewView];
}
}
跟著就是完善受到委托申請的類涕烧,這里是對應(yīng)CustomTableViewCell
所在的控制器月而,首先遵循CustomTableViewCellDelegate
協(xié)議,然后要實現(xiàn)其中的pushToNewView方法议纯,最重要的是設(shè)置CustomTableViewCell
對象cell的delegate等于self父款。
@interface ViewController ()< CustomTableViewCellDelegate >
@property (nonatomic, strong) NSArray *textArray;
@end
實現(xiàn) pushToNewView的方法,
- (void) pushToNewView {
DetailViewController*detailVC = [[DetailViewController alloc] init];
[self.navigationController pushViewController:detailVC animated:YES];
}
同時需要設(shè)置 CustomTableViewCell 對象cell的delegate痹扇,在 cellForRow
中實現(xiàn) cell.delegate = self
即可铛漓。
此時self.delegate其實就是ViewController,cell對象委托了ViewController實現(xiàn)pushToNewVC方法鲫构。這個簡單的場景描述了使用代理的一種情況浓恶,就是CustomTableViewCell沒有能力實現(xiàn)pushViewController的功能,所以委托ViewController來實現(xiàn)结笨。
------------------- 分割線 --------------------------
接著用 Block 實現(xiàn)上述功能
根據(jù)需求:定義一個無參無返的Block, 簡化代碼使用 typedef
typedef void(^ButtonCallback)(void);
同時需要在CustomTableViewCell
類中添加block屬性
@property (nonatomic, copy) ButtonCallback buttonCallback;
在button 點擊事件中包晰,調(diào)用剛剛聲明的 buttonCallback
- (void)buttonClick:(UIButton *)button {
if(self.buttonCallback){
self.buttonCallback();
}
}
最后我們回到CustomTableViewCell
所在的控制器中湿镀,去實現(xiàn) buttonCallback
找到cellForRow
的方法,通過cell點出buttonCallback
的屬性伐憾,并實現(xiàn)
cell.buttonCallback = ^{
DetailViewController*detailVC = [[DetailViewController alloc] init];
[self.navigationController pushViewController:detailVC animated:YES];
};
兩種方式對比勉痴,Block
要比 delegate
精簡很多.
下面認識下 OC 中的 Block
1. 閉包 (了解)
在計算機科學(xué)中,閉包(Closure)是詞法閉包(Lexical Closure)的簡稱树肃,是引用了自由變量的函數(shù)蒸矛。這個被引用的自由變量將和這個函數(shù)一同存在,即使已經(jīng)離開了創(chuàng)造它的環(huán)境也不例外胸嘴。所以雏掠,有另一種說法認為閉包是由函數(shù)和與其相關(guān)的引用環(huán)境組合而成的實體。 ---- 維基百科
閉包就是一個函數(shù)劣像,或者一個指向函數(shù)的指針乡话,加上這個函數(shù)執(zhí)行的非局部變量。
說的通俗一點耳奕,就是閉包允許一個函數(shù)訪問聲明該函數(shù)運行上下文中的變量绑青,甚至可以訪問不同運行上文中的變量。
2.Block 基本語法及使用
Block實際上是OC語言對閉包的實現(xiàn)屋群,是帶有自動變量值的匿名函數(shù)
2.1 Block的原型及定義
Block語法及常見類型
^ 返回值類型 參數(shù)列表 表達式
其中闸婴,返回值類型可省略
^ 參數(shù)列表 表達式
當沒有參數(shù)時,可省略參數(shù)列表
^表達式
Block基本語法
Block 常見類型
- 有參有返回值
NSString(^testBlock)(int) = ^(int num){
return [NSString stringWithFormat:"%d",num];
};
- 有參無返回值
void(^testBlock)(int) = ^(int num){
NSLog(num = "%d",num);
};
- 無參無返回值
void(^testBlock)(void)= ^{
NSLog(@"無參無返回值");
};
- 無參有返回值
int(^testBlock)(void) = ^{
return 10;
};
下面我們簡單使用下Block:
先寫一個Block原型 (有參有返回值)
NSString *(^myBlock)(int)
下面看下Block的實現(xiàn)部分
myBlock = ^(int num){
return [NSString stringWithFormat: @"Passed number: %d", num];
};
代碼中將一個函數(shù)體賦值給了myBlock變量谓晌,其接收一個名為num一個NSString對象掠拳。
Block最后的分號一定不能忘
至于調(diào)用
myBlock(3)
可以像調(diào)用其他函數(shù)一樣使用Block。
代碼簡化:
由于block數(shù)據(jù)類型的語法會降低整個代碼的閱讀性纸肉,所以常使用typedef來定義block類型溺欧。
typedef NSString(^MyBlock)(int);
上述重定義后構(gòu)建了MyBlock新類型,這樣我們就可以在屬性聲明或方法中使用更加有語義的數(shù)據(jù)類型柏肪。
下圖很好的總結(jié)Block結(jié)構(gòu)
2.2 Block的使用 (代碼為例)
- 界面?zhèn)髦?/li>
- 回調(diào)
3. Block對外部變量的截獲
3.1 局部變量
局部自動變量姐刁,在Block中可被讀取。Block定義時copy變量的值烦味,在Block中作為常量使用聂使,所以即使變量的值在Block外改變,也不影響他在Block中的值谬俄,Block此時對局部變量只是做了值傳遞的操作柏靶。
3.2 static 修飾的全局變量
因為全局變量或靜態(tài)變量在內(nèi)存中的地址是固定的,Block在讀取該變量值的時候是直接從其所在內(nèi)存地址讀出溃论,獲取到的是最新值屎蜓,而不是在定義時copy的常量。
3.3 對OC對象的截獲
NSMutableArray *array = [NSMutableArray array];
void(^block)() = ^(){
NSObject *obj = [[NSObject alloc] init];
[array addObject:obj];
};
block();
上述代碼編譯通過钥勋,Block截獲的值為NSMutableArray類的對象炬转,用C語言表述辆苔,就是用的NSMutableArray類的對象所用的結(jié)構(gòu)體實例的指針,所以向該對象中添加元素操作屬于使用截獲變量的值扼劈,因此是沒有問題的驻啤。那么對該截獲的變量進行賦值
編譯未通過,提示缺少__block修飾符荐吵。
3.4 C語言數(shù)組
上圖代碼中, 在Block外部定義一個C語言字符串字面量數(shù)組, 在Block內(nèi)部截獲自動變量的方法并沒有實現(xiàn)對C語言數(shù)組的截獲, 此時訪問數(shù)組元素
text[2]
會報錯. 此時使用指針可以解決.
const char *text = "adsdczv";
void(^block)() = ^(){
NSLog(@"%c",text[2]);
};
block();
3.5 ______block 修飾的變量
某些場景下骑冗,我們需要在Block內(nèi)部對外部變量進行修改。這時需要使用__block來修飾該變量實現(xiàn)在Block內(nèi)部的修改先煎,此時Block是復(fù)制其引用地址來實現(xiàn)訪問的沐旨。
關(guān)于______block 修飾符
從上面講解我們已經(jīng)知道,Block內(nèi)部能夠讀取外部局部變量的值榨婆。但如果我們需要在Block內(nèi)部修改變量的值,則需要在Block外部給該變量添加一個__block
修飾符褒侧。
__block
另一個使用場景是良风,避免某些情況下Block使用中出現(xiàn)的循環(huán)引用的問題,此時可以給相應(yīng)的對象加上一個__block
來修飾闷供。
為什么使用__block可以實現(xiàn)在Block內(nèi)部修改外部變量的值烟央?
這邊我們用一個Block代碼,并使用clang _rewrite_objc
命令轉(zhuǎn)換成C++的代碼來說明__block
是怎么實現(xiàn)內(nèi)部變量的修改歪脏。
Block在main中實現(xiàn)
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSInteger val = 10;
void (^block)(void) = ^{
NSLog(@"%ld", val);
};
block();
}
return 0;
}
轉(zhuǎn)碼后:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__val_folders_gm_0jk35cwn1d3326x0061qym280000gn_T_main_41daf1_mi_0, val);
}
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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int val = 10;
void (*block)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val);
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
從展開代碼可以發(fā)現(xiàn)疑俭,Block被轉(zhuǎn)成了一個struct __main_block_impl_0
類型的結(jié)構(gòu)體實例,并且該結(jié)構(gòu)體成員中包含局部變量val
婿失。當執(zhí)行Block時钞艇,通過該實例找到Block執(zhí)行部分void __main_block_func_0
,并把該結(jié)構(gòu)體實例傳入到void __main_block_func_0
方法中豪硅。
void __main_block_func_0
方法中第一個參數(shù)聲明如下
struct __main_block_impl_0 *__cself
*注意:這里的__cself就類似于OC中的self, * 而它指向結(jié)構(gòu)體的指針哩照。
此時我們就可以通過__cself->val
訪問該局部變量。
那么問題來了懒浮,為什么此時不對變量val
進行修改飘弧?
因為main函數(shù)中的局部變量val
和函數(shù)__main_block_func_0
不在同一個作用域中,調(diào)用過程中只是進行了值傳遞砚著。
當然次伶,在上面代碼中,我們可以通過指針來實現(xiàn)局部變量的修改稽穆。不過這是由于在調(diào)用__main_block_func_0
時冠王,main函數(shù)棧還沒展開完成,變量val
還在棧中秧骑。
但是在很多情況下版确,Block是作為參數(shù)傳遞以供后續(xù)回調(diào)執(zhí)行的扣囊。通常在這些情況下,Block被執(zhí)行時绒疗,定義時所在的函數(shù)棧已經(jīng)被展開侵歇,局部變量已經(jīng)不在棧中了,再用指針訪問會產(chǎn)生野指針錯誤吓蘑。
所以惕虑,這類情況下對于auto類型的局部變量,不允許Block進行修改是合理的磨镶。
__block是如何實現(xiàn)變量修改的
此時使用更新后的代碼
添加
__block
修飾符后
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSInteger val = 0;
void (^block)(void) = ^{
val = 1;
};
block();
NSLog(@"val = %ld", val);
}
return 0;
}
使用_rewrite_objc
展開
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
NSInteger val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 0};
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_2h_70k4gzp53qn7ytk0cdjr9kk80000gn_T_main_7eb9e7_mi_0,(val.__forwarding->val));
}
return 0;
}
這次轉(zhuǎn)碼后似乎比剛才多了些東西溃蔫,仔細看下,
一個是__Block_byref_val_0
的結(jié)構(gòu)體以及兩個方法static void __main_block_copy_0
和static void __main_block_dispose_0
; 后面的兩個方法先暫且不關(guān)注(后面會涉及)琳猫。
其實結(jié)構(gòu)體__Block_byref_val_0
產(chǎn)生的實例就是我們使用__block修飾過的變量伟叛。
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
NSInteger val;
};
從該結(jié)構(gòu)體聲明可以看出,這個結(jié)構(gòu)體中包含了該實例本身的引用 __forwarding脐嫂。
- 我們從上述被轉(zhuǎn)化的代碼中可以看出 Block 本身也一樣被轉(zhuǎn)換成了
__main_block_impl_0
結(jié)構(gòu)體實例统刮,該實例持有__Block_byref_val_0
結(jié)構(gòu)體實例的指針。
我們再看一下Block實現(xiàn)和調(diào)用部分代碼被轉(zhuǎn)化后的結(jié)果:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
- 不難發(fā)現(xiàn)從
__cself
找到__Block_byref_val_0
結(jié)構(gòu)體實例账千,然后通過該實例的__forwarding
訪問成員變量val
侥蒙。成員變量val
是該實例自身持有的變量,指向的是原來的局部變量匀奏。
詳情參見下圖:
至此鞭衩,已經(jīng)展示了__block
變量在Block中查找和修改的過程,那么:
- 當Block作為回調(diào)執(zhí)行時娃善,局部變量val已經(jīng)出棧了论衍,這個時候代碼為什么還能正常工作呢?
- 我們?yōu)槭裁赐ㄟ^成員變量
__forwarding
而不是直接去訪問結(jié)構(gòu)體中我們需要修改的變量呢?
這邊需要引入下一個概念: 存儲域
我們在上述轉(zhuǎn)換過的代碼中可以發(fā)現(xiàn) __main_block_impl_0
結(jié)構(gòu)體構(gòu)造函數(shù)中,isa
指針指向的是 _NSConcreteStackBlock
; 而Block還有另外兩個與之相似的類:
- _NSConcreteGlobalBlock //全局的靜態(tài)block 不會訪問任何外部變量
- _NSConcreteMallocBlock //保存在堆區(qū)的会放,引用計數(shù)為0時會被銷毀饲齐。
- _NSConcreteStackBlock //保存在棧區(qū),出棧后被銷毀
上述示例代碼中咧最,Block是被設(shè)為_NSConcreteStackBlock
捂人,在棧上生成。當我們把Block作為全局變量使用時矢沿,對應(yīng)生成的Block將被設(shè)為_NSConcreteGlobalBlock
void (^block)(void) = ^{NSLog(@"This is a Global Block");};
int main(int argc, const char * argv[]) {
@autoreleasepool {
block();
}
return 0;
}
該代碼轉(zhuǎn)碼c++后滥搭,Block結(jié)構(gòu)體的isa指針初始化時如下:
impl.isa = &_NSConcreteGlobalBlock;
那_NSConcreteMallocBlock
何時被使用
分配在全局變量上的Block捣鲸,在變量作用域外也可以通過指針安全的訪問瑟匆。
但分配在棧上的Block,如果它所屬的變量作用域結(jié)束栽惶,該Block就被廢棄愁溜。同樣疾嗅,__block
變量也分配在棧上,當超過該變量的作用域時冕象,該__block
變量也會被廢棄代承。
此時,就需要使用 _NSConcreteMallocBlock
渐扮,OC中提供了將Block和__block變量從棧上復(fù)制到堆上的方法论悴,將分配到棧上的Block復(fù)制到堆上,這樣當棧上的Block超過它原本作用域時墓律,堆上的Block還可以繼續(xù)存在膀估。
復(fù)制到堆上的Block,它的結(jié)構(gòu)體成員變量isa將變?yōu)椋?br>
impl.isa = &_NSConcreteMallocBlock耻讽;
而_block
變量中結(jié)構(gòu)體成員__forwarding
就在此時保證了從棧上復(fù)制到堆上能夠正確訪問__block
變量察纯。在這種情況下,只要棧上的_block
變量的成員變量__forwarding
指向堆上的實例针肥,我們就能夠正確訪問捐寥。
我們一般可以使用copy方法手動將 Block 或者 __block變量從棧復(fù)制到堆上。比如我們把Block做為類的屬性訪問時祖驱,我們一般把該屬性設(shè)為copy。有些情況下我們可以不用手動復(fù)制,比如Cocoa框架中使用含有usingBlock方法名的方法時瞒窒,或者GCD的API中傳遞Block時捺僻。
當一個Block從棧復(fù)制到堆中,與之相關(guān)的__block
變量也會被復(fù)制到堆中崇裁。此時堆中的Block持有相應(yīng)堆上的__block
變量匕坯,當堆上的__block
變量沒有持有者,才會被釋放拔稳。
- 而在棧上的
__block
變量被復(fù)制到堆上之后葛峻,會將成員變量__forwarding
的值替換為堆上的__block
變量的地址。這個時候我們可以通過以下代碼訪問:
val.__forwarding->val
如下圖:
__block變量和循環(huán)引用問題
__block修飾符可以指定任意類型的局部變量巴比。此時還記這兩個方法嗎术奖?
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src {
_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);
}
當Block從棧復(fù)制到堆時,會調(diào)用_Block_object_assign
函數(shù)持有該變量(相當于retain);
當堆上的Block被廢棄時轻绞,會調(diào)用_Block_object_dispose
函數(shù)釋放該變量(相當于release)采记。
由上文描述可知,我們可以使用下述代碼解除Block循環(huán)引用的問題:
__block id tmp = self;
void(^block)(void) = ^{
tmp = nil;
};
block();
通過執(zhí)行block方法掰读,nil被賦值到_block
變量tmp中背稼。這個時候_block
變量對 self 的強引用失效吊洼,從而避免循環(huán)引用的問題磅氨。
*總結(jié):
通過__block
變量可以控制對象的生命周期既棺,在不能使用__weak
修飾符的環(huán)境中讽挟,我們可以避免使用__unsafe_unretained
修飾符。
在執(zhí)行Block時可動態(tài)地決定是否將nil或者其它對象賦值給__block
變量丸冕。
但是這種方法有一個明顯的缺點就是耽梅,我們必須去執(zhí)行Block才能夠解除循環(huán)引用問題,否則就會出現(xiàn)問題晨仑。
4. 比較 ______weak 和 __strong
這邊用AFN中的一段代碼
__weak __typeof(self)weakSelf = self;
AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.networkReachabilityStatus = status;
if (strongSelf.networkReachabilityStatusBlock) {
strongSelf.networkReachabilityStatusBlock(status);
}
};
1. __weak
我們在使用Block時褐墅,有時候會用到self
,而Block內(nèi)部對self
默認都是強引用洪己。在ARC下妥凳,編譯器將Block從棧區(qū)拷貝到堆區(qū),Block會強引用和持有self
答捕,而self 也會強引用和持有Block逝钥,于是就造成了循環(huán)引用。
此時就需要使用__weak
拱镐,在修飾變量時艘款,修飾符修飾變量 self
,讓 block 不強引用 self
沃琅,從而破除循環(huán)哗咆。
__weak typeof(self) weakSelf = self;
self.passValueBlock = ^(NSString *string){
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.pointView.startLabel.text = string;
});
};
弱引用不會影響對象釋放,當一個對象被釋放是益眉,所有指向它的弱引用會被置空晌柬,也避免出現(xiàn)野指針。
2. __strong
上面提到郭脂,__weak
很好的解決retain Cycle
年碘,但還是會存在一些隱患。不知道self
什么時候被釋放展鸡,為了保證在Block內(nèi)部不會被釋放屿衅,所以使用__strong
修飾。
看下一段測試代碼
在ViewController
添加屬性
@property (nonatomic, strong) ViewController *vc;
viewDidLoad
中
ViewController *vc = [[ViewController alloc] init];
self.vc = vc;
__weak ViewController * weakVC = self.vc;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSInteger count = 0;
while (count < 4) {
count++;
NSLog(@"%@",weakVC);
sleep(1);
}
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.vc = nil;
});
實現(xiàn)dealloc方法
- (void)dealloc {
NSLog(@"%@",[self class]);
}
看輸出結(jié)果:
2016-06-20 15:12:27.797 __strongTest[14823:1753981] <ViewController: 0x7fdbd2724c20>
2016-06-20 15:12:28.802 __strongTest[14823:1753981] <ViewController: 0x7fdbd2724c20>
2016-06-20 15:12:29.797 __strongTest[14823:1753934] ViewController
2016-06-20 15:12:29.804 __strongTest[14823:1753981] (null)
2016-06-20 15:12:30.808 __strongTest[14823:1753981] (null)
可以看出:Block內(nèi)部的對self.vc
是弱引用莹弊。當2s后涤久,self.vc
在外部被釋放,則Block內(nèi)部對self.vc
的持有也失效忍弛。
現(xiàn)在在Block內(nèi)部對self.vc
進行強引用拴竹,Block內(nèi)部代碼調(diào)整為:
__strong ViewController *strongVC = weakVC;
NSInteger count = 0;
while (count < 4) {
count++;
NSLog(@"%@",strongVC);
sleep(1);
}
再看輸出結(jié)果:
2016-06-20 15:22:38.423 __strongTest[14839:1762881] <ViewController: 0x7fd632d1f690>
2016-06-20 15:22:39.424 __strongTest[14839:1762881] <ViewController: 0x7fd632d1f690>
2016-06-20 15:22:40.429 __strongTest[14839:1762881] <ViewController: 0x7fd632d1f690>
2016-06-20 15:22:41.430 __strongTest[14839:1762881] <ViewController: 0x7fd632d1f690>
2016-06-20 15:22:42.431 __strongTest[14839:1762835] ViewController
Block內(nèi)部對對象采用strong修飾后,既使原持有對象在block外部已經(jīng)被釋放剧罩,但Block內(nèi)部扔能持有栓拜,于是執(zhí)行完Block后,該對象才被dealloc。
總結(jié):
weakSelf
是為了Block不持有self幕与,避免循環(huán)引用挑势,而再聲明一個strongSel
f是因為一旦進入Block執(zhí)行,就不允許self在這個執(zhí)行過程中釋放啦鸣。Block執(zhí)行完后這個strongSelf
會自動釋放潮饱,沒有循環(huán)引用問題。
最后诫给,使用Block時的注意事項
1.Block內(nèi)部不能直接修改局部變量
Block內(nèi)部可以訪問外部的變量, 默認是將其拷貝到其數(shù)據(jù)結(jié)構(gòu)中來實現(xiàn)訪問的, 屬性是只讀的. Block內(nèi)部不能修改外面的局部變量.
如果要修改需要對要修改的局部變量用__block
修飾, 這樣局部變量就可以在Block內(nèi)部修改了香拉,Block是復(fù)制其引用地址來實現(xiàn)訪問的
2.當Block里面的出現(xiàn)self,造成的循環(huán)引用
循環(huán)引用就是當self 擁有一個Block的時候,在Block中又調(diào)用self的方法中狂。形成了你中有我凫碌,我中有你,造成誰都無法將誰釋放胃榕。從而發(fā)生內(nèi)存泄漏盛险。
解決方法:
__weak typeof (self) weakSelf = self;
定義一個weakSelf變量并加上__weak
修飾符,在Block代碼塊中勋又,所有需要self的地方都用weakSelf來替代苦掘。這樣就不會增加引用計數(shù),所以Block持有self對象也就不會造成循環(huán)引用楔壤,從而避免內(nèi)存泄漏鹤啡。
參考
Objective-C中的Block
閉包(Closures)
Objective-C中Block的存儲域
______block & ______weak & __strong
Objective-C 高級編程: iOS和OS X多線程和內(nèi)存管理