Copy 拷贝

前言

  • copy:需要先实现 NSCopying 协议,创建的是不可变副本。

  • mutableCopy:需要实现 NSMutableCopying 协议,创建的是可变副本。

  • 浅拷贝:指针拷贝,源对象和副本指向的是同一个对象。对象的引用计数器 +1,其实相当于做了一次 retain 操作。

  • 深拷贝:内容拷贝,源对象和副本指向的是两个不同的对象。源对象引用计数器不变,副本计数器设置为 1。

  • 只有不可变对象创建不可变副本(copy)才是浅拷贝,其它都是深拷贝。

  • 在 iOS 中并不是所有的对象都支持 copy,mutableCopy,遵守 NSCopying 协议的类可以发送 copy 消息,遵守 NSMutableCopying 协议的类才可以发送 mutableCopy 消息。假如发送了一个没有遵守上诉两协议而发送 copy 或者 mutableCopy,那么就会发生异常。但是默认的 iOS 类并没有遵守这两个协议。如果想自定义一下 copy 那么就必须遵守 NSCopying,并且实现 copyWithZone: 方法,如果想自定义一下 mutableCopy 那么就必须遵守 NSMutableCopying,并且实现 mutableCopyWithZone: 方法。

  • copy 是创建一个新对象,retain 是创建一个指针,引用对象计数加 1。copy 属性表示两个对象内容相同,新的对象 retain 为 1 ,与旧有对象的引用计数无关,旧有对象没有变化。copy 减少对象对上下文的依赖。retain 属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的 retain 值 +1 也就是说,retain 是指针拷贝,copy 是内容拷贝。

1、系统的非容器类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/*
系统的非容器类对象指的是 NSString,NSNumber 等等一类的对象。

对于系统的非容器类对象,如果对一不可变对象复制,copy 是指针复制(浅拷贝),mutableCopy 就是对象复制(深拷贝)。
如果是对可变对象复制,都是深拷贝,但是 copy 返回的对象是不可变的。
*/

NSString *string = @"origion";

NSString *stringCopy = [string copy];
NSMutableString *mstringMCopy = [string mutableCopy];

NSLog(@"%p", string);
NSLog(@"%p", stringCopy); // 地址与 string 相同
NSLog(@"%p", mstringMCopy); // 地址与 string 不同

NSLog(@"%zi", [string retainCount]); // 引用计数为 2
NSLog(@"%zi", [stringCopy retainCount]); // 引用计数为 2
NSLog(@"%zi", [mstringMCopy retainCount]); // 引用计数为 1

NSLog(@"%@", stringCopy); // 内容与 string 相同
NSLog(@"%@", mstringMCopy); // 内容与 string 相同

[mstringMCopy appendString:@"!!"]; // mstringMCopy 是可变的

NSLog(@"%@", mstringMCopy);

/*
string 和 stringCopy 指向的是同一块内存区域(又叫 apple 弱引用 weak reference),此时 stringCopy 的
引用计数和 string 的一样都为2。而 mstringMCopy 则是我们所说的真正意义上的复制,系统为其分配了新内存,但指针
所指向的字符串还是和 string 所指的一样。
*/

NSMutableString *mstring = [NSMutableString stringWithString: @"origion"];

NSString *stringCopy = [mstring copy];
NSMutableString *mStringCopy = [mstring copy];

NSMutableString *mstringMCopy = [mstring mutableCopy];

NSLog(@"%p", mstring);
NSLog(@"%p", stringCopy); // 地址与 string 不同
NSLog(@"%p", mStringCopy); // 地址与 string 不同,与 stringCopy 相同
NSLog(@"%p", mstringMCopy); // 地址与 string 不同

NSLog(@"%@", mstring);
NSLog(@"%@", stringCopy); // 内容与 string 相同
NSLog(@"%@", mStringCopy); // 内容与 string 相同
NSLog(@"%@", mstringMCopy); // 内容与 string 相同

[mstring appendString:@" origion!"];

// [mStringCopy appendString:@"mm"]; // error,mStringCopy 不可变

[mstringMCopy appendString:@"!!"]; // mstringMCopy 可变

NSLog(@"%@", mstring);
NSLog(@"%@", mstringMCopy);

/*
以上四个 NSString 对象所分配的内存都是不一样的。但是对于 mStringCopy 其实是个不可变对象,所以上述会报错。
*/

2、系统的容器类对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
/*
系统的容器类对象指 NSArray,NSDictionary 等。

对于容器类本身,上面讨论的结论也是适用的,需要探讨的是复制后容器内对象的变化。

对于系统的容器类对象,copy 返回不可变对象,是指针复制,包括里面的元素也是指向相同的指针。mutablecopy
返回可变对象,是对象复制,其中容器内的元素内容都是指针复制,可以改变其内的元素。
*/

NSArray *array = [NSArray arrayWithObjects:@"a", @"b", @"c", nil];

NSArray *arrayCopy = [array copy];

NSMutableArray *mArrayMCopy = [array mutableCopy];

NSLog(@"%p", array);
NSLog(@"%p", arrayCopy); // 地址与 array 相同
NSLog(@"%p", mArrayMCopy); // 地址与 array 不同

NSLog(@"%zi",[array retainCount]);
NSLog(@"%zi",[arrayCopy retainCount]);
NSLog(@"%zi",[mArrayMCopy retainCount]);

NSLog(@"%@", array);
NSLog(@"%@", arrayCopy); // 内容与 array 相同
NSLog(@"%@", mArrayMCopy); // 内容与 array 相同

[mArrayMCopy addObject:@"de"]; // mArrayMCopy 可变

NSLog(@"%@", array);
NSLog(@"%@", mArrayMCopy);

[mArrayMCopy removeObjectAtIndex:0];

NSLog(@"%@", array);
NSLog(@"%@", mArrayMCopy);

/*
copy 返回不可变对象,mutablecopy 返回可变对象,arrayCopy 是指针复制,而 mArrayMCopy 是对象复制。
arrayCopy 是和 array 同一个 NSArray 对象(指向相同的对象),包括 array 里面的元素也是指向相同的指针。
mArrayMCopy 是 array 的可变副本,指向的对象和 array 不同,但是其中的元素和 array 中的元素指向的是同一
个对象。mArrayMCopy 还可以改变其内的元素:删除或添加。但是注意的是,容器内的元素内容都是指针复制。
*/

NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"], @"b", @"c", nil];

NSArray *arrayCopy = [array copy];

NSMutableArray *mArrayMCopy = [array mutableCopy];

NSLog(@"%p", array);
NSLog(@"%p", arrayCopy); // 地址与 array 相同
NSLog(@"%p", mArrayMCopy); // 地址与 array 不同

NSLog(@"%zi",[array retainCount]);
NSLog(@"%zi",[arrayCopy retainCount]);
NSLog(@"%zi",[mArrayMCopy retainCount]);

NSLog(@"%@", array);
NSLog(@"%@", arrayCopy); // 内容与 array 相同
NSLog(@"%@", mArrayMCopy); // 内容与 array 相同

NSMutableString *testString = [array objectAtIndex:0];
[testString setString:@"1a1"]; // 这样会改变 testString 的指针,其实是将 @“1a1” 临时对象
赋给了 testString。这样以上三个数组的首元素都被改变了。

NSLog(@"%@", array);
NSLog(@"%@", arrayCopy); // 内容与 array 相同
NSLog(@"%@", mArrayMCopy); // 内容与 array 相同

/*
arrayCopy,mArrayMCopy 和 array 指向的都是不一样的对象,但是其中的元素都是一样的对象,同一个指针。
对于容器而言,其元素对象始终是指针复制。如果需要元素对象也是对象复制,就需要实现深拷贝。
*/

NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"first"], @"b", @"c", nil];

NSArray *deepCopyArray = [[NSArray alloc] initWithArray:array copyItems: YES];

// 使用归档/反归档拷贝
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: array]];

NSLog(@"%@", array);
NSLog(@"%@", deepCopyArray); // 内容与 array 相同
NSLog(@"%@", trueDeepCopyArray); // 内容与 array 相同

NSMutableString *testString1 = [array objectAtIndex:0];
[testString1 setString:@"1a1"];

NSLog(@"%@", array);
NSLog(@"%@", deepCopyArray); // 内容与 array 不同
NSLog(@"%@", trueDeepCopyArray); // 内容与 array 不同

/*
trueDeepCopyArray 是完全意义上的深拷贝,而 deepCopyArray 则不是,对于 deepCopyArray 内的不可变元素其还是
指针复制。或者我们自己实现深拷贝的方法。因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要
指针复制即可。除非你对容器内的元素重新赋值,否则指针复制即已足够。举个例子,[[array objectAtIndex:0] appendstring:@”sd”]
后其他的容器内对象并不会受影响。[[array objectAtIndex:1] 和 [[deepCopyArray objectAtIndex:1] 尽管是指向同一块内存,
但是我们没有办法对其进行修改——因为它是不可改变的。所以指针复制已经足够。所以这并不是完全意义上的深拷贝,但是 apple 的官方
文档将其列为 deep copy 了,并添加了 copy 和 mutablity 的关系说明。
*/

3、自定义对象

  • 需要自己要实现 NSCopying,NSMutableCopying 这样就能调用 copy 和 mutablecopy 了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    // Teacher.h

    // 遵守 NSCopying, NSMutableCopying 协议
    @interface Teacher : NSObject <NSCopying, NSMutableCopying>

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

    @end

    // Teacher.m

    @implementation Teacher

    // 实现协议方法,委托方会自动调用协议中的该方法
    - (id)copyWithZone:(NSZone *)zone{

    Teacher *copy = [[Teacher allocWithZone:zone] init];

    copy.name = self.name;
    copy.age = self.age;

    return copy;
    }

    // 实现协议方法,委托方会自动调用协议中的该方法
    - (id)mutableCopyWithZone:(NSZone *)zone{

    Teacher *copy = [[Teacher allocWithZone:zone] init];

    copy.name = self.name;
    copy.age = self.age;

    return copy;
    }

    - (NSString *)description{

    return [NSString stringWithFormat:@"%@ %i", self.name, self.age];
    }

    @end

    // main.m

    #import "Teacher.h"

    Teacher *tch = [[Teacher alloc] init];
    tch.name = @"xiao xin";
    tch.age = 28;

    // 内容复制
    Teacher *tchCpy = [tch copy];

    // 内容复制
    Teacher *tchMCopy = [tch mutableCopy];

    tchCpy.age = 20;
    tchMCopy.age = 22;

    NSLog(@"%p", tch);

    // tchCpy 与 tch 的地址不同
    NSLog(@"%p", tchCpy);

    // tchMCopy 与 tch 的地址不同
    NSLog(@"%p", tchMCopy);

    NSLog(@"%@", tch);

    // tchCpy 与 tch 的实例变量的值不同
    NSLog(@"%@", tchCpy);

    // tchMCopy 与 tch 的实例变量的值不同
    NSLog(@"%@", tchMCopy);

    // Children.h

    // 遵守 NSCopying, NSMutableCopying 协议
    @interface Children : NSObject <NSCopying, NSMutableCopying>

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

    @end

    // Children.m

    @implementation Children

    // 实现协议方法,委托方会自动调用协议中的该方法。
    - (id)copyWithZone:(NSZone *)zone{

    // 若有类继承此类,并且此方法在子类中可用
    id copy = [[[self class] allocWithZone:zone] init];

    [copy setName:self.name];

    [copy setAge:self.age];

    return copy;
    }

    // 实现协议方法,委托方会自动调用协议中的该方法。
    - (id)mutableCopyWithZone:(NSZone *)zone{

    // 若有类继承此类,并且此方法在子类中可用
    id copy = [[[self class] allocWithZone:zone] init];

    [copy setName:self.name];

    [copy setAge:self.age];

    return copy;
    }

    @end

    // GoodChildren.h

    @interface GoodChildren : Children

    @property (nonatomic, assign)int score;

    @end

    // GoodChildren.m

    @implementation GoodChildren

    // 实现协议方法,委托方会自动调用协议中的该方法。
    - (id)copyWithZone:(NSZone *)zone{

    GoodChildren *copy = [super copyWithZone:zone];

    copy.score = self.score;

    return copy;
    }

    // 实现协议方法,委托方会自动调用协议中的该方法。
    - (id)mutableCopyWithZone:(NSZone *)zone{

    GoodChildren *copy = [super copyWithZone:zone];

    copy.score = self.score;

    return copy;
    }

    - (NSString *)description{

    return [NSString stringWithFormat:@"%@ %i %d", self.name, self.age, self.score];
    }

    @end

    // main.m

    #import "GoodChildren.h"

    GoodChildren *gChild = [[GoodChildren alloc] init];
    gChild.name = @"xiao xin";
    gChild.age = 18;
    gChild.score = 88;

    // 内容复制
    GoodChildren *gChildCopy = [gChild copy];

    // 内容复制
    GoodChildren *gChildMCopy = [gChild mutableCopy];

    gChildCopy.age = 20;
    gChildMCopy.age = 22;

    gChildCopy.score = 100;
    gChildMCopy.score = 120;

    NSLog(@"%p", gChild);

    // gChildCopy 与 gChild 的地址不同
    NSLog(@"%p", gChildCopy);

    // gChildMCopy 与 gChild 的地址不同
    NSLog(@"%p", gChildMCopy);

    NSLog(@"%@", gChild);

    // gChildCopy 与 gChild 的实例变量的值不同
    NSLog(@"%@", gChildCopy);

    // gChildMCopy 与 gChild 的实例变量的值不同
    NSLog(@"%@", gChildMCopy);
文章目录
  1. 1. 前言
  2. 2. 1、系统的非容器类对象
  3. 3. 2、系统的容器类对象
  4. 4. 3、自定义对象
隐藏目录