iOS Copy与MutableCopy 和 Copy与Strong 深度解析

开发中,数据处理是整个项目的重中之重,清晰的数据结构,安全高效的处理流程,能大大提高开发效率和系统的稳定性。数据是事物状态和变化的记录,具有可修改性和拷贝性,当多处使用,并有可能改变时,为了保障原数据的不变,我们需要拷贝一份新的数据,改变新的数据,而不改变原数据。数据处理中的,操作权限控制,数据的传递,数据的深、浅拷贝等。今天主要深度分析下,Copy与MutableCopy 和 Copy与Strong 区别及使用。

CSDN的排版有点糟心,抱歉。言归正传 》》》》》》》》》》

1 深浅拷贝的区别(Copy与MutableCopy)

 浅拷贝:指针拷贝,不产生新的对象,源对象的引用计数器+1;

 深拷贝:对象拷贝,会产生新的对象,源对象的引用计数器不变;

       判断是浅拷贝和深拷贝就看一下两个变量的内存地址是否一样,一样就是浅拷贝,不一样就是深拷贝,也可以改变一个变量的其中一个属性值看两者的值都会发生变化;

1.1 系统原生的对象深浅拷贝区别

NSObject类提供了copy和mutableCopy方法,通过这两个方法即可拷贝已有对象的副本,主要的系统原生对象有:NSStringNSMutableStringNSArrayNSMutableArrayNSDictionaryNSMutableDictionaryNSSetNSMutableSet。 NSValueNSNumber 只遵守的NSCopying协议。

注意:基本数据类型(assign修饰),没有对应的指针,是直接赋值操作,没有,也无需copy 操作。

这里以NSString 和 NSMutableString为例演示说明。

NSString —— copy/mutableCopy
    NSString *string = @"copyTest";
    NSString *copyString = [string copy];
    NSString *mutableCopyString = [string mutableCopy];
    NSMutableString *copyMutableString = [string copy];
    NSMutableString *mutableCopyMutableString = [string mutableCopy];
    NSLog(@"\n string = %p \n copystring = %p \n mutablecopystring = %p "
           "\n copyMutableString = %p \n mutableCopyMutableString = %p \n",
          string, copyString, mutableCopyString, copyMutableString, mutableCopyMutableString);

打印结果:

 string = 0x10dc8c260 
 copystring = 0x10dc8c260 
 mutablecopystring = 0x61000007de40 
 copyMutableString = 0x10dc8c260 
 mutableCopyMutableString = 0x61000007dac0 

小结论:在字符串是直接赋值的,是否生成新对象是和 = 右边有关的,如果 = 右边是mutableCopy才会生成新对象。

NSMutableString —— copy/mutableCopy
    NSMutableString *string = [NSMutableString stringWithString:@"学习研究"];
    NSString *copyString = [string copy];
    NSString *mutableCopyString = [string mutableCopy];
    NSMutableString *copyMutableString = [string copy];
    NSMutableString *mutableCopyMutableString = [string mutableCopy];
    NSLog(@"\n string = %p \n copystring = %p \n mutablecopystring = %p "
           "\n copyMutableString = %p \n mutableCopyMutableString = %p \n",
          string, copyString, mutableCopyString, copyMutableString, mutableCopyMutableString);
打印结果:
 string = 0x600000268e40 
 copystring = 0x600000221340 
 mutablecopystring = 0x600000268f40 
 copyMutableString = 0x600000221260 
 mutableCopyMutableString = 0x600000268fc0 
小结论:只要=右边从创建到赋值,至少包含一个NSMutable便会重新生成一个对象。如果对一个不可变对象拷贝,copy是指针拷贝(浅拷贝)和mutableCopy就是对象拷贝(深拷贝)。但是,无论原对象是否含有NSMutable,copy返回的对象都是不可变的。

注意:其他对象NSArray、NSMutableArray 、NSDictionary、NSMutableDictionary、NSSet、NSMutableSet一样适用。


1.2 自定对象的深浅拷贝

实际开发中,所使用的数据模型基本都是自定义对象,并常见自定义对象的嵌套使用,我们如何实现自定义对象的深度拷贝呢?

1.2.1 前期准备

创建两个对象  PersonItem、DogItem,.m文件中暂时不做处理,.h文件参考如下:

DogItem.h

#import <Foundation/Foundation.h>

@interface DogItem : NSObject 

@property (nonatomic, copy) NSString *dogName;

@end
PersonItem.h
#import <Foundation/Foundation.h>
#import "DogItem.h"

@interface PersonItem : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) NSUInteger age;

@property (nonatomic, strong) DogItem *dog;

@end
参考本文1.1章节中的使用
    PersonItem *person = [[PersonItem alloc] init];
    person.name = @"person";
    person.age = 100;
    DogItem *dog = [[DogItem alloc] init];
    dog.dogName = @"dog";
    person.dog = dog;
    //拷贝对象
    PersonItem *copyPerson = [person copy];
    NSLog(@"\n person: %p name = %p dog = %p", person, person.name, person.dog);
    NSLog(@"\n copyPerson: %p name = %p dog = %p", copyPerson, copyPerson.name,copyPerson.dog);
    // 更新数据
    copyPerson.name = @"copy";
    copyPerson.dog.dogName = @"xiaohuang";
    NSLog(@"\n person.name: %@ copyPerson.name = %@", person.name, copyPerson.name);
    NSLog(@"\n person.dog.dogName: %@ copyPerson.dog.dogName = %@", person.dog.dogName, copyPerson.dog.dogName);
    
    NSLog(@"\n person: %p name = %p dog = %p", person, person.name, person.dog);
    NSLog(@"\n copyPerson: %p name = %p dog = %p", copyPerson, copyPerson.name,copyPerson.dog);
来Run 下,竟然崩溃了。。。未找到PersonItem类的 对象方法copyWithZone:
-[PersonItem copyWithZone:]: unrecognized selector sent to instance 0x60000003d3e0
我们需要实现
- (id)copyWithZone:(NSZone *)zone
{
}
//或
- (id)mutableCopyWithZone:(NSZone *)zone
{
}
注意:细心的你是否发现,我明明调用的是copy 方法 ,却要实现copyWithZone:而不copy?
我们需要通过copyWithZone: 或 mutableCopyWithZone: 来开辟存储空间,拷贝对象及其实例变量。无需在.h文件中声明这两个方法,这是因为系统方法的 copy(mutableCopy)中实现了对 copyWithZone:(mutableCopyWithZone: )的调用。
为了编码规范化,你需要自定义对象遵守 NSCopying 或NSMutableCopying 协议,其实此处不遵守该协议,只要正确实现了上述的两个方法,不会报错能也正常使用,请养成规范编码的好习惯。
1.2.2 关键步骤: 实现copyWithZone: 方法

在自定义对象的.m文件中实现copyWithZone: 方法,主要实现形式有一下四种。

方法一:伪拷贝不可用哦 
- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

打印结果:

 person: 0x61800002cd80 name = 0x1054e5118 dog = 0x618000006370
 copyPerson: 0x61800002cd80 name = 0x1054e5118 dog = 0x618000006370
 person.name: copy copyPerson.name = copy
 person.dog.dogName: xiaohuang copyPerson.dog.dogName = xiaohuang
 person: 0x61800002cd80 name = 0x1054e5198 dog = 0x618000006370
 copyPerson: 0x61800002cd80 name = 0x1054e5198 dog = 0x618000006370
小结论:伪拷贝,指针拷贝,没有开辟内存空间,原对象引用计数器加1,新对象及其实例变量的指针地址都与原对象一样。
方法二:浅拷贝(单层拷贝)
- (id)copyWithZone:(NSZone *)zone
{
    PersonItem *personCopy = [[PersonItem allocWithZone:zone] init];
    personCopy.name = self.name;
    personCopy.age = self.age;
    personCopy.dog = self.dog;
    
    return personCopy;
}
打印结果:
 person: 0x60000003d480 name = 0x10b455118 dog = 0x60000001de80
 copyPerson: 0x60000003c2e0 name = 0x10b455118 dog = 0x60000001de80
 person.name: person copyPerson.name = copy
 person.dog.dogName: xiaohuang copyPerson.dog.dogName = xiaohuang
 person: 0x60000003d480 name = 0x10b455118 dog = 0x60000001de80 
 copyPerson: 0x60000003c2e0 name = 0x10b455198 dog = 0x60000001de80
小结论:浅拷贝,开辟了内存空间,原对象引用计数器不变,对象的指针地址与原对象不同,但新对象的实例变量初始化的指针地址与原对象的实例变量指针地址一样,当新对象的该实例变量再次赋值后,该实例变量的指针地址变更。对象嵌套时,不能拷贝dog对象的实例变量的内容。
方法三:深拷贝(完全拷贝)
- (id)copyWithZone:(NSZone *)zone
{
    PersonItem *personMutableCopy = [[PersonItem allocWithZone:zone] init];
    personMutableCopy.name = [self.name mutableCopy];
    // assign 修饰的基本数据类型,没有对应的指针,可以直接赋值操作,没有也无需copy操作。
    personMutableCopy.age = self.age;
    personMutableCopy.dog = [self.dog copy];
    
    return personMutableCopy;
}
打印结果:
 person: 0x600000024aa0 name = 0x102a9d118 dog = 0x600000002100
 copyPerson: 0x600000024ba0 name = 0x60000006ac80 dog = 0x6000000020f0
 person.name: person copyPerson.name = copy
 person.dog.dogName: dog copyPerson.dog.dogName = xiaohuang
 person: 0x600000024aa0 name = 0x102a9d118 dog = 0x600000002100
 copyPerson: 0x600000024ba0 name = 0x102a9d198 dog = 0x6000000020f0
小结论:深拷贝,开辟了内存空间,新对象及其实例变量的指针地址都与原对象都不一样,如果涉及到容器中包含容器,深拷贝就是容器中每一层对象都是深拷贝。
方法四:深拷贝(完全拷贝)
- (id)copyWithZone:(NSZone *)zone
{
    PersonItem *personCopy = [[PersonItem alloc] init];
    personCopy.name = self.name;
    personCopy.age = self.age;
    DogItem *dog = [[DogItem alloc] init];
    dog.dogName = self.dog.dogName;
    personCopy.dog = dog;
 
    return personCopy;
}
打印结果:
 person: 0x61800003fe80 name = 0x1030f3118 dog = 0x61800000fb30
 copyPerson: 0x61800003fd20 name = 0x1030f3118 dog = 0x61800000fb60
 person.name: person copyPerson.name = copy
 person.dog.dogName: dog copyPerson.dog.dogName = xiaohuang
 person: 0x61800003fe80 name = 0x1030f3118 dog = 0x61800000fb30
 copyPerson: 0x61800003fd20 name = 0x1030f3198 dog = 0x61800000fb60
小结论:深拷贝,开辟了内存空间, 新对象及其实例变量的指针地址都与原对象都不一样,如果涉及到容器中包含容器,深拷贝就是容器中每一层对象都是深拷贝。
综述:
方法一是伪拷贝只是指针拷贝,达不到拷贝对象内容的要求。
方法二是浅拷贝(单层拷贝),对象嵌套时,不能拷贝其所包含自定义对象的其实例,不能完全满足要求。
方法三是深拷贝(完全拷贝),只要所包含自定义对象实现了copy协议和相关方法实现,能完全拷贝对象及所包含自定义对象的内容。
方法四是深拷贝(完全拷贝),和方法三效果相同。方法四可以有另外的实现方式,不用遵守协议和实现copyWithZone: 方法,可以在自定义类中实现一个myCopy的对象方法,实现和方法四中一样,声明并显式的调用。
1.2.3 mutableCopyWithZone: 方法

mutableCopyWithZone和copyWithZone: 一样是个待实现的方法,关键的区别在于内部实现的区别。通常,copyWithZone: 做自定义对象的单层拷贝处理(有容器嵌套的化,只copy最外一层)。mutableCopyWithZone: 做自定义对象的完全拷贝处理(有容器嵌套的化,容器中每一层对象都做拷贝处理)。

2 Copy与Strong的区别

说完深浅拷贝,现在让我们一起梳理下property里的copy、strong的区别。

以NSString为例说明下,首先定义以下属性。

@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@property (nonatomic, strong) NSMutableString *strongMutableString;
@property (nonatomic, copy) NSMutableString *copyedMutableString;

2.1 当外部赋给对应属性一个不可变(非mutable)的字符串 NSString

- (void)testPropertyCopyOrStrong
{
    NSString *string = [NSString stringWithFormat:@"abc"];
    self.strongString = string;
    self.strongMutableString = string;
    self.copyedString = string;
    self.copyedMutableString = string;
    string = [string stringByReplacingOccurrencesOfString:@"c" withString:@"233"];

    NSLog(@"\n origin        string: %p, %p  %@  %@", string, &string, string, NSStringFromClass([string class]));
    NSLog(@"\n strong        string: %p, %p  %@  %@", _strongString, &_strongString, _strongString, NSStringFromClass([_strongString class]));
    NSLog(@"\n strongMutable string: %p, %p  %@  %@", _strongMutableString, &_strongMutableString, _strongMutableString, NSStringFromClass([_strongMutableString class]));
    NSLog(@"\n copy          string: %p, %p  %@  %@", _copyedString, &_copyedString, _copyedString, NSStringFromClass([_copyedString class]));
    NSLog(@"\n copyMutable   string: %p, %p  %@  %@", _copyedMutableString, &_copyedMutableString, _copyedMutableString, NSStringFromClass([_copyedMutableString class]));

}

打印结果:

 origin        string: 0x103a74098, 0x7fff5c18ca88  ab233  __NSCFString
 strong        string: 0xa000000006362613, 0x7f84c9f056d8  abc  NSTaggedPointerString
 strongMutable string: 0xa000000006362613, 0x7f84c9f056e8  abc  NSTaggedPointerString
 copy          string: 0xa000000006362613, 0x7f84c9f056e0  abc  NSTaggedPointerString
 copyMutable   string: 0xa000000006362613, 0x7f84c9f056f0  abc  NSTaggedPointerString

2.2 当外部赋给对应属性一个可变的字符串 NSMutableString

- (void)testPropertyCopyOrStrong
{
    NSMutableString *string = [NSMutableString stringWithFormat:@"abc"];
    self.strongString = string;
    self.strongMutableString = string;
    self.copyedString = string;
    self.copyedMutableString = string;
    [string appendString:@"123"];
    
    NSLog(@"\n origin        string: %p, %p  %@  %@", string, &string, string, NSStringFromClass([string class]));
    NSLog(@"\n strong        string: %p, %p  %@  %@", _strongString, &_strongString, _strongString, NSStringFromClass([_strongString class]));
    NSLog(@"\n strongMutable string: %p, %p  %@  %@", _strongMutableString, &_strongMutableString, _strongMutableString, NSStringFromClass([_strongMutableString class]));
    NSLog(@"\n copy          string: %p, %p  %@  %@", _copyedString, &_copyedString, _copyedString, NSStringFromClass([_copyedString class]));
    NSLog(@"\n copyMutable   string: %p, %p  %@  %@", _copyedMutableString, &_copyedMutableString, _copyedMutableString, NSStringFromClass([_copyedMutableString class]));

}

打印结果:

 origin        string: 0x60000006cec0, 0x7fff5417ea88  abc123  __NSCFString
 strong        string: 0x60000006cec0, 0x7fc702508148  abc123  __NSCFString
 strongMutable string: 0x60000006cec0, 0x7fc702508158  abc123  __NSCFString
 copy          string: 0xa000000006362613, 0x7fc702508150  abc  NSTaggedPointerString
 copyMutable   string: 0xa000000006362613, 0x7fc702508160  abc  NSTaggedPointerString
注意:此处的 string 是局部变量,存储在栈区,_strongString、_strongMutableString、_copyedString、_copyedMutableString 是全局变量,存储在堆区。他们有不同存储的空间,对应的指针地址会不同。
__NSCFString 、__NSCFConstantString 都是NSSting抽象类的具体数据处理子类。NSTaggedPointerString
扩展阅读:
这里的底层知识,我也不甚清楚,暂时只能说到这个程度,后续补充。

2.3 原理分析

让我们回顾下,我们定义的四个属性:

@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copyedString;
@property (nonatomic, strong) NSMutableString *strongMutableString;
@property (nonatomic, copy) NSMutableString *copyedMutableString;
它们的set 方法是怎实现的呢?
property strong  strongString 实际上就是对 strongString 做了
- (void)setStrongString:(NSString *)strongString
{
    _strongString = strongString;
}
property copy  copyString 实际上就是对 copyString 做了
- (void)setCopyedString:(NSString *)copyedString
{
    _copyedString = [copyedString copy];
}
property strong strongMutableString  实际上就是对 strongMutableString 做了
- (void)setStrongMutableString:(NSMutableString *)strongMutableString
{
    _strongMutableString = strongMutableString;
}
然而,property copy copyMutableString  实际上就是对 copyMutableString 做了
- (void)setCopyedMutableString:(NSMutableString *)copyedMutableString
{
    _copyedMutableString = [copyedMutableString copy];
}
问题来了。。。。。。。
iOS 类型声明的 陷阱
看似你对 copyMutableString 属性 声明了 NSMutableString 类型,在使用上,调用了 NSMutableString 的方法,你会发现,程序运行到这行,直接崩溃了~_~,原因: 无论原对象是否含有NSMutable,copy返回的对象都是不可变的。
NSString 实例,调用 NSMutableString 的方法,找不到该方法,当然就崩溃了。
因此,在声明名属性时,就会有一些不成文的规范。以字符串为例,为了保障数据的安全,以免被随意修改,尽量使用NSString(非mutable)类型,若不希望该属性内容的随着外部变化而影响初始值,应该用copy修饰,甚至用readonly 加强修饰。若希望该属性的内容随时变化并存储,可以用strong修饰,NSMutable* 类,都是继承自NS*类,因此,NS* 可以接收NSMuatble* 或NS * 类型的值,调用NS* 相关属性或方法,是正常的操作。或声明成NSMutableString类型,切记一定要用strong修饰。 
推荐的属性声明方式:
@property (nonatomic, copy) NSString *copyedString;
@property (nonatomic, strong) NSMutableString *strongMutableString;
根据需要选择,NSArray、NSMutableArray; NSDictionary、NSMutableDictionary; NSSet、NSMutableSet;类似。
不足之处,欢迎交流!
  • 5
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值