本文主要總結(jié)了:
- block的基本語(yǔ)法
有/無(wú)返回值和形參
typedef定義block類(lèi)型- 變量訪(fǎng)問(wèn),__block底層原理
- block的3種類(lèi)型
- 什么時(shí)候會(huì)觸發(fā)block的copy
- block的循環(huán)引用
什么是循環(huán)引用
block循環(huán)引用例子
檢測(cè)工具instruments-Leaks- __weak
為什么__weak可以打破循環(huán)引用?
常見(jiàn)使用- __strong
使用場(chǎng)景,例子(延遲函數(shù))
一、block基本語(yǔ)法
block用來(lái)保存一段代碼,封裝代碼段,在需要的時(shí)候用。
block的標(biāo)志:^
1. 沒(méi)有返回值和形參時(shí),可以省略后面的()
void (^myBlock) ()= ^ {
//要保存的代碼段
};
// 調(diào)用方式與函數(shù)一樣:
myBlock();
2. 有返回值和形參
int (^sumBlock) (int , int) = ^(int a , int b){
return a+b;
};
int c = sumBlock(10,12);
對(duì)比函數(shù)指針:
int sum(int a, int b){
return a+b;
}
//調(diào)用:
int (*p)(int, int)=sum;
int d = p(10,12);
- 再來(lái)一個(gè)例子:
void (^lineBlock)(int) = ^(int n){
for(int i=0; i<n; i++){
NSLog(@“_______”);
}
};
lineBlock(5);
3. 使用typedef定義block類(lèi)型
//定義MyBlock是一個(gè)傳兩個(gè)int類(lèi)型的參數(shù),返回一個(gè)int的代碼塊
typedef int (^MyBlock)(int, int);
這時(shí)
int (^sumBlock) (int , int) = ^(int a , int b){
return a+b;
};
int c = sumBlock(10,12);
就可以寫(xiě)成:
MyBlock sumBlock=^(int a, int b){
return a+b;
};
sumBlock(1, 2);
二、變量訪(fǎng)問(wèn)
- 在block中可以使用和改變全局變量
- block內(nèi)部可以訪(fǎng)問(wèn)外面的變量
- 局部變量,可以使用,不能改變
本地變量,代碼塊會(huì)在定義的時(shí)候復(fù)制并保存他們的狀態(tài),作為常量獲取到
typedef double (^MulBlock)(void);
double a=10,b=20;
MulBlock b=^(void){
return a*b;
}
a=20;
b=50;
NSLog(@“%f”,b(a, b));//此時(shí)還是會(huì)輸出200
*可以通過(guò)將變量標(biāo)記為全局(static)解決
- 默認(rèn)情況下,block內(nèi)部不能修改外面的局部變量
- 給局部變量加上_blcok關(guān)鍵字,這個(gè)局部變量就可以在block內(nèi)部捕捉到變量,并可以進(jìn)行修改
__block int b=20;
【注意】__block修飾時(shí),底層實(shí)現(xiàn)是將變量從??截愐环莸蕉阎?,從而獲得使用權(quán),用代碼打印一下:
__block int a = 0;
NSLog(@"a address %p",&a);
void (^function)()=^(){
NSLog(@"a address %p",&a);
a = 100;
NSLog(@"1 %d",a);
};
a = 20;
function();
NSLog(@"a address %p",&a);
NSLog(@"2 %d",a);
//打印結(jié)果:
// a address 0x7ffeefbff5d8,還在棧中
// a address 0x10060fff8,拷貝到了堆中
// 1 100
// a address 0x10060fff8
// 2 100,在block中對(duì)a修改成功
- __block int a = 0;對(duì)a的內(nèi)存地址進(jìn)行修改。從棧copy到堆中,此時(shí)不會(huì)被隨意銷(xiāo)毀。
- 棧的區(qū)間一般來(lái)說(shuō)是2M,變化區(qū)間過(guò)大內(nèi)存地址會(huì)發(fā)生變化,由高地址跑到低地址,進(jìn)入堆中。
附上內(nèi)存中堆棧的關(guān)系圖(參考了_block關(guān)鍵字的實(shí)現(xiàn)原理):

三、block的3種類(lèi)型:
1. NSGlobalBlock,全局block:靜態(tài)
- 位于全局區(qū)
- 在block內(nèi)部不使用外部變量,或者只使用靜態(tài)變量和全局變量
//例子1
- (void)viewDidLoad {
// main thread [block copy]
void (^block)(void) = ^{
NSLog(@"hello block");
}; //匿名函數(shù)
block();
NSLog(@"%@",block);
//萬(wàn)物皆對(duì)象 <__NSGlobalBlock__: 0x108656088>
//雖然賦值給了強(qiáng)引用對(duì)象“block”(默認(rèn)__strong修飾),但是沒(méi)有使用外部變量,所以是globalBlock
}
//例子2
- (void)viewDidLoad {
NSLog(@"%@",^{
});
//<__NSGlobalBlock__: 0x100001028>
//沒(méi)有使用外部變量,所以還是globalBlock
}
2. NSMallocBlock,堆block
- 位于堆區(qū)
- 在block內(nèi)部使用局部變量或者OC屬性,并且賦值給強(qiáng)引用或者Copy修飾的變量
- (void)viewDidLoad {
int a = 10; //捕獲外部變量
//下面括號(hào)里的block,默認(rèn)是用__strong修飾的強(qiáng)引用
void (^block)(void) = ^{
//訪(fǎng)問(wèn)內(nèi)存空間訪(fǎng)問(wèn)、捕獲外部變量
NSLog(@"hello block %d",a);
}; //匿名函數(shù)
block();
NSLog(@"%@",block); //<__NSMallocBlock__: 0x6000025c4ba0>
}
3. NSStackBlock,棧block
- 位于棧區(qū)
- 與mallocBlock一樣,可以在內(nèi)部使用局部變量或者OC屬性。但是!不能賦值給強(qiáng)引用或Copy修飾的變量。
//例子1
- (void)viewDidLoad {
int a = 10;
NSLog(@"%@",^{
NSLog(@"%d",a); //<__NSStackBlock__: 0x7ffee71e2930>
});
}
//例子2
- (void)viewDidLoad {
int a = 10;
//不寫(xiě)"__weak"時(shí)默認(rèn)是__strong修飾
void(^__weak block)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",block); //<__NSStackBlock__: 0x7ffeefbff4c8>
}
四、什么情況下會(huì)觸發(fā)block的Copy
- 手動(dòng)copy?
- (void)viewDidLoad {
int a = 10;
void (^__weak myBlock)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",myBlock); //<__NSStackBlock__: 0x7ffeefbff4c8>
NSLog(@"%@",[myBlock copy]); //<__NSMallocBlock__: 0x1007003f0>
}
- Block作為返回值? 不一定會(huì)觸發(fā)copy
- (void)viewDidLoad {
NSLog(@"%@", [self returnBlock]);
//因?yàn)橄旅娣椒ㄖ械腷lock,并沒(méi)有使用局部變量,仍然是globalBlock
}
- (void(^)(void))returnBlock {
return ^{
};
}
- 被強(qiáng)引用或者Copy修飾?也要符合“在block內(nèi)部使用了局部變量”
- 系統(tǒng)API包含usingBlock
- NSArray
五、block的循環(huán)引用
1. 什么是循環(huán)引用?
在堆上的對(duì)象與堆上的對(duì)象互相強(qiáng)引用造成的環(huán)。
可能是兩個(gè)對(duì)象,可能是N個(gè)對(duì)象
2. block循環(huán)引用例子
- 例子1
person->block, block->person
@interface Person : NSObject
@property (nonatomic, copy) void(^block)(void);
@end
int main(int argh, const char * argue[]) {
Person *person = [Person new];
person.block = ^{ //賦值時(shí)person持有了block
NSLog(@"%@", person); //block內(nèi)部又捕獲person,引用+1
};
return 0;
}
- 例子2
在最外層ViewController中,創(chuàng)建BlockCoat類(lèi),BlockCoat中有有一個(gè)屬性是BlockTest類(lèi),BlockTest類(lèi)中有一個(gè)屬性是block。
在ViewController中,調(diào)用BlockCoat的方法,通過(guò)方法調(diào)用完之后,BlockTest有無(wú)執(zhí)行dealloc方法,來(lái)判斷是否存在循環(huán)引用。
// ViewController.m
#import "BlockCoat.h"
@implementation ViewController
- (void)viewDidLoad {
NSLog(@"-----lychee1----");
BlockCoat *bc = [BlockCoat new];
[bc test]; //在最外層的vc中測(cè)試,blockTest是否能正常dealloc
NSLog(@"-----lychee2----");
}
// BlockCoat.h
#import <Foundation/Foundation.h>
@class BlockTest;
@interface BlockCoat : NSObject
@property(nonatomic, strong) BlockTest *bt;
- (void)test;
@end
// BlockCoat.m
#import "BlockCoat.h"
@implementation BlockCoat
- (void)test {
int a = 10;
self.bt = [BlockTest new]; //self(blockCoat)持有了blockTest
__weak typeof (self) weakSelf = self;
[self.bt testBlock:^BOOL{ //參數(shù)block中,捕獲了self
NSLog(@"%@",self);
// NSLog(@"%@", weakSelf);
NSLog(@"%d",a);
return YES;
}];
}
@end
// BlockTest.h
#import <Foundation/Foundation.h>
typedef BOOL(^myBlock)(void);
@interface BlockTest : NSObject
@property(nonatomic, copy) myBlock b;
- (BOOL)testBlock:(myBlock)block;
@end
// BlockTest.m
#import "BlockTest.h"
@implementation BlockTest
- (BOOL)testBlock:(myBlock)block {
//還未進(jìn)行賦值copy,傳進(jìn)來(lái)的block有捕獲外部變量(self:BlockCoat),打印出來(lái)是stackBlock (不捕獲外部變量時(shí)是globalBlock)
NSLog(@"33---%@",block);
self.b = block;
//賦值給了copy修飾的b屬性,此時(shí)BlockTest持有了block,有捕獲外部變量,打印的是mallocBlock
NSLog(@"44---%@",self.b);
return block();
}
- (void)dealloc {
NSLog(@"dealloc~~");
}
@end
由于在BlockCoat.m中,持有了BlockTest,同時(shí)block捕獲了self(即BlockCoat);
下面的BlockTest.m中,持有了block,變?yōu)榱薽allocBlock;
此時(shí)就造成了循環(huán)引用:BlockCoat->BlockTest->block->BlockCoat
打破循環(huán)引用的方法?
在BlockCoat.m中,調(diào)用方法的block中,self換成weakSelf
什么時(shí)候不存在循環(huán)引用?(打破循環(huán)鏈中的任一環(huán)節(jié))
(1)BlockCoat中,不把BlockTest作為屬性賦值,用到的時(shí)候直接創(chuàng)建
(2)或者,BlockTest中,直接調(diào)用傳來(lái)的block,不要賦值給自己的屬性(持有)
(3)或者,block中,不捕獲self(BlockCoat)
當(dāng)不存在循環(huán)引用時(shí),就不需要寫(xiě)__weak,直接用self
從上面這個(gè)例子中聯(lián)想,如果BlockTest是系統(tǒng)框架中的類(lèi),那么在我們自定義的類(lèi)中,如果持有了系統(tǒng)類(lèi)對(duì)象,并且傳參的block中捕獲了self,就要特別注意測(cè)試是否存在循環(huán)引用。
如果能夠確定系統(tǒng)庫(kù)/三方庫(kù)的類(lèi)里,沒(méi)有對(duì)block進(jìn)行持有,例如masonry,只執(zhí)行了block方法,那么就不會(huì)產(chǎn)生循環(huán)引用。
- 例子3
self -> _source -> block -> self
@interface ViewController()
@property (nonatomic, strong) dispatch_source_t source;
@end
@implementation ViewController
- (void)viewDidLoad {
[self dispatch_source_block];
}
- (void)dispatch_source_block {
_source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, 0), 1, 3);
dispatch_block_t block = ^{
NSLog(@"%@",self);
NSLog(@"12321");
};
dispatch_source_set_event_handler(_source, block); //_source持有block
dispatch_activate(_source);
}
- (void)dealloc{
NSLog(@"dealloc----");
}
3. 循環(huán)引用檢測(cè)工具
instruments:Leaks
- 菜單欄 Xcode->Open developer tool->instruments
- 選擇Blank
- 點(diǎn)右上角“+”號(hào),下拉框中選Leaks
- 左上角選擇要運(yùn)行的應(yīng)用程序,點(diǎn)擊紅點(diǎn)開(kāi)始
有紅色?說(shuō)明檢測(cè)到內(nèi)存泄露,根據(jù)下面顯示的信息,具體排查問(wèn)題。
(這里只是簡(jiǎn)單使用,具體還待進(jìn)一步對(duì)工具進(jìn)行學(xué)習(xí)、總結(jié)。)

六、 __weak 打破循環(huán)引用
為什么__weak可以打破循環(huán)引用?
(1)weak的作用:把self加入sideTables的弱引用表中,不對(duì)它產(chǎn)生強(qiáng)引用,在使用的時(shí)候取出來(lái)。當(dāng)self的強(qiáng)引用計(jì)數(shù)變?yōu)?時(shí),將其從表中移除,并自動(dòng)置為nil
(2)mallocBlock捕獲__weak修飾的變量時(shí),捕獲進(jìn)內(nèi)部的也是用__weak修飾。實(shí)際是在block內(nèi)部另外定義了一個(gè)__weak修飾的局部變量,指向外部的變量。
//例如這么寫(xiě)
__weak typeof (self) weakSelf = self;
void(^block)(void) = ^{
weakSelf;
};
//在底層,實(shí)際上,block內(nèi)部新建了__weak修飾的局部變量,來(lái)指向weakSelf
__weak typeof (self) weakSelf = self;
void(^block)(void) = ^{
//即 __weak NSObject *a = weakSelf;
__weak typeof(weakSelf) a = weakSelf;
a;
};
【tips】經(jīng)常用到的宏定義弱引用:
//用weakState(weakVar, strongVar)來(lái)指代后面的__weak typeof(strongVar) weakVar = strongVar
#define weakState(weakVar, strongVar) __weak typeof(strongVar) weakVar = strongVar;
//__weak表示修飾詞,typeof()獲取原始變量的類(lèi)型,定義同類(lèi)型的新變量weakVar
//將原始變量strongVar,賦值給weakVar
__weak typeof(strongVar) weakVar = strongVar;
此時(shí),在需要弱引用的地方只需要寫(xiě):
weakState(weakSelf, self);
后文中,就可以拿weakSelf來(lái)用
七、 __strong
- 應(yīng)用場(chǎng)景:希望對(duì)象存活的時(shí)間久一點(diǎn)(持續(xù)到block結(jié)束),一般用在異步回調(diào)block中,防止執(zhí)行block時(shí),引用的對(duì)象已經(jīng)被釋放。OC中給nil發(fā)送消息是不響應(yīng)的。
- 以延時(shí)函數(shù)為例
(1)不存在循環(huán)引用的情況:
block中的self,延遲函數(shù)延遲2秒后才釋放
換成weakSelf,self將立即釋放,不會(huì)延遲2秒
- (void)viewDidLoad {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self tp]; //這里沒(méi)有循環(huán)引用,直接使用self即可
//如果使用了weakSelf,由于沒(méi)有強(qiáng)引用self,self釋放掉之后,weakSelf就指向了nil,不會(huì)執(zhí)行tp方法。
//[weakSelf tp];
NSLog(@"---延遲2秒");
});
}
- (void)tp{
NSLog(@"hahaha-----%@",self);
}
(2)存在循環(huán)引用,且block嵌套的情況
- 因?yàn)閟elf->myBlock->self,所以myBlock中用到self的地方要用weakSelf替代
- 由于延遲函數(shù),要執(zhí)行self的tp方法,就要求self要能夠延遲2秒后再釋放,這時(shí)就需要用到__strong。但是__strong是寫(xiě)在myBlock中呢?還是dispatch_after中呢?
先寫(xiě)答案,要寫(xiě)在myBlock中,然后在dispatch_after中捕獲外層的strongSelf。
- (void)viewDidLoad {
__weak typeof (self) weakSelf = self;
self.myBlock = ^(int a) {
__strong typeof(weakSelf) strongSelf = weakSelf; //正確寫(xiě)法
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// __strong typeof(weakSelf) strongSelf = weakSelf; //錯(cuò)誤寫(xiě)法
[strongSelf tp];
NSLog(@"---延遲2秒");
});
[weakSelf tp];
};
self.myBlock(0);
}
原因是:變量在block中是層層傳遞的。

而如果__strong寫(xiě)在dispatch_after的block中,則會(huì)變成:

【延伸】
后面這種寫(xiě)法,雖然無(wú)法“延長(zhǎng)變量存活時(shí)間”,但反其道行之,既然正常不存在循環(huán)引用的情況下,不會(huì)執(zhí)行對(duì)應(yīng)方法。那么如果方法有執(zhí)行,就說(shuō)明存在循環(huán)引用?
FBRetainCycleDetector + MLeaksFinder 閱讀中,指出MLeaksFinder關(guān)鍵源碼中,就有這樣的寫(xiě)法:
- (BOOL)willDealloc {
NSString *className = NSStringFromClass([self class]);
if ([[NSObject classNamesWhitelist] containsObject:className]) return NO;
NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);
if ([senderPtr isEqualToNumber:@((uintptr_t)self)]) return NO;
__weak id weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
__strong id strongSelf = weakSelf;
[strongSelf assertNotDealloc];
//此處,如果方法有響應(yīng),說(shuō)明對(duì)象沒(méi)有正常釋放,存在內(nèi)存泄露情況??!
});
return YES;
}
(flag: MLeaksFinder源碼之后抽空學(xué)習(xí)總結(jié)!)
以上~如有錯(cuò)誤還望不吝指出。