OC学习记录,对象复制

news/2025/1/11 19:57:06/

疯狂iOS讲义,对象复制

  • copy与mutableCopy方法
  • NSCopying与NSMutableCopy协议
  • 浅赋值与深复制
  • setter方法的复制选项

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方法,程序返回一个不可修改的副本NSMutableString* bookCopy2 = [book copy];//由于bookCopy2是不可修改的,因此下面的代码将会出现错误[bookCopy2 appendString:@"aa"];}return 0;
}

在这里插入图片描述
从上面的程序可以看出,当程序复制对象的副本后,对副本所做的任何修改,对原始对象本身并没有任何影响。因此可以看到book的值依然是“疯狂iOS讲义”,而bookCopy已经被改为“疯狂Android讲义”。
在这里插入图片描述

NSCopying与NSMutableCopy协议

通过copy和mutableCopy方法复制对象的副本使用起来确实很方便,那么自定义类是否可调用copy与mutablecopy方法来复制副本呢?
下面程序先定义一个FKDog类,该FKDog类的接口部分代码如下。

#import <Foundation/Foundation.h>NS_ASSUME_NONNULL_BEGIN@interface FKDog : NSObject
@property (nonatomic, strong) NSMutableString* name;
@property (nonatomic, assign) int age;
@endNS_ASSUME_NONNULL_END

然后为FKDog类提供实现部分,FKDog类实现部分非常简单,此处暂不给出。
接下来程序尝试调用FKDog类的copy方法来复制一个副本。


#import"FKDog.h"int main(int argc, char * argv[]) {@autoreleasepool{FKDog* dog1  = [FKDog new];//创建一个FKDog对象dog1.name = [NSMutableString stringWithString:@"旺财"];dog1.age = 20;FKDog* dog2 = [dog1 copy];//复制副本}return 0;
}

其中,粗体字代码调用了dog1的copy方法来复制副本。由于FKDog类继承了NSObject,因此编译程序不会有任何问题。运行该程序,将会看到如下错误:

[FKDog copyWithZone:]: unrecognized selector sent to instance 0x7f9d7bc0bb90

上面的错误提示:FKDog类找不到copyWithZone方法。可能会有读者感到奇怪:程序只是调用FKDog对象的copy方法,并未调用copyWithZone方法,为何会提示找不到copyWithZone:方法呢?
将复制副本那一行的代码改为如下代码:

//复制对象的可变副本
FKDog* dog2 = [dog1 mutableCopy];

再次编译该程序,依然可以通过编译。运行该程序,将会提示如下错误:

[FKDog mutableCopyWithZone:]:unrecognized selector sent to instance 0x7fedc3406b30

上面的错误提示:FKDog类找不到mutableCopyWithZone:方法。
从上面的错误不难看出,输入NSObject类提供了copy和mutableCopy方法,但自定义类并不能直接调用这两个方法来复制自身。
为了保证一个对象可调用copy方法来复制自身的不可变副本,通常需要做如下事情。

  • 让该类实现NSCopying协议
  • 让该类实现copyWithZone:方法

与此同时,为了保证一个对象可以调用mutableCopy方法来复制自身的可变副本,通常需要做如下事情。

  • 让该类实现NSMutableCopying协议
  • 让该类实现mutableCopyWithZone:方法

当程序调用对象的copy方法来复制自身时,程序底层需要调用copyWithZone:方法来完成实际的复杂工作,copy返回的实际上就是copyWithZone:方法的返回值;当程序调用对象的mutableCopy方法来复制自身时,程序底层需要调用mutableCopyWithZone:方法来完成实际的复制工作,mutableCopy返回的实际上就是mutableCopyWithZone:方法的返回值。
为了保证上面的FKDog类可调用copy方法来复制自身,程序可先FKDog类的接口部分声明实现NSCopying协议;然后在FKDog类的实现部分增加如下copyWithZone:方法。

#import "FKDog.h"@implementation FKDog
- (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;
}
@end

上面程序让FKDog实现了NSCopy协议,并实现了copyWithZone:方法,在该方法中重新创建了一个FKDog对象,并让该对象的所有属性值与被复制对象的属性值相等,最后返回这个新创建的对象,也就是返回该对象的副本。copyWithZone:(NSZone*)zone方法中的zone参数与不同的存储区有关,通常无须过多的关心该参数,只要将zone参数传给copyWithZone:方法,即可创建该对象的副本。
此时将FKDogTest重写改为如下形式。

#import"FKDog.h"int main(int argc, char * argv[]) {@autoreleasepool{FKDog* dog1 = [FKDog new];//创建一个FKDog对象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);}return 0;
}

从上面的程序可以看出,程序复制了dog1的副本,并将复制的副本赋给dog2变量,接下来可以对dog2的name、age属性重新赋值,这些赋值对dog1不会产生任何影响。编译、运行该程序,可以看到以下输出;

2022-06-02 17:09:04.241577+0800 练习[68996:2944106] --执行copyWithZone:–
2022-06-02 17:09:04.241825+0800 练习[68996:2944106] dog1 的名字为:旺财
2022-06-02 17:09:04.241849+0800 练习[68996:2944106] dog1 的年龄为:20
2022-06-02 17:09:04.241875+0800 练习[68996:2944106] dog2 的名字为:snoopy
2022-06-02 17:09:04.241889+0800 练习[68996:2944106] dog2 的年龄为:12
Program ended with exit code: 0

从上面的程序可以看出,当程序调用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)的概念,先看如下程序:
程序清单:codes/07/7.3/FKDogTest2.m

#import"FKDog.h"int main(int argc, char * argv[]) {@autoreleasepool{FKDog* dog1 = [FKDog new];//创建一个FKDog对象dog1.name = [NSMutableString stringWithString:@"旺财"];dog1.age = 20;FKDog* dog2 = [dog1 copy];//复制副本[dog2.name replaceCharactersInRange:NSMakeRange(0, 2)withString:@"snoopy"];//查看dog2、dog1的name属性值NSLog(@"dog2的name为:%@", dog2.name);NSLog(@"dog1的name为:%@",dog1.name);}return 0;
}

上面程序调用了dog1的copy方法复制了一个副本,并将该副本赋给dog2变量,接下来修改了dog2对象的name属性值,输出dog1、dog2两个对象的name属性值。编译、运行该程序,可以看到如下输出:

2022-06-02 19:06:23.966937+0800 练习[69840:2986989] --执行copyWithZone:--
2022-06-02 19:06:23.967130+0800 练习[69840:2986989] dog2的name为:snoopy
2022-06-02 19:06:23.967149+0800 练习[69840:2986989] dog1的name为:snoopy
Program ended with exit code: 0

为了向读者更好地说明这个问题,接下来将通过示意图进行说明。程序创建了第一个FKDog对象,并使用dog1指针指向对象后的内存存储示意图如下图(图7.4)所示。

在这里插入图片描述
接下来程序复制了一个FKDog对象,查看copyWithZone:方法的代码,看到如下两行:

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

其中,dog代表复制出来的对象,此时程序将被复制对象的name复制给dog的name。注意,name只是一个指针变量,该变量中存放的只是字符串的地址,并不是字符串本身。这样赋值的效果是让dog对象的name属性与被复制对象的name属性指向同一个字符串,此时的效果如图7.5所示。

在这里插入图片描述
从图7.5可以看出,此时dog1、dog2两个指针分别指向两个不同的FKDog对象,但这两个FKDog对象的name属性都是指针,而且他们都指向同一个NSMutableString对象。这样当程序修改任何一个FKDog的name属性值时,另一个FKDog对象的name属性值也会随着改变。
对于图7.5所示的这种复制方式:当对象的属性时指针变量时,如果程序只是复制该指针的地址,而不是真正复制指针所指向的对象,这种方式就被称为“浅复制”,从上面的程序可以看出,对浅复制而言,在内存中复制了两个对象,这两个对象的指针变量将会指向同一个对象,也就是两个对依然存在共用的部分。
深复制则会采用于此不同的方式,深复制不仅会复制对象本身,而且会“递归”复制每个指针类型的属性,直到两个对没有任何共用的部分。如果将上面的FKDog的copyWithZone:改为如下形式,即可实现深复制。

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

上面程序中并没有简单地将被复制对象的name属性值赋给新对象的name属性,而是先将原对象的naem属性值复制了一份可变副本,再将该可变副本的值赋给新对象的name属性。这样就保证了原FKDog对象与新的FKDog对象之间没有任何共用的部分,这就实现了深复制。
当FKDog实现了深复制之后,再次编译、运行上面的FKDogTest2.m程序,将可以看到两个FKDog对象之间没有任何关联,当一个FKDog对象的name属性值改变时,另一个FKDog对象的name属性值不会收到任何影响。
在这里插入图片描述

setter方法的复制选项

前面介绍合成setter和getter方法时提到可以使用copy指示符,copy指示符就是指定当程序调用setter方法复制时,实际上就是将传入参数的副本赋给程序的实例变量。
下面再定义一个FKItem类,该类的接口部分代码如下。
程序清单:codes/07/7.3/FKItem.h

#import <Foundation/Foundation.h>@interface FKItem: NSObject
@property (nonatomic, copy) NSMutableString* name;
@end

上面程序定义name属性时使用了copy指示符,接下来为该FKItem类提供实现部分,实现部分代码很简单,此处不再给出。
使用如下程序来测试FKItem类。

程序清单:codes/07/7.3/FKitemTest.m

在这里插入代码片

上面程序先创建了一个FKItem对象,接下来对FKIetm对象的name属性赋值,而且确实赋了一个NSMutableString对象。编译该程序没有任何问题,但运行该程序时会提示如下错误:

*** Terminating app due to uncaught exception ‘NSInvalidArgumentException’, reason: ‘Attempt to mutate immutable object with appendString:’
这段错误提示不允许修改item的name属性值,这是因为程序定义name属性时使用了copy指示符,该指示符指定调用setName:方法时(通过点语法赋值时,实际上是调用对应的setter方法),程序实际上会使用参数的副本对name实例变量赋值。也就是说,setName:方法的代码如下:

- (void) setName: (NSMutableString*)name
{name = [aname copy];
}

copy方法默认是赋值该对象的不可变副本,输入程序传入的是NSMutableString,但程序调用该参数的copy方法得到的是不可变副本。因此,程序赋给FKItem对象的name实例变量的值仍然是不可变字符串。
在这里插入图片描述


http://www.ppmy.cn/news/129892.html

相关文章

HP D360 G7 上架 排错   配置RAID

今天老大说&#xff1a;“机房有一个老爷机还闲着&#xff0c;”让我把它上架。平时搭建个杂七杂八的东西时用一用。于是问题就一个个的来了。 在机房忙活半天服务器通电后&#xff0c;等了半天。一直启动不起来&#xff0c;接着服务器开始哔哔&#xff01;哔哔&#xff01;叫个…

重学数据结构与算法

学习数据结构与算法的目的&#xff1a; 优化时间复杂度与空间复杂度 优化时间复杂度与空间复杂度 优化时间复杂度与空间复杂度 教程总纲&#xff1a; 暴力解法(模拟)、算法优化(递归/二分/排序/DP)、时刻转换(数据结构) 1.时间复杂度的核心方法论2.增删查——选取数据结构的基…

MyBatis——缓存

是一种临时存储少量数据至内存或者是磁盘的一种技术.减少数据的加载次数,可以降低工作量,提高程序响应速度 缓存的重要性是不言而喻的。mybatis的缓存将相同查询条件的SQL语句执行一遍后所得到的结果存在内存或者某种缓存介质当中&#xff0c;当下次遇到一模一样的查询SQL时候…

二维我的世界源代码(c++)免费复制

这几天整了一个二维我的世界的源代码 累死我了 &#xff08;跪求赞和评论&#xff01;&#xff01;&#xff09; &#xff08;跪求赞和评论&#xff01;&#xff01;&#xff09; &#xff08;跪求赞和评论&#xff01;&#xff01;&#xff09; 小的做东西真的不容易&…

Spring Boot 集成Kafka简单应用

说明&#xff1a;当前kafka的版本为2.13-2.8.1&#xff0c;Spring Boot的版本为2.7.6。 第一步&#xff1a;在pom.xml中引入下述依赖 <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId><ver…

算命全+冲害关系

<?php $znew paipan(); echo json_encode($z->fatemaps($xb, $yy, $mm, $dd, $hh, $mt 0, $ss 0)); /*** author hkargv139.com* 此日历转换及排盘类完全源于 */ class paipan{/*** 四柱是否区分 早晚子 时,true则23:00-24:00算成上一天*/public $zwz true;/*** 均…

CRNN-基于序列的(端到端)图像文本识别

文章目录 一、前言二、网络架构2.1 特征序列提取2.2. 序列标注2.3. 转录2.3.1 标签序列的概率2.3.2 无字典转录2.3.3 基于词典的转录 2.4. 网络训练4. 总结 一、前言 在现实世界中&#xff0c;稳定的视觉对象&#xff0c;如场景文字&#xff0c;手写字符和乐谱&#xff0c;往往…

Minio 搭建对象存储服务

文章目录 1 mino简介2 环境3 部署3.1 获取程序3.2 存储类别3.3 挂载硬盘3.4 单机部署3.4.1 部署及测试3.4.2 作为Linux Service启动 3.5 分布式集群扩容方案3.5.1 部署及测试3.5.2 作为Linux Service启动 3.6 多机部署&#xff0c;扩容支持 4 客户端及演示5 PrometheusGrafana …