参考资料
为什么要将block从栈区copy到堆区
You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior. For more information, see Blocks Programming Topics.
从官方文档中可以看出,block复制到堆区的主要目的是为了保持block的状态,延长其生命周期。因为block如果在栈上的话,其所属的变量作用域结束,该block就被释放掉,block中的__block变量也同时被释放掉。为了解决栈块在其作用域结束之后被释放掉的问题,我们就需要把block复制到堆中。
详细分析参考:iOS进阶(一)block与property
C语言内存分配
C语言内存分为一下几个区:
- 栈区stack
存放函数的参数值、局部变量的值等,由编译器自动分配和释放,通常在函数结束的时候就释放了,其操作方式类似于数据结构中的栈。栈内存的分配运算内置于处理器的指令集,效率很高,但分配的内存容量有限。 - 堆区heap
就是通过new、malloc、realloc分配的内存块,它们的释放编译器不去管,需要我们的应用程序区释放。如果应用程序没有释放掉,系统会自动回收。分配类似于链表。 - 静态区(全局区)static
全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后,由系统释放。 - 代码区
存放函数体的二进制代码。 - 常量区
常量存储在这里,不允许修改的。
通过NSString的创建方式进一步理解内存分区
为什么NSString、NSDictionary、NSArray要使用copy修饰符呢?
对于NSString、NSDictionary、NSArray等经常使用copy关键字,是因为它们有对应的可变类型:NSMutableString、NSMutableDictionary、NSMutableArray,它们之间可能进行赋值操作,为确保对象中的字符串值不会无意间变动,应该在设置新属性时拷贝一份。
要搞清楚这个问题,我们先来弄明白深拷贝与浅拷贝的区别,以非集合类与集合类两种情况来进行说明下。
先看非集合类的情况,代码如下:
NSString *name = @"xiaoMing";
NSString *newName = [name copy];
NSLog(@"name memory address: %p newName memory address: %p", name, newName);
运行之后,输出的信息如下:
name memory address: 0x10159f758 newName memory address: 0x10159f758
可以看出复制过后,内存地址是一样的,没有发生变化,这就是浅复制,只是把指针地址复制了一份。
我们改下代码改成[name mutableCopy],此时日志的输出信息如下:
name memory address: 0x101b72758 newName memory address: 0x608000263240
我们看到内存地址发生了变化,并且newName的内存地址的偏移量比name的内存地址要大许多,由此可见经过mutableCopy操作之后,复制到堆区了,这就是深复制了,深复制就是内容也就进行了拷贝。
上面的都是不可变对象,在看下可变对象的情况,代码如下:
NSMutableString *name = [[NSMutableString alloc] initWithString:@"xiaoMing"];
NSMutableString *newName = [name copy];
NSLog(@"name memory address: %p newName memory address: %p", name, newName);
运行之后日志输出信息如下:
name memory address: 0x600000076e40 newName memory address: 0x6000000295e0
从上面可以看出copy之后,内存地址不一样,且都存储在堆区了,这是深复制,内容也就进行拷贝。
在把代码改成[name mutableCopy],此时日志的输出信息如下:
name memory address: 0x600000077380 newName memory address: 0x6000000776c0
可以看出可变对象copy与mutableCopy的效果是一样的,都是深拷贝。
总结:对于非集合类对象的copy操作如下:
[immutableObject copy]; //浅复制
[immutableObject mutableCopy]; //深复制
[mutableObject copy]; //深复制
[mutableObject mutableCopy]; //深复制
采用同样的方法可以验证集合类对象的copy操作如下:
[immutableObject copy]; //浅复制
[immutableObject mutableCopy]; //单层深复制
[mutableObject copy]; //深复制
[mutableObject mutableCopy]; //深复制