iOS开发之深浅拷贝

本文通过实例详细介绍了Objective-C中的深拷贝与浅拷贝概念,包括字符串、数组等对象的拷贝方式,并探讨了多层集合对象的深拷贝问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

      序言:

在学习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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值