說起B(yǎng)lock在iOS開發(fā)中作用非常多患膛,用處也非常廣。但要用好Block耻蛇,確保業(yè)務(wù)邏輯正常踪蹬,并且內(nèi)存管理不出問題胞此,也是不簡(jiǎn)單的。本篇不再闡述Block的概念和語法用法跃捣,就Block訪問外部變量和循環(huán)引用問題來介紹一下漱牵。
一、Block訪問外部變量
我們先看一個(gè)block訪問外部變量的例子:
int a = 10;
void(^myblock)(void) = ^(void) {
NSLog(@"a=%d",a);
};
a = 20;
myblock();
結(jié)果會(huì)發(fā)現(xiàn)打印的 a = 10
而且如果試圖在block里修改a的值的話疚漆,Xcode就會(huì)報(bào)錯(cuò)酣胀,提示在a的定義前添加__block。
按提示修改代碼后發(fā)現(xiàn):
__block int a = 10;
void(^myblock)(void) = ^(void) {
NSLog(@"a=%d",a);
};
a = 20;
myblock();
現(xiàn)在打印的就是 a = 20 了娶聘。
這是為什么呢闻镶?我們把mian.m文件編譯成C++代碼看一下到底是怎么實(shí)現(xiàn)的:
~/Desktop $ clang -rewrite-objc main.m
得到的main.cpp文件,其中main函數(shù)編譯成了
int main(int argc, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(*myblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
(a.__forwarding->a) = 20;
((void (*)(__block_impl *))((__block_impl *)myblock)->FuncPtr)((__block_impl *)myblock);
return 0;
}
我們的block被編譯成了一個(gè)結(jié)構(gòu)體__main_block_impl_0丸升,變量a作為了結(jié)構(gòu)體的一個(gè)成員:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在main函數(shù)里我們可以看到铆农,a傳過來不只是a的值,而是(__Block_byref_a_0 *)&a狡耻,變量a的指針地址墩剖,所以我們就可以任意讀寫a的值了。
所以:
- block在訪問外部變量時(shí)夷狰,會(huì)在block內(nèi)部創(chuàng)建一個(gè)新的變量來保存這個(gè)外部變量岭皂,修改的是內(nèi)部新變量的值,外部的變量不受影響孵淘;
- 當(dāng)外部變量加了__block修飾蒲障,block保存了其指針引用,對(duì)指針變量的修改直接影響外部變量的值瘫证;
- 另外,靜態(tài)變量庄撮、全局變量和全局靜態(tài)變量背捌,傳入的就是地址值,可以直接被block修改洞斯。
二毡庆、Block循環(huán)引用問題
首先什么是循環(huán)引用呢?就是兩個(gè)對(duì)象相互持有烙如,在釋放時(shí)么抗,相互等待釋放,造成死循環(huán)誰都釋放不了亚铁,從而內(nèi)存泄露蝇刀。
即block作為self的屬性時(shí),又在block內(nèi)部調(diào)用了self的屬性和方法徘溢,block和self相互持有吞琐,那么兩者的引用計(jì)數(shù)都至少是1捆探,都不會(huì)被釋放。
舉個(gè)栗子:
//
// SecondViewController.m
// UITest
//
// Created by z on 2019/8/10.
// Copyright ? 2019年 com.jzsec. All rights reserved.
//
#import "SecondViewController.h"
typedef void(^MyBlock)(int a);
@interface SecondViewController ()
@property (nonatomic,copy) MyBlock myblock;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
SecondViewController *weakSelf = self;
self.myblock = ^(int a) {
[self doSomething];
};
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)doSomething
{
NSLog(@"%s",__func__);
}
-(void)dealloc
{
NSLog(@"%s",__func__);
}
@end
這個(gè)地方還有第一個(gè)界面我就沒寫站粟,很簡(jiǎn)單就是點(diǎn)擊屏幕彈出這個(gè)SecondViewController黍图,這個(gè)second點(diǎn)擊界面就dismiss掉。但是運(yùn)行后我們發(fā)現(xiàn):
- second的dealloc方法并沒有調(diào)用奴烙,說明第二個(gè)界面消失后并沒有釋放助被;
- 代碼中[self doSomething];編譯器給出警告:Capturing 'self' strongly in this block is likely to lead to a retain cycle,可以看出block里的變量默認(rèn)是strong持有的切诀,self又持有myBlock揩环,所以提示循環(huán)引用了;
那么怎么避免造成循環(huán)引用呢趾牧?這里介紹三種方法:
- ARC下用__weak或__block;
- MRC下用__block;
- self作為block的參數(shù)傳進(jìn)去检盼;
1、__weak
使用__weak定義一個(gè)弱引用的self臨時(shí)變量翘单,去掉強(qiáng)引用吨枉,這也是最常用的一種方法:
__weak typeof(self) weakSelf = self;
self.myblock = ^(int a) {
[weakSelf doSomething];
};
這樣既去掉了編譯器警告,而且還打印出了-[SecondViewController dealloc]哄芜,說明second已經(jīng)釋放掉貌亭,解決了循環(huán)引用。
2认臊、__block
同樣是聲明一個(gè)臨時(shí)self的變量圃庭,但是要記得在block里使用完這個(gè)臨時(shí)變量時(shí),把它置空失晴,而且還要調(diào)用這個(gè)block:
__block SecondViewController *weakSelf = self;
self.myblock = ^(int a) {
[weakSelf doSomething];
weakSelf = nil;
};
self.myblock(1);
此時(shí)打印出:
2019-08-10 19:02:35.638863+0800 UITest[11066:395433] -[SecondViewController doSomething]
2019-08-10 19:02:36.792805+0800 UITest[11066:395433] -[SecondViewController dealloc]
說明second已經(jīng)釋放掉剧腻,解決了循環(huán)引用。
3涂屁、self作為block的參數(shù)
這個(gè)地方我們需要做一些改造书在,block重新定義一下:
typedef void(^MySecondBlock)(SecondViewController *vc);
... ...
@property (nonatomic,copy) MySecondBlock mySecondblock;
... ...
self.mySecondblock = ^(SecondViewController *vc) {
[vc doSomething];
};
self.mySecondblock(self);
此時(shí)運(yùn)行后,依然能打印出:
2019-08-10 19:05:12.690490+0800 UITest[11160:399180] -[SecondViewController doSomething]
2019-08-10 19:05:14.369980+0800 UITest[11160:399180] -[SecondViewController dealloc]
說明second已經(jīng)釋放掉拆又,解決了循環(huán)引用儒旬。
三、注意
1帖族、block屬性
block作為屬性時(shí)栈源,修飾符一定要用copy!在ARC下使用strong和copy效果一樣竖般,如果使用weak甚垦,那么這個(gè)block屬性隨時(shí)都有可能被釋放掉,而無法調(diào)用。
2制轰、不會(huì)造成循環(huán)引用的情況
那么只要是用到block前计,而不用上面三種方法就一定會(huì)造成循環(huán)引用嗎?不是的垃杖,下面情況不會(huì)造成循環(huán)引用:
- 大部分GCD的block男杈;
- Block作為臨時(shí)變量;
3调俘、避免self在block里提前釋放
比如說self在block里延遲使用了伶棒,但是block走完之后weakSelf就不存在了,所以doSomething也就沒有響應(yīng):
__weak typeof(self) weakSelf = self;
self.myblock = ^(int a) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf doSomething];
});
};
Block執(zhí)行過程中self對(duì)象被釋放彩库,用__strong修飾self肤无,記得得調(diào)用了Block才起作用:
__weak typeof(self) weakSelf = self;
self.myblock = ^(int a) {
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf doSomething];
});
};
self.myblock(1);
此時(shí)依然打印出:
2019-08-10 19:18:34.574815+0800 UITest[11490:411849] -[SecondViewController doSomething]
2019-08-10 19:18:34.575089+0800 UITest[11490:411849] -[SecondViewController dealloc]
__strong typeof(self) strongSelf = weakSelf;
這句延長(zhǎng)了weakSelf的生命周期,走完dispatch_after的代碼才會(huì)釋放掉骇钦,即延遲釋放宛渐。