block记录
1. block的分类
MRC下,block有三种 NSGlobalBlock(全局的), NSMallocBlock(堆区) ,NSStackBlock(栈区) ;在ARC下,系统实现对内存的管理,同时把NSStackBlock 默认优化成了 NSMallocBlock
如下,就是一个NSGlobalBlock
void(^block)(void) = ^{
NSLog(@"block");
};
block();
NSLog(@"%@",block); // <__NSGlobalBlock__: 0x1085ad038>
如下block引入了外部变量,就是一个堆block
int a = 10;
void(^block2)(void) = ^{
NSLog(@"%d",a);
NSLog(@"block2");
};
block2(); //引入了外部变量,堆block
NSLog(@"%@",block2);// <__NSMallocBlock__: 0x6000022b50b0> // Malloc 分配内存 的意思
上面的block2,如果在MRC下就是 NSStackBlock,ARC下系统把block从栈区移到了堆区;
ARC下,假如block2是NSStackBlock类型,如果我们把block2当作方法参数传递到了另外一个方法,那么一旦block2不在原来方法的调用栈,而新方法调用block2的时机又不得而知,可能在新方法调用的时候block2的时候堆区的obj已经被释放。即:ARC下编译器在NSStackBlock类型的block传递过程中进行了自动优化;
2. 采用__block修饰的原因
这里主要通过cpp的代码,了解一个变量,在使用__block 和不适用的情况下,block捕获这个变量时,有什么区别来了解内部的原因
1. 首先,创建一个block.c
文件,执行一个简单的block代码
#include "stdio.h"
int main(){
void (^block)(void) = ^{
printf("V15");
};
block();
return 0;
}
- 通过cpp编译成C++代码
clang -rewrite-objc block.c
Pasted Graphic 6.png
苹果的ObjectiveC官方文档中在“Working with Blocks”明确说明:
“ Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary. ”
可见, Block是Objective C语言中的对象
- 添加一个不加__block的变量
int a = 10;
void (^block)(void) = ^{
printf("V15");
printf(“%d”,a);
};
Pasted Graphic 8.png
block内部创建了一个变量,然后获取了a的值存储到了变量上,它只是使用了a的这个值,所以内部对这个a进行修改的情况下,和外部的变量a是完全没有关系的;
- 如果使__block修饰,得到的 cpp如下
__block int a = 10;
void (^block)(void) = ^{
printf("V15");
printf("%d",a);
};
Pasted Graphic 9.png
Pasted Graphic 10.png
传的是a的指针地址了,block中得到a的指针地址(指针地址的传递);__Block_byref_a_0 的意思就是由栈copy到堆中;block内操作的是堆的指针地址了
block不一定用copy,用strong修饰也是可以的 MRC下必须用copy ARC下,是都可以的
其他知识:
assign 与 weak, 它们都是弱应用声明类型, 最大的区别在哪呢?
weak 声明的变量对象释放后自动清空, 赋值为nil
assign声明的变量对象释放后不会自动赋值为nil, 会造成野指针错误!
3. block的具体使用
- 做自由变量:
void (^testBlock) (NSString *);
testBlock = ^(NSString *name) {
NSLog(@"-----%@",name);
};
testBlock(@"我的名字");
int (^numBlock)(int) = ^(int num){
return 10 * num;
};
int num_10 = numBlock(8);
NSLog(@"%d",num_10);
- 传值 和 传址 __block
__block int a = 10;// 传值 和 传址 __block
NSLog(@"定义前:%p", &a);//栈区
void (^block_2)(void) = ^{
NSLog(@"%d",a);
NSLog(@"block内部:%p", &a); //堆区
};
NSLog(@"定义后:%p", &a);
🔥block内有对a的调用,则 a会被放在堆区;没有对a的调用,则不会移动到堆区;
🔥和block代码块是否调用无关,因为编译期就会执行__Block_byref_a_0 *)&a,
// block_2();
打印:
定义前:0x7ffee136e728
定义后:0x6000000595d8
可变字符串传入block:
NSMutableString * a = [NSMutableString stringWithFormat:@"Tom"];
//NSMutableString * __block a = [NSMutableString stringWithFormat:@"Tom"];
🔥其实这里加__block,指针方面没有什么变化,但是可以解决系统因为没有识别到 __block而出现的报错,一般不会这样写,这里只是为了去更好的理解.
NSLog(@"\n 1定义前:------a指向的堆中地址:%p;a在栈中的指针地址:%p", a, &a);
void (^stringBlock)(void) = ^{
a.string = @"Jerry";
NSLog(@"\n 2定义后:------a指向的堆中地址:%p;a的指针地址:%p", a, &a);//打印后对比可以做到,其实a的指针已经在堆中了 0_0,这也是为什么可以j修改a的值的原因.
// a = [NSMutableString stringWithFormat:@"Cooci"];//会提示不可赋值,要加 __block
NSLog(@"a== %@",a);
NSLog(@"\n 3定义后:------a指向的堆中地址:%p;a的指针地址:%p", a, &a);
};
stringBlock();
Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。
block在执行的时候有可能自动变量已经被销毁了,那么此时如果再去访问被销毁的地址肯定会发生坏内存访问,
因此对于自动变量一定是值传递而不可能是指针传递了。而静态变量不会被销毁,所以完全可以传递地址。
而因为传递的是值得地址,所以在block调用之前修改地址中保存的值,block中的地址是不会变得。所以值会随之改变
转cpp文件,看的更透彻:
__block int a = 10;
void (^MyBlock)(void) = ^{
NSLog(@"%d",a);
};
MyBlock();
对应的cpp
cpp文件
***加了 __block的***🔥
__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 (*)())&__BlockViewController__viewDidLoad_block_impl_0((void *)__BlockViewController__viewDidLoad_block_func_0, &__BlockViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);
***没添加__block的***🔥
int a = 10;
void (*MyBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0((void *)__BlockViewController__viewDidLoad_block_func_0, &__BlockViewController__viewDidLoad_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)MyBlock)->FuncPtr)((__block_impl *)MyBlock);
void (*MyBlock)(void) --->没有参数没有返回值的函数指针 ,说明右侧会给到一个指针给 *MyBlock;🔥
其中__BlockViewController__viewDidLoad_block_impl_0是一个结构体🔥
struct __BlockViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __BlockViewController__viewDidLoad_block_desc_0* Desc;
int a;
*MyBlock应该就是获得这段代码的地址🔥
__BlockViewController__viewDidLoad_block_impl_0(void *fp, struct __BlockViewController__viewDidLoad_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 字符串的存储区(看到了就记录下,和block没关系)
NSString *c = @"1"; 🔥常量字符串,存储在常量区 __NSCFConstantString
NSLog(@"%@ : %p",c.class,c); //__NSCFConstantString : 0x106dec338
NSString *e = [NSString stringWithFormat:@"1"];
NSLog(@"%@ : %p",e.class,e);//NSTaggedPointerString : 0xdac8f48553b5a989
🔥NSTaggedPointerString 没有ISA指针的,它根本不是一个对象;NSTaggedPointerString根本不是对象,是分配在栈区的;
🔥具体可以查看 http://www.cocoachina.com/articles/13449
为什么字面量常量 c 苹果不使用NSTaggedPointerString呢 ? 而 e 却使用?
原因是常量字符串需要在跨系统上保持二进制兼容,而 tagged pointers在技术上并不能保证这个。因此对于这种短的字符串字面量还是使用__NSCFConstantString类型。 Tagged Pointer指针的值不再是地址了,而是真正的值;所以,实际上它不再是一个对象了,它只是一个披着对象皮的普通变量而已。所以,它的内存并不存储在堆中,也不需要malloc和free。
为什么会引入 Tagged Pointer ?
https://www.infoq.cn/article/deep-understanding-of-tagged-pointer/
NSString真的是很复杂的东西,可能分配在栈区、堆区、常量区,虽然日常中我们基本上可以无视这些区别,看似没有什么用,然而对自己来说,对做技术来说,多较些真,这样才能走的更远吧。
- "==" ,比较两个指针的值
- isEqualToString,比较两个字符串是否相同
- isEqual,判断是一个类方法,判断两个对象在类型和值上是否一样
- block的释放问题(待补充)