文章目录
block
什么是block?
blocks是C语言的扩充功能。用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。
但是在C语言的标准中不允许存在匿名函数。通过Blocks,源代码中就可以使用匿名函数。
block的本质
blocks是C语言的扩充功能。用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。
但是在C语言的标准中不允许存在匿名函数。通过Blocks,源代码中就可以使用匿名函数。
- block本质上也是一个OC对象,它内部也有个
isa
指针 - block是封装了函数调用以及函数调用环境的OC对象
- block是封装函数及其上下文的OC对象
block的变量捕获
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
block语法
标准格式
例如:
^int (int count){return count + 1;}
返回值类型默认void
如果是void
我们也可以默认省略(void
)
Block变量
- Block变量类似于函数指针
- 声明Block类型变量仅仅是将声明函数指针类型变量的"
*
“变为”^
"
int (^blk)(int)
block变量与c语言变量完全相同,可以作为以下用途:
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
block的三种类型
block的类型,取决于isa
指针,可以通过调用class
方法或者isa
指针查看具体类型,最终都是继承自NSBlock
类型
__NSGlobalBlock __ ( _NSConcreteGlobalBlock )
对象存储在数据区__NSStackBlock __ ( _NSConcreteStackBlock )
对象存储在栈区__NSMallocBlock __ ( _NSConcreteMallocBlock )
对象存储在堆区
简单来说:
- 捕获了自动变量 就是Block就是栈类型
- 没有捕获就是数据区
- 不会有一创建就在堆区的,堆区的意义可以理解为和
autorelease
一样:延长作用域Stack
类型的Block进行了copy
操作之后变成了堆区 - 堆:动态分配内存,需要程序员自己申请,程序员自己管理
- 栈:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况
NSGlobalBlock
如果一个 block
没有访问外部局部变量,或者访问的是全局变量,或者静态局部变量,此时的 block
就是一个全局 block
,并且数据存储在全局区。
//block1没有引用到局部变量
int a = 10;
void (^block)(void) = ^{
NSLog(@"hello world");
};
NSLog(@"block:%@", block);
// block2中引入的是静态变量
static int a1 = 20;
void (^block)(void) = ^{
NSLog(@"hello - %d",a1);
};
NSLog(@"block:%@", block);
NSStackBlock
在Block
名前面加个__weak
就是栈区block
__block int a = 10;
static int a1 = 20;
void (^__weak block)(void) = ^{
NSLog(@"hello - %d",a);
NSLog(@"hello - %d",a1);
};
NSLog(@"block:%@", block);
运行结果:
NSMallocBlock
当Block
里有引用到局部变量时为堆区block
int a = 10;
void (^block1)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"block1:%@", block1);
__block int b = 10;
void (^block2)(void) = ^{
NSLog(@"%d",b);
};
NSLog(@"block2:%@", block2);
运行结果
block继承于NSObject
代码演示示例:
void (^block1)(void) = ^{
NSLog(@"block1");
};
NSLog(@"%@",[block1 class]);
NSLog(@"%@",[[block1 class] superclass]);
NSLog(@"%@",[[[block1 class] superclass] superclass]);
NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);
NSLog(@"%@",[[[[[block1 class] superclass] superclass] superclass] superclass]);
运行结果:
- 上述代码输出了
block1
的类型,也证实了block
是对象,最终继承NSObject
代码展示block的三种类型:
int age = 1;
void (^block1)(void) = ^{
NSLog(@"block1");
};
void (^block2)(void) = ^{
NSLog(@"block2:%d",age);
};
NSLog(@"%@/%@/%@",[block1 class],[block2 class],[^{
NSLog(@"block3:%d",age);
} class]);
输出结果:
堆Block和栈Block的区别
示例一:
__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);
输出结果:
- 通过结果我们看到,首先
block
的地址是在栈区,而block1
的地址是在堆区,而栈block
引用的变量a
的地址并没有变化,而堆block1
引用的变量b
的地址也相应变成了堆区0x283e0b1b8
,并且后面使用的b的地址都是堆区上的。
总结:栈block
存放在栈区,对局部变量引用只拷贝局部变量的地址,而堆block
存放在堆区,并且直接将局部变量拷贝了一份到堆空间。
示例二:
NSObject *objc = [NSObject new];
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));// 1
// block 底层源码
// 捕获 + 1
// 堆区block
// 栈 - 内存 -> 堆 + 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();
输出结果:
奇怪为什么堆区block
里面的对象引用计数加2呢?而后面的mallocBlock
只加1呢?
- 首先
objc
在strongBlock
里面必然会拷贝一份到堆区,所以会加1,但是他是从当前函数的栈区拷贝吗?并不是,对于堆区Block
一开始编译时是栈block
这时候objc
对象地址拷贝了一份引用计数加1,后面从栈block变成堆block,又拷贝了一份引用计数又加1,所以这时候是3,weakBlock
是栈block仅拷贝了一份,所以引用计数加1,这时候是4,mallocBlock
从weakblock
拷贝了一份,所以引用计数再加1,这时候是5,相当于strongBlock = weakblock + void(^mallocBlock)(void) = [weakBlock copy];
示例三:
NSObject *a = [NSObject alloc];
NSLog(@"1---%@--%p", a, &a);
void(^__weak weakBlock)(void) = nil;
{
// 栈区
void(^__weak strongBlock)(void) = ^{
NSLog(@"2---%@--%p", a, &a);
};
weakBlock = strongBlock;
strongBlock();
NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
NSLog(@"4---%@--%p", a, &a);
- 当前是栈区
strongBlock
的赋值给外面的栈区weakBlock
,因为都是存放在栈空间的,只有当前函数结束才会被销毁,随意这边weakBlock
调用并不会有什么问题。如果换成堆区block
就不一样了。
注意:这边的a对象在weakBlock()
调用时是nil
,通过上面打印可以看出a对象在进入到strongblock
里,&a
拷贝了一份,拷贝的这一份地址指向的跟外面一样,但是当strongblock
出了{},尽管strongblock
对象不再了,但是其指向的内存空间还在,销毁之前给了外面的weakBlock,同理a
也一样,对象(此时a指向的内容)不在了,但是内存空间却还在。
示例四:
NSObject *a = [NSObject alloc];
// NSLog(@"1---%@--%p", a, &a);
void(^__weak weakBlock)(void) = nil;
{
// 堆区
void(^strongBlock)(void) = ^{
NSLog(@"2---%@--%p", a, &a);
};
weakBlock = strongBlock;
strongBlock();
NSLog(@"3 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
// NSLog(@"4---%@--%p", a, &a);
- 为什么呢?因为在{}里面的堆区
strongBlock
出了大括号就会被销毁,此时你去调用这个block
就会崩溃
注意:这边weakBlock
为什么也是__NSMallocBlock__
,其实weakBlock
相当于是指针,此时指向的是一个堆上的内存所以是__NSMallocBlock__
block的循环引用
内存泄漏一个主要原因就是block的循环引用。那么如何解决循环引用呢?
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
self.name = @"hongfa";
self.block = ^{
NSLog(@"%@", self.name);
}
self.block();
}
这边vc 引用了block ,block引用了vc,最后面造成了循环引用,那么如何解决呢?
方案一:通过weak来解决