iOS开发——深拷贝与浅拷贝详解

深拷贝和浅拷贝这个问题在面试中常常被问到,而在实际开发中,只要稍有不慎,就会在这里出现问题。尤其对于初学者来说,我们有必要来好好研究下这个概念。我会以实际代码来演示,相关示例代码上传至 这里

首先通过一句话来解释:深拷贝就是内容拷贝,浅拷贝就是指针拷贝。

深拷贝就是拷贝出和原来仅仅是值一样,但是内存地址完全不一样的新的对象,创建后和原对象没有任何关系。浅拷贝就是拷贝指向原来对象的指针,使原对象的引用计数+1,可以理解为创建了一个指向原对象的新指针而已,并没有创建一个全新的对象。

(1)非容器类对象的深拷贝、浅拷贝

 // 字符串是直接赋值的,右侧如果是copy,那么就是浅拷贝;右侧如果是mutableCopy,那么就是深拷贝。
    NSString *string1 = @"helloworld";
    NSString *string2 = [string1 copy]; // 浅拷贝
    NSString *string3 = [string1 mutableCopy]; // 深拷贝
    NSMutableString *string4 = [string1 copy]; // 浅拷贝
    NSMutableString *string5 = [string1 mutableCopy]; // 深拷贝

    NSLog(@"string1 = %d;string2 = %d",string1,string2);
    NSLog(@"string1 = %d;string3 = %d",string1,string3);
    NSLog(@"string1 = %d;string4 = %d",string1,string4);
    NSLog(@"string1 = %d;string5 = %d",string1,string5);

打印结果如下:

我这里用%d格式化打印出字符串对象的内存地址。我这里主要通过内存地址来判断是深拷贝还是浅拷贝,在该案例中,无论是深拷贝还是浅拷贝,字符串对象的值都是一样的,所以暂且就不打印值了,而只打印地址。这里的原字符串是直接赋值的,可以发现,右侧如果使用copy,那么打印出的内存地址是一样的,表示是浅拷贝,只拷贝出了指向原对象的指针,没有创建出新的对象。如果右侧使用mutableCopy,那么打印出的不一样的内存地址,表示创建了一个新的对象,是深拷贝。

简单说明一下什么是非容器类对象,像NSString,NSNumber这些不能包含其他对象的叫做非容器类。如NSArray和NSDictionary可以容纳其他对象的叫做容器类对象。

做一个小小的总结:

-- 浅拷贝类似retain,引用计数对象+1.创建一个指针;

-- 深拷贝是真正意义上的拷贝,是创建一个新对象。copy属性表示两个对象内容相同,新的对象retain为1,与原对象的引用计数无关,原对象没有改变。copy减少对象对上下文的依赖。

-- retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1.也就是说,retain是指针拷贝,copy是内容拷贝。

(2)改变字符串的创建方式,使用stringWithFormat创建字符串,而不是直接赋值

 // 结果同上。右侧如果是copy,那么就是浅拷贝;右侧如果是mutableCopy,那么就是深拷贝。
    NSString *string1 = [NSString stringWithFormat:@"helloworld"];
    NSString *string2 = [string1 copy]; // 浅拷贝
    NSString *string3 = [string1 mutableCopy]; // 深拷贝
    NSMutableString *string4 = [string1 copy]; // 浅拷贝
    NSMutableString *string5 = [string1 mutableCopy]; // 深拷贝

    NSLog(@"string1 = %d;string2 = %d",string1,string2);
    NSLog(@"string1 = %d;string3 = %d",string1,string3);
    NSLog(@"string1 = %d;string4 = %d",string1,string4);
    NSLog(@"string1 = %d;string5 = %d",string1,string5);

打印结果如下:

结果同(1)。因为都是不可变字符串,创建方式并不影响拷贝方式。所以可以得出结论,对于字符串这类非容器类对象,copy是浅拷贝,mutable是深拷贝。

(3)以上测试的都是不可变字符串,这里来尝试下可变字符串

 // 如果是一个MutableString,那么无论是copy,mutableCopy,都会创建一个新对象。
    NSMutableString *string1 = [NSMutableString stringWithString:@"helloworld"];
    NSString *string2 = [string1 copy]; // 深拷贝
    NSString *string3 = [string1 mutableCopy]; // 深拷贝
    NSMutableString *string4 = [string1 copy]; // 深拷贝
    NSMutableString *string5 = [string1 mutableCopy]; // 深拷贝

    NSLog(@"string1 = %d;string2 = %d",string1,string2);
    NSLog(@"string1 = %d;string3 = %d",string1,string3);
    NSLog(@"string1 = %d;string4 = %d",string1,string4);
    NSLog(@"string1 = %d;string5 = %d",string1,string5);

打印结果如下:

可以看到,对于MutableString,右侧无论是copy还是mutableCopy,都会创建一个新对象,都是属于深拷贝。

现在我们对以上代码做一点修改:

 // 如果是一个MutableString,那么无论是copy,mutableCopy,都会创建一个新对象。
    NSMutableString *string1 = [NSMutableString stringWithString:@"helloworld"];
    NSString *string2 = [string1 copy]; // 深拷贝
    NSString *string3 = [string1 mutableCopy]; // 深拷贝
    NSMutableString *string4 = [string1 copy]; // 深拷贝
    NSMutableString *string5 = [string1 mutableCopy]; // 深拷贝

    NSLog(@"string1 = %d;string2 = %d",string1,string2);
    NSLog(@"string1 = %d;string3 = %d",string1,string3);
    NSLog(@"string1 = %d;string4 = %d",string1,string4);
    NSLog(@"string1 = %d;string5 = %d",string1,string5);

    // 增加以下代码
    [string4 appendString:@"MMMMMM"];
    [string5 appendString:@"11111"];

    NSLog(@"string4 = %@",string4);
    NSLog(@"string5 = %@",string5);

我增加了四行代码,我想要检验一下对一个可变字符串执行copy和mutableCopy后,返回的字符串是否可变。同时为了检验在哪一行出现crash,我提前打一个异常断点。对于NSMutableString有appendString方法,而NSString没有appendString方法,所以这种检验应该是没有问题的。运行代码后,会在[string4 apendString:@“MMMMM”];这一行出现崩溃。经过实际测试,可得出以下结论:

-- 对于可变对象的复制,都是深拷贝;

-- 可变对象copy后返回的对象是不可变的,mutableCopy后返回的对象是可变的。

(4)我们上面提到的都是系统类,如果是我们自定义的类,复制结果又是怎样呢?我下面定义一个Person类,实现如下:

Person.h

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) NSInteger age;

@end

Person.m

#import "Person.h"

@interface Person()<NSCopying, NSMutableCopying>

@end

@implementation Person

// 对应copy方法
- (id)copyWithZone:(NSZone *)zone
{
    Person *person = [[Person allocWithZone:zone] init];
    person.name = self.name; // 这里的self就是被copy的对象
    person.age = self.age;
    return person;
}

- (id)mutableCopyWithZone:(NSZone *)zone
{
    Person *person = [[Person allocWithZone:zone] init];
    person.name = self.name;
    person.age = self.age;
    return person;
}

@end

首先声明一下为什么要实现上述的copyWithZone和mutableCopyWithZone方法?其实在iOS中并不是所有的对象都支持copy、mutableCopy方法,只有遵守NSCopying协议的类可以发送copy消息,遵守NSMutableCopying协议的类才可以发送mutableCopy消息,否则就会崩溃。我们可以非常方便的使用系统类中的copy,mutableCopy方法,是因为这些系统类本身就实现了NSCopying,NSMutableCopy协议,大家可以进入接口文档进行查看。

然后编写测试代码如下:

// Person类必须实现copyWithZone和mutableCopyWithZone方法。
    Person *person = [[Person alloc] init];
    person.name = @"Jack";
    person.age = 23;

    Person *copyPerson = [person copy]; // 深拷贝
    Person *mutableCopyPerson = [person mutableCopy]; // 深拷贝
    NSLog(@"person = %d;copyPerson = %d",person,copyPerson);
    NSLog(@"person = %d;mutableCopyPerson = %d",person,mutableCopyPerson);

打印结果如下:

可以看到无论是用copy还是mutableCopy,复制后都创建了一个新的对象。为什么呢?因为我们本来就在copyWithZone: ,mutableCopyWithZone方法中创建了一个新对象啊,所以一点都不奇怪。

(5)从上述(4)中可以看到我的Person自定义对象在copy后是创建了一个全新的对象,那么这个对象中的属性呢?这些属性是深拷贝还是浅拷贝呢?

 NSMutableString *otherName = [[NSMutableString alloc] initWithString:@"Jack"];
    Person *person = [[Person alloc] init];
    person.name = otherName;
    person.age = 23;

    [otherName appendString:@" and Mary"];
    NSLog(@"person.name = %@",person.name);

打印结果如下:

打印的结果是"Jack",而不是”Jack and Mary“. 说明在执行person.name = otherName;的时候是重新创建了一个字符串。但是请大家注意,这里我name的属性修饰符是copy,如果把copy改成strong会变成怎么样呢?

对,没错,name改为strong后答案就是”Jack and Mary“。所以在这里我们可以得出以下小小的结论:

-- 因为这里的otherName是可变的,person.name的属性是copy,所以创建了新的字符串,属于深拷贝;

-- 如果person.name设置为strong,那么就是浅拷贝。因为strong会持有原来的对象,使原来的对象的引用计数+1,其实就是指针拷贝。

-- 当然,这里用strong还是copy,取决于实际需求。

(6)与上面(5)类似,我现在修改代码如下:

NSString *otherName = @"jack";
    Person *person = [[Person alloc] init];
    person.name = otherName;
    person.age = 23;

    NSLog(@"othername = %d;person.name = %d",otherName,person.name);

其实这里很好理解,无论person.name是strong还是copy,都是指针拷贝。指向的都是otherName的内存地址。

现修改代码如下:

 NSString *otherName = @"jack";
    Person *person = [[Person alloc] init];
    person.name = [otherName copy];
    person.age = 23;

    NSLog(@"othername = %d;person.name = %d",otherName,person.name);

同样的,无论person.name是strong还是copy,都是指针拷贝。指向的都是otherName的内存地址。因为对不可变对象执行copy一般都是浅拷贝,这没问题。

再次修改代码如下:

 NSString *otherName = @"jack";
    Person *person = [[Person alloc] init];
    person.name = [otherName mutableCopy];
    person.age = 23;

    NSLog(@"othername = %d;person.name = %d",otherName,person.name);

无论person.name是strong还是copy,都是内容拷贝。创建一个全新的对象。因为对不可变对象执行mutableCopy一般都是深拷贝,也没问题。

(7)讲了这么多非容器对象,最后我们来讲讲容器对象Array。先讲不可变的容器对象

 NSArray *array01 = [NSArray arrayWithObjects:@"a",@"b",@"c", nil];
    NSArray *copyArray01 = [array01 copy];
    NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];
    NSLog(@"array01 = %d,copyArray01 = %d",array01,copyArray01);
    NSLog(@"array01 = %d,mutableCopyArray01 = %d",array01,mutableCopyArray01);

    NSLog(@"array01[0] = %d,array01[1] = %d,array01[2] = %d",array01[0],array01[1],array01[2]);
    NSLog(@"copyArray01[0] = %d,copyArray01[1] = %d,copyArray01[2] = %d",copyArray01[0],copyArray01[1],copyArray01[2]);
    NSLog(@"mutableCopyArray01[0] = %d,mutableCopyArray01[1] = %d,mutableCopyArray01[2] = %d",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);

打印结果如下:

我们可以得出以下结论:

-- copyArray01和array01指向的是同一个对象,包括里面的元素也是指向相同的指针。

-- mutableCopyArray01和array01指向的是不同的对象,但是里面的元素指向相同的对象,mutableCopyArray01可以修改自己的对象。

-- copyArray01是对array01的指针复制(浅复制),而mutableCopyArray01是内容复制。

----------------------------------------------------------------------------------------------------------------------------------------------------------------------打个分割线

上面的是不可变容器NSArray,这里测试下可变容器NSMutableArray:

NSMutableArray *array01 = [NSMutableArray arrayWithObjects:@"a",@"b",@"c", nil];
    NSArray *copyArray01 = [array01 copy];
    NSMutableArray *mutableCopyArray01 = [array01 mutableCopy];
    NSLog(@"array01 = %d,copyArray01 = %d",array01,copyArray01);
    NSLog(@"array01 = %d,mutableCopyArray01 = %d",array01,mutableCopyArray01);

    NSLog(@"array01[0] = %d,array01[1] = %d,array01[2] = %d",array01[0],array01[1],array01[2]);
    NSLog(@"copyArray01[0] = %d,copyArray01[1] = %d,copyArray01[2] = %d",copyArray01[0],copyArray01[1],copyArray01[2]);
    NSLog(@"mutableCopyArray01[0] = %d,mutableCopyArray01[1] = %d,mutableCopyArray01[2] = %d",mutableCopyArray01[0],mutableCopyArray01[1],mutableCopyArray01[2]);

打印的结果如下:

可得结论如下:

-- 容器对象和非容器对象类似,可变对象复制(copy,mutableCopy)的都是一个新对象;不可变对象copy是浅复制,mutableCopy是深复制。

-- 对于容器而言,元素对象始终是指针复制。

以上涉及的也只是一部分,想要对深拷贝、浅拷贝有更为深入的了解,必须首先要对内存管理较为熟悉,然后在实际开发中不断去实践,才会随心所欲。

时间: 2024-08-24 09:41:35

iOS开发——深拷贝与浅拷贝详解的相关文章

IOS中复制对象的用法及深拷贝和浅拷贝详解

亲爱的网友,我这里有套课程想和大家分享,如果对这个课程有兴趣的,可以加我的QQ2059055336和我联系. 课程内容简介 我们软件是基于移动设备的.所以我们必然的选择了安卓作为我们的开发工具.课程中,我们将简要的介绍Android的基本概念,然后进行我们的实战开发.在开发中,大家讲学习到基本的组件,适配UI,数据的存储,多线程下载,开机广播,闹钟提醒,短信发送等实际项目开发中碰到的有用的知识点.通过课程学习,让大家能够掌握Android软件开发的流程,注意点,及优化.帮助大家迅速的掌握Andr

【转】 c++拷贝构造函数(深拷贝,浅拷贝)详解

c++拷贝构造函数(深拷贝,浅拷贝)详解 2013-11-05 20:30:29 分类: C/C++ 原文地址:http://blog.chinaunix.net/uid-28977986-id-3977861.html 一.什么是拷贝构造函数      首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=100; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.  下面看一个类对象拷贝的简单例子. #include<iostream

iOS开发摇动手势实现详解

1.当设备摇动时,系统会算出加速计的值,并告知是否发生了摇动手势.系统只会运动开始和结束时通知你,并不会在运动发生的整个过程中始终向你报告每一次运动.例如,你快速摇动设备三次,那只会收到一个摇动事件. 2,想要实现摇动手势,首先需要使视图控制器成为第一响应者,注意不是单独的控件.成为第一响应者最恰当的时机是在视图出现的时候,而在视图消失的时候释放第一响应者. ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 -(BOOL)canBecomeFirstRespond

iOS 开发之照片框架详解之二 —— PhotoKit 详解(下)

这里接着前文<iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)>,主要是干货环节,列举了如何基于 PhotoKit 与 AlAssetLibrary 封装出通用的方法. 三. 常用方法的封装 虽然 PhotoKit 的功能强大很多,但基于兼容 iOS 8.0 以下版本的考虑,暂时可能仍无法抛弃 ALAssetLibrary,这时候一个比较好的方案是基于 ALAssetLibrary 和 PhotoKit 封装出一系列模拟系统 Asset 类的自定义类,然后在其中封装好兼容 A

iOS 开发之照片框架详解之二 —— PhotoKit 详解(上)

一. 概况 本文接着 iOS 开发之照片框架详解,侧重介绍在前文中简单介绍过的 PhotoKit 及其与 ALAssetLibrary 的差异,以及如何基于 PhotoKit 与 AlAssetLibrary 封装出通用的方法. 这里引用一下前文中对 PhotoKit 基本构成的介绍: PHAsset: 代表照片库中的一个资源,跟 ALAsset 类似,通过 PHAsset 可以获取和保存资源 PHFetchOptions: 获取资源时的参数,可以传 nil,即使用系统默认值 PHAssetCo

**Python中的深拷贝和浅拷贝详解

Python中的深拷贝和浅拷贝详解 这篇文章主要介绍了Python中的深拷贝和浅拷贝详解,本文讲解了变量-对象-引用.可变对象-不可变对象.拷贝等内容. 要说清楚Python中的深浅拷贝,需要搞清楚下面一系列概念: 变量-引用-对象(可变对象,不可变对象)-切片-拷贝(浅拷贝,深拷贝) [变量-对象-引用] 在Python中一切都是对象,比如说:3, 3.14, 'Hello', [1,2,3,4],{'a':1}...... 甚至连type其本身都是对象,type对象 Python中变量与C/

【iOS开发必收藏】详解iOS应用程序内使用IAP/StoreKit付费、沙盒(SandBox)测试、创建测试账号流程!【2012-12-11日更新获取”产品付费数量等于0的问题”】

转的别人的 看到很多童鞋问到,为什么每次都返回数量等于0?? 其实有童鞋已经找到原因了,原因是你在 ItunesConnect 里的 “Contracts, Tax, and Banking”没有完成设置账户信息. 确定 ItunesConnect 里 “Contracts, Tax, and Banking”的状态,如下图所示,即可: 这里也是由于Himi疏忽的原因没有说明,这里先给童鞋们带来的麻烦,致以歉意. //——2012-6-25日更新iap恢复 看到很多童鞋说让Himi讲解如何恢复i

iOS开发之手势gesture详解(二)

与其他用户界面控件交互 UIControl子类会覆盖parentView的gesture.例如当用户点击UIButton时,UIButton会接受触摸事件,它的parentView不会接收到.这仅适用于手势识别重叠的默认动作的控制,其中包括: 一根手指单击动作:UIButton, UISwitch, UIStepper, UISegmentedControl, and UIPageControl. 一根手指擦碰动作:UISlider 一根手指拖动动作:UISwitch 包含多点触摸的事件 在iO

iOS开发之手势gesture详解(一)

前言 在iOS中,你可以使用系统内置的手势识别(GestureRecognizer),也可以创建自己的手势.GestureRecognizer将低级别的转换为高级别的执行行为,是你绑定到view的对象,当发生手势,绑定到的view对象会响应,它确定这个动作是否对应一个特定的手势(swipe,pinch,pan,rotation).如果它能识别这个手势,那么就会向绑定它的view发送消息,如下图 UIKit框架提供了一些预定义的GestureRecognizer.包含下列手势 UITapGestu