关于iOS中objc深复制与浅复制你该知道的

时间:2022-08-23 12:07:19

什么是浅复制(浅拷贝)与深复制(深拷贝)?

浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用变量不同,即名称不同。对某中任何一个对象的改动都会影响另一个对象。
深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另一个对象造成影响。

浅拷贝与深拷贝的区别?


1、浅拷贝是指将对象中的数值类型的字段拷贝到新对象中,而对象中的引用型字段则只复制它的一个引用到上标对象。如果改变目标对象中的引用型字段的值则将反映在原对象中,也就是说原始对象中对象的字段也会发生变化。
2、深拷贝与浅拷贝不同的是对于引用的处理,深拷贝将会在新对象中创建一个新的和原对象中对应字段内容相同的字段,也就是说这个引用对象与原有对象引用是不同的,在改变新对象中这个字段的进修是不会影响到原始对象中对应字段的内容。


OC中浅拷贝与深拷贝的区别?


浅拷贝
1、复制时,源对象在复制时引用计数器+1,相当于做一个retain操作,但没有产生新的对象
2、复制后,源对象和副本对象是同一个对象。


深拷贝
1、复制时,源对象引用计数不变,副本对象引用计数为1,产生了新的对象
2、复制后,源对象和副本对象是两个不同的对象。

retain、copy和MutableCopy的区别

1、retain:始终是浅复制。引用计数每次加一。返回对象是否可变与被复制的对象保持一致。
2、copy:对于可变对象为深复制,引用计数不改变;对于不可变对象是浅复制, 引用计数每次加一。始终返回一个不可变对象。  
3、mutableCopy:始终是深复制,引用计数不改变。始终返回一个可变对象。

容器类与非容器类

1、非容器类,NSString NSInteger NSNumber NSArray, NSSet, NSDictionary等,即不包含子类型的数据类型。
2、容器类,就是自定义的组合类,即有多个数据类型的组合而成。


非容器类,基本数据类型的拷贝是浅拷贝

对于那些实现了NSCopying,NSMutableCopying协议的基本类型,如NSString、NSNumber、NSDictionary、NSArray、NSSet等,这些
在执行copy mutableCopy时是浅拷贝。

    // 数字
    NSNumber *n1 = @(1);
    NSNumber *n2 = [n1 copy];
    NSLog(@"n1-%p, n2-%p", n1, n2);
    
    // 字符串
    NSString *s1 = @"s1";
    NSString *s2   = [s1 copy];
    NSLog(@"s1-%p, s2-%p", s1, s2);
    
    // 数组
    NSArray *a1 = @[@"1", @"2", @"3"];
    NSArray *a2 = [a1 copy];
    NSLog(@"a1-%p, a2-%p", a1, a2);
    
    // 字典
    NSDictionary *d1 = @{@"k1":@"v1", @"k2":@"v2"};
    NSDictionary *d2 = [d1 copy];
    NSLog(@"d1-%p, d2-%p", d1, d2);
    
    // 集合
    NSSet *t1 = [[NSSet alloc]initWithArray:@[@"1", @"2"]];
    NSSet *t2 = [t1 copy];
    NSLog(@"t1-%p, t2-%p", t1, t2);



执行结果为, 
n1-0xb000000000000012, n2-0xb000000000000012
s1-0x1073ed098, s2-0x1073ed098
a1-0x618000052a20, a2-0x618000052a20
d1-0x610000075d00, d2-0x610000075d00
t1-0x618000052990, t2-0x618000052990


自定义类型,即容器类型的深浅拷贝

1、只包含非容器类型的容器类

自定义一个学生,只有一个NSString的name属性时,我们仅以实现NSCopy协议为例

student.h

/**
 * @file      Student.h
 * @brief     学生实体类
 * @author    ruglcc
 * @version   1.0
 * @date      2017/03/29
 */

#import <Foundation/Foundation.h>

@interface Student : NSObject <NSCopying>

/// 姓名
@property (nonatomic, strong) NSString *name;


- (instancetype)initWithName:(NSString *)name;

@end


student.m

/**
 * @file      Student.m
 * @brief     学生实体类
 * @author    ruglcc
 * @version   1.0
 * @date      2017/03/29
 */

#import "Student.h"

@implementation Student

- (instancetype)initWithName:(NSString *)name
{
    self = [super init];
    if(self)
    {
        _name = name;
    }
    
    return self;
}

- (id)copyWithZone:(nullable NSZone *)zone
{
    Student *student = [[self class]allocWithZone:zone];
    
    // 浅拷贝
    //student.name = _name;

    // 深拷贝
    student.name = [_name copy];

    
    return student;
}
@end

测试函数

  // 学生小李
    Student *xiaoLi = [[Student alloc]initWithName:@"小李"];
    
    // 通过小李克隆一个小王
    Student *xiaoWang = [xiaoLi copy];
    
    NSLog(@"xiaoLi:      %p, xiaoWang     : %p", xiaoLi, xiaoWang);
    NSLog(@"xiaoLi.name: %p, xiaoWang.name: %p", xiaoLi.name, xiaoWang.name);
    


测试结果

2017-03-29 15:16:54.276 CopyDemo[59192:14737328] xiaoLi:0x60000001ed60, xiaoWang : 0x60000001ee80
2017-03-29 15:16:54.276 CopyDemo[59192:14737328] xiaoLi.name: 0x10875a218, xiaoWang.name: 0x10875a218


2、 嵌套类型的浅拷贝

现在手机人手一部,学生也不例外了,我们向学生类中添加一个新属性:手机。手机类简单定义为:

phone.h

/**
 * @file      Phone.h
 * @brief     手机实体类
 * @author    ruglcc
 * @version   1.0
 * @date      2017/03/29
 */

#import <Foundation/Foundation.h>

@interface Phone : NSObject<NSCopying>

/// 手机品牌或型号
@property (nonatomic, strong) NSString *name;


/// 电话号码
@property (nonatomic, strong) NSString *number;


- (instancetype)initWithName:(NSString *)name
                      number:(NSString *)number;


@end


phone.m

/**
 * @file      Phone.m
 * @brief     手机类实现
 * @author    ruglcc
 * @version   1.0
 * @date      2017/03/29
 */

#import "Phone.h"

@implementation Phone


- (instancetype)initWithName:(NSString *)name
                      number:(NSString *)number
{
    self = [super init];
    if(self)
    {
        _name = name;
        _number = number;
    }
    
    return self;
}


- (id)copyWithZone:(nullable NSZone *)zone
{
    Phone *phone = [[self class]allocWithZone:zone];
    
    // 对于基本数据类型,下面两种方式是一样的, 因为基本数据类型执行copy也是浅拷贝。

    // 1、浅拷贝
    //phone.name   = _name;
    //phone.number = _number;
    
    // 2、深拷贝
    phone.name   = [_name copy];
    phone.number = [_number copy];

    
    return phone;
}
@end


添加手机属性后的学生类

student.h

#import "Phone.h"

@interface Student : NSObject <NSCopying>

/// 姓名
@property (nonatomic, strong) NSString *name;

@property (nonatomic, strong) Phone *phone;

- (instancetype)initWithName:(NSString *)name phone:(Phone*)phone;

@end

student.m

#import "Student.h"

@implementation Student

- (instancetype)initWithName:(NSString *)name phone:(Phone*)phone
{
    self = [super init];
    if(self)
    {
        _phone = phone;
        _name = name;
    }
    
    return self;
}

- (id)copyWithZone:(nullable NSZone *)zone
{
    Student *student = [[self class]allocWithZone:zone];
    
    // 浅拷贝
    student.name = _name;
    student.phone  = _phone;
    
    return student;
}
@end

测试函数

    
    // 定义一个手机
    Phone *phoneXl = [[Phone alloc]initWithName:@"iphone7" number:@"13888888888"];
    
    // 学生小李
    Student *xiaoLi = [[Student alloc]initWithName:@"小李" phone:phoneXl];
    
    // 通过小李克隆一个小王
    Student *xiaoWang = [xiaoLi copy];
    
    NSLog(@"xiaoLi:      %p, xiaoWang     : %p", xiaoLi, xiaoWang);
    NSLog(@"xiaoLi.name: %p, xiaoWang.name: %p", xiaoLi.name, xiaoWang.name);
    NSLog(@"xiaoLi.phone: %p, xiaoWang.phone: %p", xiaoLi.phone, xiaoWang.phone);
 
    xiaoWang.phone.name = @"荣誉7";
    
    NSLog(@"xiaoLi.phone %@", xiaoLi.phone.name);

测试结果


CopyDemo[59477:14770601] xiaoLi:      0x61800003c3a0, xiaoWang     : 0x61800003c500
CopyDemo[59477:14770601] xiaoLi.name: 0x10c7dd258, xiaoWang.name: 0x10c7dd258
CopyDemo[59477:14770601] xiaoLi.phone: 0x61800003c5e0, xiaoWang.phone: 0x61800003c5e0
CopyDemo[59477:14770601] xiaoLi.phone 荣誉7

结论:

1、源对象与拷贝对象的地址不同,说明浅拷贝生成了新的容器对象。

2、源对象与拷贝对象的子自定义对象的地址相同,说明浅拷贝虽然生成了新的容器对象,但是它们包含的内容是同一个对象。

3、修改拷贝对象的子自定义对象的值,对应的源对象的属性值也改变了,也进一步说明,浅拷贝时,子自定义对象没有产生新对象。


3、嵌套类型的深拷贝

同样对于上例,将学生类中 - (id)copyWithZone:(nullable NSZone *)zone 方法实现改为 深拷贝方式

#import "Student.h"

@implementation Student

- (instancetype)initWithName:(NSString *)name phone:(Phone*)phone
{
    self = [super init];
    if(self)
    {
        _phone = phone;
        _name = name;
    }
    
    return self;
}

- (id)copyWithZone:(nullable NSZone *)zone
{
    Student *student = [[self class]allocWithZone:zone];
    
    // 浅拷贝
   // student.name = _name;
    //student.phone  = _phone;

    // 深拷贝
    student.name = [_name copy];
    student.phone = [_phone copy];
    
    return student;
}
@end


同样的测试函数

    
    // 定义一个手机
    Phone *phoneXl = [[Phone alloc]initWithName:@"iphone7" number:@"13888888888"];
    
    // 学生小李
    Student *xiaoLi = [[Student alloc]initWithName:@"小李" phone:phoneXl];
    
    // 通过小李克隆一个小王
    Student *xiaoWang = [xiaoLi copy];
    
    NSLog(@"xiaoLi:      %p, xiaoWang     : %p", xiaoLi, xiaoWang);
    NSLog(@"xiaoLi.name: %p, xiaoWang.name: %p", xiaoLi.name, xiaoWang.name);
    NSLog(@"xiaoLi.phone: %p, xiaoWang.phone: %p", xiaoLi.phone, xiaoWang.phone);
 
    xiaoWang.phone.name = @"荣誉7";
    
    NSLog(@"xiaoLi.phone %@", xiaoLi.phone.name);

测试结果


2017-03-29 15:45:53.052 CopyDemo[59549:14775946] xiaoLi:      0x60000002da80, xiaoWang     : 0x600000031340
2017-03-29 15:45:53.054 CopyDemo[59549:14775946] xiaoLi.name: 0x10459a258, xiaoWang.name: 0x10459a258
2017-03-29 15:45:53.054 CopyDemo[59549:14775946] xiaoLi.phone: 0x600000030c00, xiaoWang.phone: 0x600000030680
2017-03-29 15:45:53.055 CopyDemo[59549:14775946] xiaoLi.phone iphone7

结论:

1、源对象与拷贝对象的地址不同,说明自定义对象引用本身深拷贝。

2、源对象与拷贝对象中的子属性的地址都不同了,说明深拷贝是完全不同的两个对象了

3、修改拷贝对象中自定义对象中的一个子属性,源对象对应的属性没有变。说明深拷贝是生成了两个不同的对象。