Block的分類
Block有三種類型:全局Block应结,堆區(qū)Block泉唁,棧區(qū)Block
全局Block
當(dāng)Block沒有引用到局部變量時(shí)或者Block里面使用的是全局變量,靜態(tài)變量時(shí)為全局Block
int a = 10;
void (^block)(void) = ^{
NSLog(@"hello world");
};
NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSGlobalBlock__: 0x104e580f8>
static int a1 = 20;
void (^block)(void) = ^{
NSLog(@"hello - %d",a1);
};
NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSGlobalBlock__: 0x100cec0f8>
堆區(qū)Block
當(dāng)Block里有引用到局部變量時(shí)為堆區(qū)block
int a = 10;
void (^block)(void) = ^{
NSLog(@"hello - %d",a);
};
NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSMallocBlock__: 0x281065950>
__block int a = 10;
void (^block)(void) = ^{
NSLog(@"hello - %d",a);
};
NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSMallocBlock__: 0x281aad5c0>
棧區(qū)Block
在Block名前面加個(gè)__weak就是棧區(qū)block
__block int a = 10;
static int a1 = 20;
void (^__weak block)(void) = ^{
NSLog(@"hello - %d",a);
NSLog(@"hello - %d",a1);
};
NSLog(@"block:%@", block);
輸出結(jié)果:block:<__NSStackBlock__: 0x16f7ccfb0>
堆Block和棧Block的區(qū)別
接下來看看這兩種block有什么區(qū)別呢扮休?
先看個(gè)示例:
__block int a = 10;
__block int b = 20;
NSLog(@"a:%p---b:%p", &a, &b);
void (^__weak block)(void) = ^{
NSLog(@"hello - %d---%p",a, &a);
a++;
};
void (^block1)(void) = ^{
NSLog(@"hello - %d---%p",b, &b);
b++;
};
block();
block1();
NSLog(@"block:%@---block1:%@", block, block1);
NSLog(@"a:%d---b:%d", a, b);
NSLog(@"a:%p---b:%p", &a, &b);
輸出結(jié)果:
a:0x16bb7cfe8---b:0x16bb7cfc8
hello - 10---0x16bb7cfe8
hello - 20---0x283e0b1b8
block:<__NSStackBlock__: 0x16bb7cf70>---block1:<__NSMallocBlock__: 0x28307d9b0>
a:11---b:21
a:0x16bb7cfe8---b:0x283e0b1b8
通過結(jié)果我們看到,首先block
的地址是在棧區(qū)拴鸵,而block1
的地址是在堆區(qū)宝踪,而棧block
引用的變量a
的地址并沒有變化,而堆block1
引用的變量b
的地址也相應(yīng)變成了堆區(qū)0x283e0b1b8
瘩燥,并且后面使用的b
的地址都是堆區(qū)上的。
總結(jié):棧block存放在棧區(qū)溶耘,對(duì)局部變量引用只拷貝局部變量的地址服鹅,而堆block存放在堆區(qū),并且直接將局部變量拷貝了一份到堆空間庐扫。
接下來我們?cè)賮砜磦€(gè)示例:
NSObject *objc = [NSObject new];
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));// 1
// block 底層源碼
// 捕獲 + 1
// 堆區(qū)block
// 棧 - 內(nèi)存 -> 堆 + 1
void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕獲 + 1 = 2
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
輸出結(jié)果:
<NSObject: 0x28292ff20>---1
<NSObject: 0x28292ff20>---3
<NSObject: 0x28292ff20>---4
<NSObject: 0x28292ff20>---5
奇怪為什么堆區(qū)block里面的對(duì)象引用計(jì)數(shù)加2呢形庭?而后面的mallocBlock只加1呢?
首先objc
在strongBlock
里面必然會(huì)拷貝一份到堆區(qū)萨醒,所以會(huì)加1,但是他是從當(dāng)前函數(shù)的棧區(qū)拷貝嗎囤踩?并不是晓褪,對(duì)于堆區(qū)Block
一開始編譯時(shí)是棧block
這時(shí)候objc
對(duì)象地址拷貝了一份引用計(jì)數(shù)加1,后面從棧block
變成堆block
怔锌,又拷貝了一份引用計(jì)數(shù)又加1变过,所以這時(shí)候是3
weakBlock
是棧block
僅拷貝了一份,所以引用計(jì)數(shù)加1岛杀,這時(shí)候是4
mallocBlock
從weakblock
拷貝了一份崭孤,所以引用計(jì)數(shù)再加1,這時(shí)候是5
相當(dāng)于strongBlock = weakblock + void(^mallocBlock)(void) = [weakBlock copy];
再來看個(gè)示例:
NSObject *a = [NSObject alloc];
NSLog(@"1---%@--%p", a, &a);
void(^__weak weakBlock)(void) = nil;
{
// 棧區(qū)
void(^__weak strongBlock)(void) = ^{
NSLog(@"2---%@--%p", a, &a);
};
weakBlock = strongBlock;
strongBlock();
NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
NSLog(@"4---%@--%p", a, &a);
輸出結(jié)果:
1---<NSObject: 0x2820337a0>--0x16bcf4fd8
2---<NSObject: 0x2820337a0>--0x16bcf4fc0
3 - <__NSStackBlock__: 0x16bcf4fa0> - <__NSStackBlock__: 0x16bcf4fa0>
2---(null)--0x16bcf4fc0
4---<NSObject: 0x2820337a0>--0x16bcf4fd8
當(dāng)前是棧區(qū)strongBlock
的賦值給外面的棧區(qū)weakBlock
遗锣,因?yàn)槎际谴娣旁跅嗤形?臻g的,只有當(dāng)前函數(shù)結(jié)束才會(huì)被銷毀笔咽,隨意這邊weakBlock
調(diào)用并不會(huì)有什么問題霹期。如果換成堆區(qū)block
就不一樣了
注意:這邊的a對(duì)象
在weakBlock()
調(diào)用時(shí)是nil,通過上面打印可以看出a對(duì)象
在進(jìn)入到strongblock里甩十,&a
拷貝了一份,拷貝的這一份地址指向的跟外面一樣溢十,但是當(dāng)strongblock
出了{(lán)},盡管strongblock
對(duì)象不再了达吞,但是其指向的內(nèi)存空間還在荒典,銷毀之前給了外面的weakBlock,同理a
也一樣覆糟,對(duì)象(此時(shí)a指向的內(nèi)容)不在了遮咖,但是內(nèi)存空間卻還在。
NSObject *a = [NSObject alloc];
// NSLog(@"1---%@--%p", a, &a);
void(^__weak weakBlock)(void) = nil;
{
// 堆區(qū)
void(^strongBlock)(void) = ^{
NSLog(@"2---%@--%p", a, &a);
};
weakBlock = strongBlock;
strongBlock();
NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
// NSLog(@"4---%@--%p", a, &a);
輸出結(jié)果:
2---<NSObject: 0x281218810>--0x281e7f6e0
3 - <__NSMallocBlock__: 0x281e7f6c0> - <__NSMallocBlock__: 0x281e7f6c0>
調(diào)用weakBlock時(shí)崩潰
為什么呢麦箍?因?yàn)樵趝}里面的堆區(qū)strongBlock
出了大括號(hào)就會(huì)被銷毀陶珠,此時(shí)你去調(diào)用這個(gè)block就會(huì)崩潰
注意:這邊weakBlock
為什么也是__NSMallocBlock__
,其實(shí)weakBlock
相當(dāng)于是指針诀蓉,此時(shí)指向的是一個(gè)堆上的內(nèi)存所以是__NSMallocBlock__
Block的循環(huán)引用
內(nèi)存泄漏一個(gè)主要原因就是block的循環(huán)引用暑脆。那么如何解決循環(huán)引用呢?
- (void)viewDidLoad {
[super viewDidLoad];
// 循環(huán)引用
self.name = @"hongfa";
self.block = ^{
NSLog(@"%@", self.name);
}
self.block();
}
這邊vc 引用了block 沥曹,block引用了vc根资,最后面造成了循環(huán)引用,那么如何解決呢部脚?
方案一:通過weak來解決
- (void)viewDidLoad {
[super viewDidLoad];
// 循環(huán)引用
self.name = @"hongfa";
__weak typeof(self) weakself = self;
self.block = ^{
NSLog(@"%@", weakself.name);
};
self.block();
}
直接使用weakself來代替self裤纹,weakself是弱引用丧没,這樣不會(huì)導(dǎo)致引用計(jì)數(shù)+1呕童。
這邊也有一個(gè)問題如下:
self.name = @"hongfa";
__weak typeof(self) weakself = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakself.name);
});
};
self.block();
如果當(dāng)我們延遲使用weakself的話淆珊,這時(shí)候的weakself可能已經(jīng)被銷毀了,這時(shí)候就需要用到__strong typeof(weakself) strongself = weakself;
self.name = @"hongfa";
__weak typeof(self) weakself = self;
self.block = ^{
__strong typeof(weakself) strongself = weakself;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongself.name);
});
};
self.block();
__strong
可以讓對(duì)象暫時(shí)在存活一段時(shí)間往声,用完就會(huì)銷毀戳吝,這樣也不會(huì)帶來內(nèi)存泄漏。
以上就是通過__weak
和__strong
來解決block
的循環(huán)引用慢洋。
方案二:通過臨時(shí)變量來解決
__block ViewController *vc = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();
定義一個(gè)跟self
一樣類型的vc = self
陆盘,然后block
引用vc
,用完之后再把vc=nil
斑芜,這樣引用鏈:self -> block -> vc -> self
這樣也可以解決循環(huán)引用問題
方案三:通過參數(shù)將self傳進(jìn)去祟霍,傳參的話,參數(shù)是在棧區(qū)醇王,函數(shù)運(yùn)行好棧區(qū)銷毀參數(shù)也就跟著銷毀崭添,所以也可以解決循環(huán)引用問題
// 通訊 參數(shù) block 通知
self.hfblock = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.hfblock(self);
以上就是目前掌握的三種解決循環(huán)引用的方案。
block的本質(zhì)
接下來看看block通過xcrun后究竟是什么棘伴?
int main(int argc, char * argv[]) {
int a = 9;
__block int b = 10;
NSObject *objc = [NSObject alloc];
void(^Block)(void) = ^{
NSLog(@"a:%d", a);
NSLog(@"b:%d", b);
NSLog(@"objc:%@", objc);
};
return 0;
}
xcrun -sdk iphonesimulator clang -rewrite-objc main.m
int main(int argc, char * argv[]) {
int a = 9;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 10};
NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc"));
void(*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, objc, (__Block_byref_b_0 *)&b, 570425344));
return 0;
}
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
NSObject *objc;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSObject *_objc, __Block_byref_b_0 *_b, int flags=0) : a(_a), objc(_objc), b(_b->__forwarding) { // 構(gòu)造函數(shù)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
xcrun后看到的__block b
焊夸,底層是變成了結(jié)構(gòu)體b
蓝角,里面保存了b的值10饭冬,整個(gè)block也變成了__main_block_impl_0
結(jié)構(gòu)體對(duì)象揪阶,把a, b, objc
作為參數(shù)傳進(jìn)去。而在block結(jié)構(gòu)體里定義了三個(gè)成員變量來保存a,b,objc
通過上面的例子我們就很清楚block的底層結(jié)構(gòu)以及他是如何對(duì)引用局部變量