在开始讲解复制对象之前先讲解下 赋值。
举个例子:
// person 和 child 表示带有两个字符串实例变量 name 和 sex 的 XLPerson对象。
person = child;
这样赋值的结果仅仅是将对象 child 的地址复制到 person 中。在赋值操作结束时,两个变量都指向内存中的同一个地址。使用一条消息对实例变量进行修改,如:
[child setName:@"小明" sex:@"男"];
因为 person 和 child 引用内存中的同一个对象,所以对应的 name 和 sex都会修改。
在 Fundation 对象中,将一个变量赋值给另一个对象,仅仅创建另一个对这个对象的引用。例如:dataArray 和 dataArray2 都是 NSMutableArray 对象。执行如下操作:
dataArray2 = dataArray;
[dataArray removeObjectAtIndex: 0];
将从 dataArray 和 dataArray2 两个变量引用的同一个数组中删除第一个元素。
copy 和 mutableCopy
Fundation 类实现名为 copy 和 mutableCopy 的方法,可以使用这些方法创建对象的副本。通过实现一个符合 <NSCopying> 协议的方法完成此任务。如果类必须区分产生对象的是可变副本还是不可变副本,还需要根据 <NSMutableCopying> 协议实现一个方法。
分析可变和不可变的字符串、字典、数组 内存地址。
字符串 - 不可变
NSString * str = @"字符串";
id strCopy = [str copy];
id strMutableCopy = [str mutableCopy];
# lldb 命令 打印地址
(lldb) p str
(__NSCFConstantString *) $0 = 0x000000010235c3c0 @"字符串"
(lldb) p strCopy
(__NSCFConstantString *) $1 = 0x000000010235c3c0 @"字符串"
(lldb) p strMutableCopy
(__NSCFString *) $2 = 0x000060000096fbd0 @"字符串"
不可变字符串 copy 后地址不变,mutableCopy后地址修改。
str = @"修改字符串";
(lldb) p str
(__NSCFConstantString *) $3 = 0x000000010235c3e0 @"修改字符串"
(lldb) p strCopy
(__NSCFConstantString *) $4 = 0x000000010235c3c0 @"字符串"
(lldb) p strMutableCopy
(__NSCFString *) $5 = 0x000060000096fbd0 @"字符串"
不可变字符串内容更新后, copy 和 mutableCopy 的内存地址不变。
注:str = @"修改字符串"; 相当于重新初始化,分配内存。
字符串 - 可变
NSMutableString *mStr = [[NSMutableString alloc] initWithString:@"可变字符串"];
id mStrCopy = [mStr copy];
id mStrMutableCopy = [mStr mutableCopy];
(lldb) p mStr
(__NSCFString *) $0 = 0x00006000003b1b30 @"可变字符串"
(lldb) p mStrCopy
(__NSCFString *) $1 = 0x00006000003b1b60 @"可变字符串"
(lldb) p mStrMutableCopy
(__NSCFString *) $2 = 0x00006000003b1b90 @"可变字符串"
可变字符串 copy 和mutableCopy后内存地址更新。
[mStr setString:@"修改可变数组"];
(lldb) p mStr
(__NSCFString *) $3 = 0x00006000003b1b30 @"修改可变数组"
(lldb) p mStrCopy
(__NSCFString *) $4 = 0x00006000003b1b60 @"可变字符串"
(lldb) p mStrMutableCopy
(__NSCFString *) $5 = 0x00006000003b1b90 @"可变字符串"
可变字符串内容更新后, copy 和 mutableCopy 的地址不变。
字符串拷贝结论如下:
| NSString | NSMutableString | |
|---|---|---|
| copy | 浅拷贝 | 深拷贝 |
| mutableCopy | 深拷贝 | 深拷贝 |
字典、数组
// 不可变字典
NSDictionary *dic = @{
@"name": @"x",
@"age": @(22)
};
id dicCopy = [dic copy];
id dicMCopy = [dic mutableCopy];
// 可变字典
NSMutableDictionary *mDic = [NSMutableDictionary dictionaryWithDictionary:dic];
id mDicCopy = [mDic copy];
id mDicMCopy = [mDic mutableCopy];
// 不可变数组
NSArray *array = [NSArray arrayWithObject:@"qaz"];
id arrayCopy = [array copy];
id arrayMCopy = [array mutableCopy];
// 可变数组
NSMutableArray *mArray = [NSMutableArray arrayWithArray:array];
id mArrayCopy = [mArray copy];
id mArrayMCopy = [mArray mutableCopy];
(lldb) p dic
(NSConstantDictionary *) $0 = 0x00000001001017a8 2 key/value pairs
(lldb) p dicCopy
(NSConstantDictionary *) $1 = 0x00000001001017a8 2 key/value pairs
(lldb) p dicMCopy
(__NSDictionaryM *) $2 = 0x00006000015f9240 2 key/value pairs
(lldb) p dic[@"name"]
(__NSCFConstantString *) $3 = 0x00000001000fc398 @"x"
(lldb) p dicCopy[@"name"]
(__NSCFConstantString *) $4 = 0x00000001000fc398 @"x"
(lldb) p dicMCopy[@"name"]
(__NSCFConstantString *) $5 = 0x00000001000fc398 @"x"
(lldb) p mDic
(__NSDictionaryM *) $6 = 0x00006000015f92a0 2 key/value pairs
(lldb) p mDicCopy
(__NSFrozenDictionaryM *) $7 = 0x00006000015f9340
(lldb) p mDicMCopy
(__NSDictionaryM *) $8 = 0x00006000015f9360 2 key/value pairs
(lldb) p mDic[@"name"]
(__NSCFConstantString *) $9 = 0x00000001000fc398 @"x"
(lldb) p mDicCopy[@"name"]
(__NSCFConstantString *) $10 = 0x00000001000fc398 @"x"
(lldb) p mDicMCopy[@"name"]
(__NSCFConstantString *) $11 = 0x00000001000fc398 @"x"
(__NSSingleObjectArrayI *) $0 = 0x0000600000c2c060 @"1 element"
(lldb) p arrayCopy
(__NSSingleObjectArrayI *) $1 = 0x0000600000c2c060 @"1 element"
(lldb) p arrayMCopy
(__NSArrayM *) $2 = 0x0000600000078b70 @"1 element"
(lldb) p mArray
(__NSArrayM *) $3 = 0x0000600000078ba0 @"1 element"
(lldb) p mArrayCopy
(__NSSingleObjectArrayI *) $4 = 0x0000600000c2c090 @"1 element"
(lldb) p mArrayMCopy
(__NSArrayM *) $5 = 0x0000600000078bd0 @"1 element"
(lldb) p array[0]
(__NSCFConstantString *) $6 = 0x0000000104cd4378 @"qaz"
(lldb) p arrayMCopy[0]
(__NSCFConstantString *) $7 = 0x0000000104cd4378 @"qaz"
(lldb) p mArray[0]
(__NSCFConstantString *) $8 = 0x0000000104cd4378 @"qaz"
字符串、字典、数组拷贝结论如下:
1、对应深浅拷贝
| 不可变(NSString,NSDictionary,NSArray) | 可变(NSMutableString,NSMutableDictionary,NSMutableArray) | |
|---|---|---|
| copy | 浅拷贝 | 深拷贝 |
| mutableCopy | 深拷贝 | 深拷贝 |
2、 字典、数组拷贝
-
浅拷贝,变量引用内存下的同一对象,当对象属性变更时,变量都会更新。 -
深拷贝,变量深拷贝后在内存中创建了不同的对象,但对象内部属性,引用地址相同。当属性赋值修改时,仅对当前对象有效;当属性为可变时,如果通过可变属性修改内容,所有变量都会更新;替换属性变量,则只会对当前变量有效。
注:可以试验下嵌套数组 的深浅copy,内部数组为可变数组和不可变数组时对应的内存地址,和属性更新时的变化。
自定义对象
@property声明中 copy 修饰
strong只是指针引用,修饰的属性指向赋值对象地址,引用计数加1。故:当NSMutableString的字符串赋值给strong修饰的字符串时,NSMutableString的字符串变了,它也会改变。copy修饰的属性赋值时会重新申请一份内存并赋予相同的内容,两个指针指向不同的内存地址,且该属性为不可变对象。
@interface XLPerson : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, copy) NSString *fullName;
@property (nonatomic, copy) NSArray *arrayC;
@property (nonatomic, strong) NSArray *arrayS;
@end
XLPerson *per = [XLPerson new];
NSMutableString *name = [NSMutableString stringWithString:@"明"];
per.name = name;
[name appendString:@"灵"];
NSMutableString *fullName = [NSMutableString stringWithString:@"李"];
per.fullName = fullName;
[fullName appendString:@"灵"];
NSMutableArray *array = [NSMutableArray arrayWithArray:@[ @"1", @"2", @"3"]];
per.arrayC = array;
per.arrayS = array;
[array addObject:@"4"];
(lldb) p fullNameM
(__NSCFString *) $0 = 0x00006000028f8000 @"李灵"
(lldb) p per.fullName
(__NSCFString *) $1 = 0x00006000026c91c0 @"李"
(lldb) p nameM
(__NSCFString *) $2 = 0x00006000028f8030 @"明灵"
(lldb) p per.name
(__NSCFString *) $3 = 0x00006000028f8030 @"明灵"
(lldb) p array
(__NSArrayM *) $0 = 0x00006000019de7f0 @"4 elements"
(lldb) p per.arrayS
(__NSArrayM *) $1 = 0x00006000019de7f0 @"4 elements"
(lldb) p per.arrayC
(__NSArrayI *) $2 = 0x00006000019de7c0 @"3 elements"
上述分析了 NSString 和 NSArray,可以尝试打印下 NSDictionary 。
扩展 - @property内存管理策略选择
非ARC
1、copy:只用于 NSString\NSDictionary\NSArray\block
2、retain:除 NSString\NSDictionary\NSArray\block以外的OC对象
3、assign:基本数据类型、枚举、结构体(非OC对象),当2个对象相互引用,一端用retain,一端用assign
ARC
1、copy:只用于NSString\NSDictionary\NSArray\block
2、strong:OC对象
3、weak:当2个对象相互引用,一端用strong,一端用weak
4、assgin:基本数据类型、枚举、结构体(非OC对象)
<NSCopying>
未实现 <NSCopying> 协议时,使用 copy 方法,如下:
XLPerson *xPerson = [per copy];
会抛出异常,异常信息如下:
2022-04-26 14:45:15.796824+0800 RetainCount[10234:6153776] -[XLPerson copyWithZone:]: unrecognized selector sent to instance 0x600002f8e400
2022-04-26 14:45:15.799449+0800 RetainCount[10234:6153776] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[XLPerson copyWithZone:]: unrecognized selector sent to instance 0x600002f8e400'
NSCopying是对象拷贝的协议;- 实现自定义类复制,必须遵循
<NSCopying>协议; - 必须实现
<NSCopying>协议中的- (id)copyWithZone:(nullable NSZone *)zone。
如果要区分开可变副本和不可变副本,需要根据 <NSMutableCopying> 协议实现 - (id)mutableCopyWithZone:(nullable NSZone *)zone 方法。
实现:
1、@interface 指令:
@interface XLPerson : NSObject <NSCopying>
2、在 XLPerson.m 中实现 copyWithZone: 方法,如下:
- (id) copyWithZone:(NSZone *)zone {
XLPerson *newPer = [[XLPerson allocWithZone:zone] init];
newPer.name = _name;
newPer.fullName = _fullName;
return newPer;
}
用设值方法和取值方法复制对象
采用设值方法和取值方法复制对象时需要考虑是否需要保护这些值。若在设值方法函数内,只是简单的将参数赋值给相应的实例变量:
- (void) setName:(NSString *) theName {
_name = theName;
}
当 NSMutableString 的字符串赋值时,NSMutableString 的字符串变了,它也会改变。及可改写为:
- (void) setName:(NSString *) theName {
_name = [theName copy];
}