Objective-C Copy相关

在日常开发中,会经常用到copy,Objective-C中很多对象都支持copy操作,如NSString,NSArray,NSDictionary,开发者自定义的对象也是可以支持copy操作的。和copy放在一起比较的还有mutableCopy、strong,那么三者之间到底有什么区别呢?什么情况下用copy,什么情况下用strong呢?下面围绕这几个问题来研究一下。

copy 和 mutableCopy

深拷贝 浅拷贝

在区分copy 和 mutableCopy的不同之前,首先了解一下深拷贝和浅拷贝。
在学习和工作中,可能会涉及到多门语言,每门语言中可能都有深拷贝和浅拷贝。简单来说,浅拷贝就是增加了一个指针,该指针指向的内存地址就是赋值对象的内存地址;深拷贝不仅仅是增加了指针,同时也分配了一块内存,该内存中的值和赋值对象所在内存中的值是一样的,深拷贝所增加的指针,指向的也是新分配的内存。
区分一个操作到底是深拷贝还是浅拷贝,可以打印内存地址。如果两个内存地址一致,则是浅拷贝,否则是深拷贝。

非集合对象的copy 和 mutableCopy操作

Objecitve-C中对象分为集合对象和非集合对象。
集合对象:NSArray、NSDictionary等。集合对象又分为可变对象和不可变对象。不可变集合对象:NSArray、NSDictionary;可变集合对象:NSMutableArray、NSMutableDictionary。
非集合对象:NSString等。非集合对象可分为可变对象和非可变对象。不可变对象:NSString;可变对象:NSMutableString。
非集合对象的copy、mutableCopy分别对应什么操作呢?
先说结论:非集合对象,可变对象,copy 和 mutableCopy都是深复制;不可变对象,copy是浅复制,mutableCopy是深复制。
写代码来验证上面的结论。

- (void)notSetTestCopy
{
    // 非集合对象,如NSString
    // 非集合对象又可分为可变对象和不可变对象,NSString为可变对象,NSMutableString为不可变对象
    NSString *str1 = @"123";
    NSString *str2 = [str1 copy];
    NSLog(@"str1 = %p str2 = %p",str1,str2); //两者的地址相同,对于非集合对象且不可变对象,copy为浅复制
    NSString *str3 = [str1 mutableCopy];
    NSLog(@"str1 = %p str3 = %p",str1,str3); //两者的地址不相同,对于非集合对象且不可变对象,mutableCopy为深复制

    NSMutableString *str4 = [NSMutableString stringWithString:@"123"];
    NSString *str5 = [str4 copy];
    NSLog(@"str4 = %p str5 = %p",str4,str5); // 两者的地址不相同,非集合对象且可变对象,copy为深复制
    NSMutableString *str6 = [str4 mutableCopy];
    NSLog(@"str4 = %p str6 = %p",str4,str6); // 两者的地址不相同,非集合对象且可变对象,mutableCopy为深复制
    [str4 insertString:@"111" atIndex:0];
    NSLog(@"str4 = %@,str5 = %@,str6 = %@",str4,str5,str6); // 111123   123     123
}
集合对象的copy 和 mutableCopy 操作

集合对象的copy、mutableCopy 对应的是什么操作呢?
同样先说结论:集合对象,可变对象,copy 和 mutableCopy 都是深复制;不可变对象,copy是浅复制,mutableCopy 是深复制。
写代码来验证。

- (void)setTestCopy
{
    // 集合对象,NSArray、NSDictionary等
    // 可变集合对象,NSMutableArray;不可变集合对象,NSArray
    NSArray *array1 = @[@"1",@"2",@"3"];
    NSArray *array2 = [array1 copy];
    NSLog(@"array1 = %p array2 = %p",array1,array2); // 两者的地址相同,不可变集合对象copy为浅复制
    NSArray *array3 = [array1 mutableCopy];
    NSLog(@"array1 = %p array3 = %p",array1,array3); // 两者的地址不相同,不可变集合对象mutableCopy为深复制

    NSMutableArray *array4 = [NSMutableArray arrayWithArray:array1];
    NSArray *array5 = [array4 copy];
    NSLog(@"array4 = %p array5 = %p",array4,array5); //两者的地址不相同,可变集合对象copy为深复制
    NSArray *array6 = [array4 mutableCopy];
    NSLog(@"array4 = %p array6 = %p",array4,array6); // 两者的地址不相同,可变集合对象mutableCopy为深复制
}

copy 和 strong

为什么NSString类型的对象通常使用 copy 修饰符?

在回答上面的问题之前,先看NSString类型的对象两种不同的写法:

@property (nonatomic, copy) NSString *str1;
@property (nonatomic, strong) NSString *str2;

NSString 类型的对象 str1、str2分别用了copy 和 strong修饰符,那么两者有什么不同呢?
NSString 有一个子类是 NSMutableString,两者的区别是,NSString 类型的对象是不可变的,而 NSMutableString 类型的对象是可变的。如果一个变量是NSString 类型的,那么通常是不希望变量的值被改变的,否则直接使用 NSMutableString 就可以了。NSString 类型的对象,可以使用NSMutableString 类型的对象来赋值,而copy修饰符,能够保证NSString 类型的对象不被修改。如果用strong 修饰NSString 对象,NSString 对象的值有可能会被篡改。
写代码验证一下使用 copy 和 strong有什么不同。

@property (nonatomic, copy) NSString *customStr;
- (void)testCopy
{
    NSString *tempStr = @"123";
    NSMutableString *mutString = [NSMutableString stringWithString:tempStr];
    self.customStr = mutString;  // 其实就相当于 self.customStr = [mutString copy];
    NSLog(@"self str = %p  mutStr = %p",self.customStr,mutString);   // 两者指向的地址是不一样的
    [mutString insertString:@"456" atIndex:0];
    NSLog(@"self str = %@  mutStr = %@",self.customStr,mutString);  // self.customStr不会随着mutString的值而改变
}

使用copy,即使mutString的值改变了,self.customStr的值不会随之改变。
再来看一下如果使用 strong 的效果:

@property (nonatomic, strong) NSString *strongStr;
- (void)testStrong
{
    NSString *tempStr = @"123";
    NSMutableString *mutString = [NSMutableString stringWithString:tempStr];
    self.strongStr = mutString;
    NSLog(@"self str = %p  mutStr = %p",self.strongStr,mutString);   // 两者指向的地址是一样的
    [mutString insertString:@"456" atIndex:0];
    NSLog(@"self str = %@  mutStr = %@",self.strongStr,mutString);  // 两者的值都会改变
}

可以看到,self.strongStr 的值会随着 mutString值的改变而改变。
为了防止NSString 类型的对象意外被改变,开发中NSString 类型的对象通常使用copy修饰符。同理,NSArray、NSDictionary 类型的对象通常也使用copy 修饰符。

为什么NSMutableString类型的对象通常使用 strong 修饰符?

其实通过上面的介绍,对于NSMutableString 类型的对象为什么使用 strong 修饰符,已经有一定了解了。通常来说,使用NSMutableString 对象,则希望该对象能够被改变。如果用copy来修饰,那么该对象是不能被修改的,也就不具有了 NSMutableString 的特性。
下面我们看一个可变对象使用copy 修饰符来修饰,会带来什么问题。
@property (nonatomic, copy) NSMutableArray *mutArray; 会有什么问题?
写代码测试一下:

@property (nonatomic, copy) NSMutableArray *mutArray;
/**
 可变对象的copy操作,虽然是深复制,但是得到的是一个不可变对象
 */
- (void)testArrayCopy
{
    NSArray *array = [NSArray arrayWithObjects:@"1",@"2", nil];
    self.mutArray = [NSMutableArray arrayWithArray:array];  // 相当于: self.mutArray = [array copy];虽然是深复制,但是得到一个不可变对象
    NSLog(@"array = %p self.mutArray = %p",array,self.mutArray); // 两者的地址不一样
    NSLog(@"array class = %@ self.mutArray class = %@",NSStringFromClass([array class]),NSStringFromClass([self.mutArray class]));  // 两者类一致,都是NSArray 对象
    [self.mutArray removeObjectAtIndex:0];  // 会崩溃,因为此时 self.mutArray是NSArray
}

最终mutArray 会由于 copy 操作,得到一个不可变对象,无法进行增加、删除的操作。

自定义对象的copy操作

在开发中,会经常有自定义对象的需求。那么自定义的对象如何支持copy操作呢?自定义对象支持copy操作,需要两个步骤:
1. 实现 NSCopying 协议
2. 重写 CopyWithZone 方法
假设有Person 类,代码如下:

Person.h
#import <Foundation/Foundation.h>

@interface Person : NSObject <NSCopying>

/**
 昵称
 */
@property (nonatomic, copy) NSString *name;

/**
 年龄
 */
@property (nonatomic, assign) NSInteger age;

/**
 使用昵称、年龄初始化

 @param name 昵称
 @param age 年龄
 @return 对象
 */
- (instancetype)initWithName:(NSString *)name age:(NSInteger)age;

+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age;

@end

Person.m 中重写 copyWithZone方法。代码如下:

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

+ (instancetype)personWithName:(NSString *)name age:(NSInteger)age
{
    return [[self alloc] initWithName:name age:age];
}

- (id)copyWithZone:(NSZone *)zone
{
    Person *copyPerson = [[Person allocWithZone:zone] initWithName:self.name age:self.age];
    return copyPerson;
}

测试Person 对象是否支持copy 操作:

- (void)testPersonCopy
{
    Person *p1 = [[Person alloc] initWithName:@"Wang" age:18];
    Person *p2 = p1;
    NSLog(@"p1 = %p p2 = %p",p1,p2); // p1、 p2指向的是同一个地址
    Person *p3 = [p1 copy];
    NSLog(@"p1 = %p p3 = %p",p1,p3); // p1、p3 指向的是不同地址
    p1.name = @"Tom";
    NSLog(@"p1.name = %@ p2.name = %@ p3.name = %@",p1.name,p2.name,p3.name); // p1、p2 name相同,p3 name不同
}

说明Person 支持copy操作,且是深拷贝。

总结

copy 虽然是一个简单的操作,但是其相关的知识是非常多的。在使用中,一定要记清楚什么情况下使用copy,什么情况下使用strong,以及mutableCopy,并且明白其原理,避免项目出现一些奇怪的bug。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值