序言:
在学习iOS之深浅拷贝的时候,一定要明白OC的内存分布。下面以一个字符串为例:
我们可以看到temp1和temp2的地址是一样的。
我们再看看下面这一个例子:
当使用copy时,打印出str和copyString两者的地址是一样的。而mutabelCopyString的地址和前两者不一样,这就涉及到了深拷贝和潜拷贝。
OC
一、Copy和mutableCopy的介绍
点击跳转详细讲解
我们先看看下面的这个表格:
在理解copy和mutableCopy之前你首先要明白下面的打印代表什么意思:
对于打印的四个结果我们要知道什么意思,stirngContentAddress打印的是字符串@“123”在栈区的地址,AfterChangeStirngContentAddress 表示字符串改变成@“456”后在栈区的地址,两者前后肯定是不一样的,而stirngPointAddress 则代表你所定义的指针string的地址,改变字符串你只是改变了string指针的指向,而它本身的地址是不变的。
1、NSString的copy/mutableCopy
string和copyString的地址一样,但mutableString的却不一样。
2、MutableString的copy/mutableCopy
stirng和copyString、mutableString三者的地址都不一样
3、理解深复制(mutableCopy)
1)我们来看看第一种写法:
我们发现我们改变原数组dataArray2,也会影响深复制后的dataArray3,深复制到底是怎么回事呢?代码dataArray3=[dataArray2 mutableCopy];只是对数组dataArray2本身进行了内容拷贝,但是里面的字符串对象却没有进行内容拷贝,而是进行的浅复制,那么dataArray2和dataArray3里面的对象是共享同一份的。所以才会出现上面的情况。
2)我们看看这一种写法:
我们发现dataArray2不再受dataArray3的影响了,但是这种复制仍然又问题。
3)我们来看看这一种写法:
很容易发现dataArray2受dataArray3的影响,这是因为dataArray3=[[NSMutableArray alloc]initWithArray:dataArray2 copyItems:YES];仅仅能进行一层深复制,对于第二层或者更多层的就无效了。
4)我们看看最终写法:
我们可以看到这里dataArray2不再受dataArray3的影响了。结论:要想对多层集合对象进行复制,我们需要进行完全复制,这里可以使用归档和解档。
4)类(NSObject)的拷贝
这里我们建立一个Person类:
在.h中我们为Person类定一个年龄
.m中建立一个Person类对象,给年龄赋值
然后运行一下:
出现打印崩溃现象,错误信息是-[Person copyWithZone:]: unrecognized selector sent to instance 0x6080000122e0
是因为copy时没有找到person对象,这里提到了一个方法copyWithZone,接着我们实现在Person类的.m中copyWithZone方法
接着我们打印copy的perons对象
直接打印出我们在.m中返回的字符串了。
如果在这里我们打印copy出来的对象的年龄
看看会出现什么结果:
这里程序崩溃,查看错误日志是因为没有找到age属性,所以需要我么你在copyWithZone中对Person对象age进行赋值处理。
然后我们打印结果:
这样就打印正常了。
#import "Person.h"
@interface Person ()<NSCopying>
@end
@implementation Person
- (id)copyWithZone:(NSZone *)zone {
Person* person=[[[self class]allocWithZone:zone]init];
person.name=self.name;
return person;
}
@end
-(void)testCopy {
Person *person1 = [[Person alloc] init];
Person *person2 = [person1 copy];
NSLog(@"person1p=%p",person1);
NSLog(@"person2p=%p",person2);
}
person1p=0x600000004620
person2p=0x600000004600
person1和person2的地址不一样
swift
在 Swift 中,所有的基本类型,包括整数、浮点数、字符串、数组和字典等都是值类型,并且都以结构体的形式实现。那么,我们在写代码时,这些值类型每次赋值传递都是会重新在内存里拷贝一份吗?
答案是否定的,在 Swift 中,Copy-on-Write(写时复制)是一种优化技术,用于在需要进行修改时避免不必要的数据复制。它主要用于值类型(value types),如结构体(struct)和枚举(enum)。
1.基本数据类型
从下面的打印信息中我们可以看到,对于String、Int等基本类型的数据进行赋值时就发生了拷贝操作。
代码示例:
func testSwiftCopy1() -> Void {
var str1 = "1111"
var str2 = str1
print("str1前=\(str1)")
print("str2前=\(str2)")
printPointer(ptr: &str1)
printPointer(ptr: &str2)
str2.append("2222")
print("str1后=\(str1)")
print("str2后=\(str2)")
printPointer(ptr: &str1)
printPointer(ptr: &str2)
var num1 = 5
var num2 = num1
print("num1前=\(num1)")
print("num2前=\(num2)")
printPointer(ptr: &num1)
printPointer(ptr: &num2)
}
打印结果:
str2前=1111
0x000000016fcff870
0x000000016fcff860
str1后=1111
str2后=11112222
0x000000016fcff870
0x000000016fcff860
num1前=5
num2前=5
0x000000016fcff7d8
0x000000016fcff7d0
2.集合类型
对于集合类型,如下面的array3和array4,我们可以看到在对写入操作前,赋值操作并未发生拷贝操作;在对array3进行修改(即写入)后,arr3的地址发生变化,也就是说此时发生了拷贝操作。
代码示例:
func testSwiftCopy() -> Void {
var array3 = ["lihua", "liming"]
var array4 = array3
print("array3前=\(array3)")
print("array4前=\(array4)")
printPointer(ptr: &array3)
printPointer(ptr: &array4)
array3.append("xiaowang")
print("array3后=\(array3)")
print("array4后=\(array4)")
printPointer(ptr: &array3)
printPointer(ptr: &array4)
}
func printPointer<T>(ptr: UnsafePointer<T>) {
print(ptr)
}
//打印内容:
array3前=["lihua", "liming"]
array4前=["lihua", "liming"]
0x00006000017574e0
0x00006000017574e0
array3后=["lihua", "liming", "xiaowang"]
array4后=["lihua", "liming"]
0x0000600002620500
0x00006000017574e0
3.自定义的结构体
我们知道 Swift 中的 COW 适用于值类型数据,而且通常是集合类型的。那么对于自定义的结构体是否默认也存在这种机制呢?
struct Student {
var name:Int
}
func testSwiftStructCopy() -> Void {
var obj1 = Student(name: 10)
var obj2 = obj1
print("obj1前=\(obj1.name)")
print("obj2前=\(obj2.name)")
printPointer(ptr: &obj1)
printPointer(ptr: &obj2)
obj1.name = 20
print("obj1后=\(obj1.name)")
print("obj2后=\(obj2.name)")
printPointer(ptr: &obj1)
printPointer(ptr: &obj2)
}
打印:
obj1前=10
obj2前=10
0x000000016b6b3878
0x000000016b6b3870
obj1后=20
obj2后=10
0x000000016b6b3878
0x000000016b6b3870
从上面的打印可以看出,对于自定义的结构体,并不支持COW。
自定义结构体COW的实现
在 Swift 的GitHub官方文档OptimizationTips.rst中有这样一段代码:
需要注意的是class Ref必须使用final修饰,有以下几个原因:
- **避免类的继承:**将 Ref 类定义为 final 可以防止其他类继承它。由于 Ref 类是用于支持 Box 结构体的内部实现,而不是作为可继承的基类,因此将其定义为 final 可以确保它不会被错误地继承和扩展。
- 保持引用计数的一致性:Ref 类使用 isKnownUniquelyReferenced(_😃 函数来检查引用计数,以确定是否需要进行复制。如果 Ref 类是可继承的,其他子类可能会引入对引用计数的修改,导致 isKnownUniquelyReferenced(_😃 函数的结果不准确。通过将 Ref 类定义为 final,可以确保引用计数的一致性,从而正确地实现 Copy-on-Write 的逻辑。
final class Ref<T> {
var val: T
init(_ v: T) { val = v }
}
struct Box<T> {
var ref: Ref<T>
init(_ x: T) { ref = Ref(x) }
var value: T {
get { return ref.val }
set {
if !isKnownUniquelyReferenced(&ref) {
ref = Ref(newValue)
return
}
ref.val = newValue
}
}
}
struct Student {
var name:Int
}
func testSwiftStructCopy2() -> Void {
let test = Student(name: 10)
var box1 = Box(test)
var box2 = box1
address(of: &box1.ref.val)
address(of: &box2.ref.val)
box2.value.name = 30
address(of: &box1.ref.val)
address(of: &box2.ref.val)
}
打印:
0x600000238430
0x600000238430
0x600000238430
0x6000002318d0