文章目录
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来解决
本文详细介绍了Objective-C中的Block,包括Block的本质、变量捕获、语法、三种类型(全局Block、栈Block、堆Block)以及它们的区别。还探讨了Block与对象的关系,展示了Block如何引发循环引用并提出了解决方案。通过对Block的理解,有助于更好地掌握Objective-C的内存管理和编程技巧。
1642

被折叠的 条评论
为什么被折叠?



