Objective-C阅读笔记——对象复制


NSObject类提供了copy和mutableCopy方法,通过这两个方法即可复制已有对象的副本。

copy与mutableCopy方法

copy方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使该对象本身是可修改的。例如,程序通常调用NSMutableString的copy方法,将会返回不可修改的字符串对象。
mutableCopy方法用于复制对象的可变副本。通常来说,mutableCopy方法总是返回该对象可修改的副本,即使被复制的对象本身是不可修改的,调用mutableCopy方法复制出来的副本也是可以修改的。例如,程序调用NSString的mutableCopy方法,将会返回一个NSMutableString对象。
无论如何,copy和mutableCopy返回的总是原对象的副本,当程序对复制的副本进行修改时,原对象通常不会受到影响。

事例

#import <Foundation/Foundation.h>
int main(int argc , char * argv[])
{
    @autoreleasepool {
        NSMutableString* book = [NSMutableString stringWithString:@"疯狂iOS讲义"];
        //复制book字符串的可变副本
        NSMutableString* bookCopy = [book mutableCopy];
        //修改副本,对原字符串没有任何影响
        [bookCopy replaceCharactersInRange:NSMakeRange(2, 3) withString:@"Android"];
        //此处看到原字符串的值并没有改变
        NSLog(@"book的值为:%@", book);
        //字符串副本发生了改变
        NSLog(@"bookCopy的值为:%@", bookCopy);
        NSString* str = @"fkit";
        //复制str(不可变字符串)的可变副本
        NSMutableString* strCopy = [str mutableCopy];
        //向可变字符串后面追加字符串
        [strCopy appendString:@".org"];
        NSLog(@"%@", strCopy);
        //调用book(可变字符串)的copy方法,程序返回一个不可修改的副本
        //然后我们追加一个字符串但是系统报错,因为copy返回的是不可修改的副本
    }
}

运行结果如图所示:
在这里插入图片描述

从上面的程序可以看出,当程序复制对象的副本后,对副本所做的任何修改,对原始对象本身没有任何影响。

NSCopying与NSMutableCopy协议

使用copy和mutableCopy复制对象的副本使用起来确实很方便,那么我们自定义的类是否调用copy与mutableCopy方法来复制副本呢?

程序先定义一个FKDog类,该FKDog类的接口部分代码如下:

#import <Foundation/Foundation.h>

@interface FKDog : NSObject
@property(nonatomic , strong)NSMutableString* name;
@property(nonatomic , assign)int age;
@end

接下来为FKDog类提供实现部分,实现部分代码如下:

#import "FKDog.h"
@implementation FKDog
@synthesize name;
@synthesize age;
@end

然后尝试调用FKDog的copy方法来复制一个副本,我们会发现FKDog类找不到mutableCopy方法和copyWithZone:方法。
所以我们可以发现虽然NSObject提供了copy和mutableCopy方法,但自定义的类并不能直接调用这两个方法来复制自身。
为了保证一个对象可调用copy方法来复制自身的不可变副本,通常需要做如下事情:
1.让该类实现NSCopying协议。
2.让该类实现copyWithZone:方法。
与此同时,为了保证一个对象可以调用mutableCopy方法来复制自身的可变副本,通常需要做如下事情:
1.让该类实现NSMutableCopying协议。
2.让该类实现mutableCopyWithZone:方法。
当程序来调用对象的copy方法来复制自身时,程序底层需要调用copyWithZone:方法来完成实际的复制工作,copy返回的实际上就是copyWithZone:方法的返回值;当程序调用对象的mutableCopy方法来复制自身时,程序底层需要调用mutableCopyWithZone:方法来完成实际的复制工作,所以mutableCopy返回的实际上是mutableCopyWithZone:方法的返回值。
为了保证上面的FKDog类可调用copy方法来复制自身,程序可先在FKDog类的接口部分声明NSCopying协议;然后在FKDog类的实现部分增加如下copyWithZone方法。

-(id)copyWithZone:(NSZone*)zone
{
    NSLog(@"--执行copyWithZone--");
    //使用zone参数创建FKDog对象
    FKDog* dog = [[[self class] allocWithZone:zone] init];
    dog.name = self.name;
    dog.age = self.age;
    return dog;
}

我们接下来测试一下程序:

#import <Foundation/Foundation.h>
#import "FKDog.h"

int main(int argc , char * argv[])
{
    @autoreleasepool {
        FKDog* dog1 = [FKDog new];
        dog1.name = [NSMutableString stringWithString:@"旺财"];
        dog1.age = 20;
        FKDog* dog2 = [dog1 copy];
        dog2.name = [NSMutableString stringWithString:@"snoopy"];
        dog2.age = 12;
        NSLog(@"dog1的名字为: %@",dog1.name);
        NSLog(@"dog1的年龄为:%d",dog1.age);
        NSLog(@"dog2的名字为: %@",dog2.name);
        NSLog(@"dog2的年龄为: %d",dog2.age);
        
    }
}

从上面的程序可以看出,程序复制了dog1的副本,并将复制的副本赋给了dog2变量,接下来可以对dog2的name、age属性重新赋值,这些赋值对dog1不会产生任何影响。编译、运行该程序,我们可以看见如图所示运行结果:
在这里插入图片描述

从上面的程序可以看出,当程序调用FKDog的copy方法来复制自身时,底层实际上调用了copyWithZone:方法来执行实际的复制操作。而且从程序的运行结果可以看出,当程序修改dog2的name、age的属性时,dog1的name、age并未受到影响。
可能读者会感动疑惑:前面介绍copy方法时提到,copy方法应该复制的时该对象不可变副本,那此处调用FKDog对象的copy方法复制后怎么依然时一个可变的FKDog对象呢?这是因为此处的FKDog类没有提供对应的不可变类,自然也就无法复制不可变的FKDog对象。如果程序为FKDog提供了不可变类,当然还是应该让FKDog的copyWithZone:方法返回不可变的FKDog对象。
需要指出的是,如果重写copyWithZone:方法时,其父类已经实现NSCopying协议,并重写过copyWithZone:方法,那么子类重写copyWithZone:方法应先调用父类的copy方法复制从父类继承得到的成员变量,然后子类中定义的成员变量进行赋值。
假如父类已经重写了copyWithZone:方法,那么子类重写copyWithZone:方法格式如下:

-(id)copyWithZone:(NSZone*)zone
{
	id obj = [super copy]
	//对子类定义的成员变量赋值
	...
	return obj;
}

浅复制与深复制

为了更好的理解浅复制(shallow copy)与深复制(deep copy)的概念,我们先看如下程序:

#import <Foundation/Foundation.h>
#import "FKDog.h"
int main(int argc , char * argv[])
{
    @autoreleasepool {
        FKDog* dog1 = [FKDog new];
        dog1.name = [NSMutableString stringWithString:@"旺财"];
        dog1.age = 20;
        FKDog* dog2 = [dog1 copy];
        [dog2.name replaceCharactersInRange:NSMakeRange(0, 2) withString:@"snoopy"];
        NSLog(@"dog1的名字为: %@",dog1.name);
        NSLog(@"dog2的名字为: %@",dog2.name);        
    }
}

上面的程序调用了dog1的copy方法复制了一个副本,并将该副本赋给dog2变量,接下来修改了dog2对象的name属性值,输出dog1、dog2两个对象的name属性。编译、运行该程序结果如下:
在这里插入图片描述

从上面的程序我们可以看出,我们修改dog2的name属性值,但name1也被修改了。
我们看一个图:
在这里插入图片描述

当程序创建第一个FKDog对象,并使用dog1指针指向对象,接下来程序复制了一个FKDog对象,查看copyWithZone:方法我们可以看见如下两行代码:

dog.name = self.name;
dog.age = self.age;

其中,dog代表复制出来的对象,此时程序将被复制对象的name复制给dog的name。注意,name只是一个指针变量,该变量中存放的只是字符串的自制,并不是字符串本身。这样赋值的效果是让dog对象的name与被复制的对象的name属性值指向同一个字符串。所以我们理解浅复制,就是将地址赋给对象,这两个对象将会指向同一个对象,也就是两个对象依然存在共用的部分。
深复制则会采用与此不同的方式,深复制不仅会复制对象本身,而且会“递归”复制每个指针类型的实例变量,知道两个对象没有任何共用部分。如果将上面FKDog的copyWithZone:改为如下形式,即可实现深复制。

#import "FKDog.h"

@implementation FKDog
@synthesize name;
@synthesize age;
-(id)copyWithZone:(NSZone*)zone
{
    NSLog(@"--执行copyWithZone--");
    //使用zone参数创建FKDog对象
    FKDog* dog = [[[self class] allocWithZone:zone] init];
    //将原对象的name实例变量复制一份副本后赋值给新对象的name实例变量
    dog.name = [self.name mutableCopy];
    dog.age = self.age;
    return dog;
}
@end

上面程序中并没有简单的将被复制对象的name实例变量的值赋给新对象的name实例变量,而是先将原对象的name事例变量复制了一份可变副本,再将该可变副本的值赋给新对象的name事例变量。这就保证了原FKDog对象与新的FKDog对象之间没有任何共用的部分,这就实现了深复制。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值