欢迎来到【微信扫码助手源码】【aide视频源码】【石头迷阵源码】nskeyedarchiver 源码-皮皮网网站!!!

皮皮网

【微信扫码助手源码】【aide视频源码】【石头迷阵源码】nskeyedarchiver 源码-皮皮网 扫描左侧二维码访问本站手机端

【微信扫码助手源码】【aide视频源码】【石头迷阵源码】nskeyedarchiver 源码

2024-11-19 06:28:41 来源:{typename type="name"/} 分类:{typename type="name"/}

1.Swift之Codable实战技巧
2.如何使用Multipeer Connectivity
3.iOS中copy,strong,retain,weak和assign的区别

nskeyedarchiver 源码

Swift之Codable实战技巧

       作者 | kingnight

       Part.1 引言

       本文将深入探讨Swift4.0引入的Codable新特性,它能将程序内部的数据结构序列化为可交换数据,亦能将通用数据格式反序列化为内部使用的数据结构,显著提升了对象与表示间转换的体验。以狐友iOS团队的实战经验为依据,本文将详细解析Codable使用过程中的微信扫码助手源码常见问题与解决方案。

       Part.2 简介

       Codable协议自Swift4.0起被引入,旨在取代NSCoding协议,支持结构体、枚举及类,并能将JSON等弱类型数据转换为代码中的强类型数据。Codable由Decodable与Encodable两个协议组成,分别定义了初始化与编码方法。Swift标准库中的多种类型如String、Int、Double及Foundation框架下的Data、Date、aide视频源码URL等,已默认支持Codable协议,仅需声明协议即可。

       以学生信息为例,通过声明支持Codable协议,学生信息对象就能默认支持init(from:)与encode(to:)方法,无需额外编写代码。本文将重点介绍使用JSON格式进行编码与解码的石头迷阵源码过程。

       Part.3 应用场景

       JSON格式广泛应用于网络数据传输,Codable协议能够将JSON数据转换为应用程序内部使用的格式,取代繁琐的手动解码过程,大幅减少编码与解码的重复工作。

       此外,Codable协议替代NSCoding协议,使得遵循Codable协议的对象能够无缝集成NSKeyedArchiver与NSKeyedUnarchiver进行数据持久化与反持久化操作,简化了原有流程,家谱祭祀源码显著提升了效率。

       Part.4 使用技巧

       4.1 嵌套对象、数组与字典

       Swift4支持条件一致性规则,当数组中的每个元素与字典中的键值均遵循Codable协议,整体对象即可默认遵循Codable协议。

       以班级对象为例,通过一致性的规则,即可实现对复杂数据结构的反向ea源码解码工作。

       4.2 空对象或空值处理

       在复杂业务场景下,数据结构可能包含空对象或空值,Codable提供灵活的处理方式。通过将对象属性设置为可选类型,Codable能够自动将空对象映射为nil,简化了处理逻辑。

       通过调整源对象属性类型为可选值,能够有效解决空对象或空值的解析问题。

       4.3 字段匹配与键值转换

       JSON中使用蛇形命名法(snake_case)命名键值,而Swift API设计中倾向于UpperCamelCase与lowerCamelCase。通过JSONDecoder的keyDecodingStrategy属性,可方便地在不同命名规范之间转换。

       对于键值不匹配的情况,可通过CodingKeys自定义映射规则,指定明确的字符串原始值,以确保正确解析。

       4.4 定制日期格式处理

       日期格式是数据处理中常见的需求,Codable提供灵活的日期解码机制。通过dateDecodingStrategy属性,可针对不同日期标准如ISO与RFC进行自定义解析。

       4.5 枚举值转换

       在移动应用开发中,模版类型字段常用于区别展示样式。通过实现字符串或整型数据到枚举类型的转换,可以简化代码逻辑并提升用户体验。

       4.6 动态键值结构处理

       当Web服务下发动态数据结构时,Codable提供灵活的init与encode方法,支持处理动态键值问题,简化了代码实现。

       4.7 特殊类型处理

       对于不遵循Codable协议的特殊类型,通过重写编码与初始化方法,可以实现与Codable协议的兼容性,避免潜在冲突。

       Part.5 总结

       引入Codable协议,简化了Swift程序中数据结构之间的转换过程,显著提升了开发效率。本文通过多种场景与实例解析了Codable的使用技巧与常见问题解决方法,希望对读者在实际开发中提供参考与启示。

如何使用Multipeer Connectivity

       æœ¬æ–‡ç”±éƒ­åŽ†æˆ[博客]翻译自nshipster中的Multipeer Connectivity一节。 Multipeer connectivity是一个使附近设备通过Wi-Fi网络、P2P Wi-Fi以及蓝牙个人局域网进行通信的框架。互相链接的节点可以安全地传递信息、流或是其他文件资源,而不用通过网络服务。

       Advertising & Discovering

       é€šä¿¡çš„第一步是让大家互相知道彼此,我们通过广播(Advertising)和发现(discovering)服务来实现。

       å¹¿æ’­ä½œä¸ºæœåŠ¡å™¨æœç´¢é™„近的节点,而节点同时也去搜索附近的广播。在许多情况下,客户端同时广播并发现同一个服务,这将导致一些混乱,尤其是在client-server模式中。

       æ‰€ä»¥ï¼Œæ¯ä¸€ä¸ªæœåŠ¡éƒ½åº”有一个类型(标示符),它是由ASCII字母、数字和“-”组成的短文本串,最多个字符。通常,一个服务的名字应该由应用程序的名字开始,后边跟“-”和一个独特的描述符号。(作者认为这和 com.apple.*标示符很像),就像下边:

       static NSString * const XXServiceType = @"xx-service";

       ä¸€ä¸ªèŠ‚点有一个唯一标示MCPeerID对象,使用展示名称进行初始化,它可能是用户指定的昵称,或是单纯的设备名称。

       MCPeerID *localPeerID = [[MCPeerID alloc] initWithDisplayName:[[UIDevice currentDevice] name]];

       èŠ‚点使用NSNetService或者Bonjour C API进行手动广播和发现,但这是一个特别深入的问题,关于手动节点管理可具体参见MCSession文档。

       Advertising

       æœåŠ¡çš„广播通过MCNearbyServiceAdvertiser来操作,初始化时带着本地节点、服务类型以及任何可与发现该服务的节点进行通信的可选信息。

       å‘现信息使用Bonjour TXT records encoded(according to RFC )发送。

       MCNearbyServiceAdvertiser *advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:localPeerID discoveryInfo:nil serviceType:XXServiceType]; advertiser.delegate = self; [advertiser startAdvertisingPeer];

       ç›¸å…³äº‹ä»¶ç”±advertiser的代理来处理,需遵从MCNearbyServiceAdvertiserDelegate协议。

       åœ¨ä¸‹ä¾‹ä¸­ï¼Œè€ƒè™‘到用户可以选择是否接受或拒绝传入连接请求,并有权以拒绝或屏蔽任何来自该节点的后续请求选项。

       #pragma mark - MCNearbyServiceAdvertiserDelegate - (void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void(^)(BOOL accept, MCSession *session))invitationHandler { if ([self.mutableBlockedPeers containsObject:peerID]) { invitationHandler(NO, nil); return; } [[UIActionSheet actionSheetWithTitle:[NSString stringWithFormat:NSLocalizedString(@"Received Invitation from %@", @"Received Invitation from { Peer}"), peerID.displayName] cancelButtonTitle:NSLocalizedString(@"Reject", nil) destructiveButtonTitle:NSLocalizedString(@"Block", nil) otherButtonTitles:@[NSLocalizedString(@"Accept", nil)] block:^(UIActionSheet *actionSheet, NSInteger buttonIndex) { BOOL acceptedInvitation = (buttonIndex == [actionSheet firstOtherButtonIndex]); if (buttonIndex == [actionSheet destructiveButtonIndex]) { [self.mutableBlockedPeers addObject:peerID]; } MCSession *session = [[MCSession alloc] initWithPeer:localPeerID securityIdentity:nil encryptionPreference:MCEncryptionNone]; session.delegate = self; invitationHandler(acceptedInvitation, (acceptedInvitation ? session : nil)); }] showInView:self.view]; }

       ä¸ºäº†ç®€å•èµ·è§ï¼Œæœ¬ä¾‹ä¸­ä½¿ç”¨äº†ä¸€ä¸ªå¸¦æœ‰block的actionsheet来作为操作框,它可以直接给invitationHandler传递信息,用以避免创建和管理delegate造成的过于凌乱的业务逻辑,以避免创建和管理自定义delegate object造成的过于凌乱的业务逻辑。这种方法可以用category来实现,或者改编任何一个CocoaPods里有效的实现。

       Creating a Session

       åœ¨ä¸Šé¢çš„例子中,我们创建了session,并在接受邀请连接时传递到节点。一个MCSession对象跟本地节点标识符、securityIdentity以及encryptionPreference参数一起进行初始化。

       MCSession *session = [[MCSession alloc] initWithPeer:localPeerID securityIdentity:nil encryptionPreference:MCEncryptionNone]; session.delegate = self;

       securityIdentity是一个可选参数。通过X.证书,它允许节点安全识别并连接其他节点。当设置了该参数时,第一个对象应该是识别客户端的SecIdentityRef,接着是一个或更多个用以核实本地节点身份的SecCertificateRef objects。

       encryptionPreference参数指定是否加密节点之间的通信。MCEncryptionPreference枚举提供的三种值是:

       MCEncryptionOptional:会话更喜欢使用加密,但会接受未加密的连接。

       MCEncryptionRequired:会话需要加密。

       MCEncryptionNone:会话不应该加密。

       å¯ç”¨åŠ å¯†ä¼šæ˜¾è‘—降低传输速率,所以除非你的应用程序很特别,需要对用户敏感信息的处理,否则建议使用MCEncryptionNone。

       MCSessionDelegate协议将会在发送和接受信息的部分被覆盖.

       Discovering

       å®¢æˆ·ç«¯ä½¿ç”¨MCNearbyServiceBrowser来发现广播,它需要local peer标识符,以及非常类似MCNearbyServiceAdvertiser的服务类型来初始化:

       MCNearbyServiceBrowser *browser = [[MCNearbyServiceBrowser alloc] initWithPeer:localPeerID serviceType:XXServiceType]; browser.delegate = self;

       å¯èƒ½ä¼šæœ‰å¾ˆå¤šèŠ‚点广播一个特定的服务,所以为了方便用户(或开发者),MCBrowserViewController将提供一个内置的、标准的方式来呈现链接到广播节点:

       MCBrowserViewController *browserViewController = [[MCBrowserViewController alloc] initWithBrowser:browser session:session]; browserViewController.delegate = self; [self presentViewController:browserViewController animated:YES completion: ^{ [browser startBrowsingForPeers]; }];

       å½“browser完成节点连接后,它将使用它的delegate调用browserViewControllerDidFinish:,以通知展示视图控制器--它应该更新UI以适应新连接的客户端。

       Sending & Receiving Information

       ä¸€æ—¦èŠ‚点彼此相连,它们将能互传信息。Multipeer Connectivity框架区分三种不同形式的数据传输:

       Messages是定义明确的信息,比如端文本或者小序列化对象。

       Streams 流是可连续传输数据(如音频,视频或实时传感器事件)的信息公开渠道。

       Resources是图片、电影以及文档的文件。

       Messages

       Messages使用-sendData:toPeers:withMode:error::方法发送。

       NSString *message = @"Hello, World!"; NSData *data = [message dataUsingEncoding:NSUTF8StringEncoding]; NSError *error = nil; if (![self.session sendData:data toPeers:peers withMode:MCSessionSendDataReliable error:&error]) { NSLog(@"[Error] %@", error); }

       é€šè¿‡MCSessionDelegate方法 -sessionDidReceiveData:fromPeer:收取信息。以下是如何解码先前示例代码中发送的消息:

       #pragma mark - MCSessionDelegate - (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID { NSString *message = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"%@", message); }

       å¦ä¸€ç§æ–¹æ³•æ˜¯å‘送NSKeyedArchiver编码的对象:

       id <NSSecureCoding> object = // ...; NSData *data = [NSKeyedArchiver archivedDataWithRootObject:object]; NSError *error = nil; if (![self.session sendData:data toPeers:peers withMode:MCSessionSendDataReliable error:&error]) { NSLog(@"[Error] %@", error); } #pragma mark - MCSessionDelegate - (void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID { NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data]; unarchiver.requiresSecureCoding = YES; id object = [unarchiver decodeObject]; [unarchiver finishDecoding]; NSLog(@"%@", object); }

       ä¸ºäº†é˜²èŒƒå¯¹è±¡æ›¿æ¢æ”»å‡»,设置requiresSecureCoding为YES是很重要的,这样如果根对象类没有遵从<NSSecureCoding>,就会抛出一个异常。欲了解更多信息,请参阅[NSHipster article on NSSecureCoding]。

       Streams

       Streams 使用 -startStreamWithName:toPeer:创建:

       NSOutputStream *outputStream = [session startStreamWithName:name toPeer:peer]; stream.delegate = self; [stream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [stream open]; // ...

       Streams通过MCSessionDelegate的方法session:didReceiveStream:withName:fromPeer:来接收:

       #pragma mark - MCSessionDelegate - (void)session:(MCSession *)session didReceiveStream:(NSInputStream *)stream withName:(NSString *)streamName fromPeer:(MCPeerID *)peerID { stream.delegate = self; [stream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; [stream open]; }

       è¾“入和输出的streams必须安排好并打开,然后才能使用它们。一旦这样做,streams就可以被读出和写入。

       Resources

       Resources 发送使用 -sendResourceAtURL:withName:toPeer:withCompletionHandler::

       NSURL *fileURL = [NSURL fileURLWithPath:@"path/to/resource"]; NSProgress *progress = [self.session sendResourceAtURL:fileURL withName:[fileURL lastPathComponent] toPeer:peer withCompletionHandler:^(NSError *error) { NSLog(@"[Error] %@", error); }];

       è¿”回的NSProgress对象可以是通过KVO(Key-Value Observed)来监视文件传输的进度,并且它提供取消传输的方法:-cancel。

       æŽ¥æ”¶èµ„源实现MCSessionDelegate两种方法:-session:didStartReceivingResourceWithName:fromPeer:withProgress: 和 -session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:

       #pragma mark - MCSessionDelegate - (void)session:(MCSession *)session didStartReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID withProgress:(NSProgress *)progress { // ... } - (void)session:(MCSession *)session didFinishReceivingResourceWithName:(NSString *)resourceName fromPeer:(MCPeerID *)peerID atURL:(NSURL *)localURL withError:(NSError *)error { NSURL *destinationURL = [NSURL fileURLWithPath:@"/path/to/destination"]; NSError *error = nil; if (![[NSFileManager defaultManager] moveItemAtURL:localURL toURL:destinationURL error:&error]) { NSLog(@"[Error] %@", error); } }

       å†æ¬¡è¯´æ˜Žï¼Œåœ¨ä¼ è¾“期间NSProgress parameter in -session:didStartReceivingResourceWithName:fromPeer:withProgress:允许接收节点来监控文件传输进度。在-session:didFinishReceivingResourceWithName:fromPeer:atURL:withError:中,delegate的责任是从临时localURL移动文件至永久位置。

       Multipeer是突破性的API,其价值才刚刚开始被理解。虽然完整的支持功能比如AirDrop目前仅限于最新的设备,你应该会看到它将成为让所有人盼望的功能。

iOS中copy,strong,retain,weak和assign的区别

       ã€€ã€€copy与retain的区别:

       ã€€ã€€copy是创建一个新对象,retain是创建一个指针,引用对象计数加1。Copy属性表示两个对象内容相同,新的对象retain为1 ,与旧有对象的引用计数无关,旧有对象没有变化。copy减少对象对上下文的依赖。

       ã€€ã€€retain属性表示两个对象地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain值+1也就是说,retain 是指针拷贝,copy 是内容拷贝。

       ã€€ã€€

       ã€€ã€€å½“然在ios中并不是所有的对象都支持copy,mutableCopy,遵守NSCopying 协议的类可以发送copy消息,遵守NSMutableCopying 协议的类才可以发送mutableCopy消息。假如发送了一个没有遵守上诉两协议而发送 copy或者 mutableCopy,那么就会发生异常。但是默认的ios类并没有遵守这两个协议。如果想自定义一下copy 那么就必须遵守NSCopying,并且实现 copyWithZone: 方法,如果想自定义一下mutableCopy 那么就必须遵守NSMutableCopying,并且实现 mutableCopyWithZone: 方法。

       ã€€ã€€é¦–先我们需要有这样的一个前提:

       ã€€ã€€ï¼»array addObject:obj];

       ã€€ã€€è¿™æ ·obj的引用计数会增加1,如果使用remove则obj的引用计数会减一。

       ã€€ã€€ios对集合的内存处理就是这样的。

       ã€€ã€€é‚£ä¹ˆï¼Œå‡è®¾obj只被array拥有:

       ã€€ã€€id temp = [array objectAtIndex:0];

       ã€€ã€€[array removeObjectAtIndex:0];

       ã€€ã€€å¦‚果你再要使用temp就会出错,因为这个时候obj已经被释放了。

       ã€€ã€€ï¼ˆæé†’一下,如果用NSString做测试,要注意@“abc”是常量 :-) )

       ã€€ã€€ç”±äºŽåœ¨ç¨‹åºä¸­ç»å¸¸ä¼šé‡åˆ°é›†åˆç±»çš„传值,所以,简单的retain未必够用,需要对集合内容的拷贝,也就是深拷贝。

       ã€€ã€€ä¸‹é¢æˆ‘们就来探讨一下。

       ã€€ã€€Ios提供了copy和mutablecopy方法,顾名思义,copy就是复制了一个imutable的对象,而mutablecopy就是复制了一个mutable的对象。以下将举几个例子来说明。

       ã€€ã€€1. 系统的非容器类对象

       ã€€ã€€è¿™é‡ŒæŒ‡çš„是NSString,NSNumber等等一类的对象。

       ã€€ã€€NSString *string = @"origion";

       ã€€ã€€NSString *stringCopy = [string copy];

       ã€€ã€€NSMutableString *stringMCopy = [string mutableCopy];

       ã€€ã€€[stringMCopy appendString:@"!!"];

       ã€€ã€€æŸ¥çœ‹å†…存可以发现,string和stringCopy指向的是同一块内存区域(又叫apple弱引用weak reference),此时stringCopy的引用计数和string的一样都为2。而stringMCopy则是我们所说的真正意义上的复制,系统为其分配了新内存,但指针所指向的字符串还是和string所指的一样。

       ã€€ã€€å†çœ‹ä¸‹é¢çš„例子:

       ã€€ã€€NSMutableString *string = [NSMutableString stringWithString: @"origion"];

       ã€€ã€€NSString *stringCopy = [string copy];

       ã€€ã€€NSMutableString *mStringCopy = [string copy];

       ã€€ã€€NSMutableString *stringMCopy = [string mutableCopy];

       ã€€ã€€[mStringCopy appendString:@"mm"];//error

       ã€€ã€€[string appendString:@" origion!"];

       ã€€ã€€[stringMCopy appendString:@"!!"];

       ã€€ã€€ä»¥ä¸Šå››ä¸ªNSString对象所分配的内存都是不一样的。但是对于mStringCopy其实是个imutable对象,所以上述会报错。

       ã€€ã€€å¯¹äºŽç³»ç»Ÿçš„非容器类对象,我们可以认为,如果对一不可变对象复制,copy是指针复制(浅拷贝)和mutableCopy就是对象复制(深拷贝)。如果是对可变对象复制,都是深拷贝,但是copy返回的对象是不可变的。

       ã€€ã€€2. 系统的容器类对象

       ã€€ã€€æŒ‡NSArray,NSDictionary等。对于容器类本身,上面讨论的结论也是适用的,需要探讨的是复制后容器内对象的变化。

       ã€€ã€€//copy返回不可变对象,mutablecopy返回可变对象

       ã€€ã€€NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];

       ã€€ã€€NSArray *arrayCopy1 = [array1 copy];

       ã€€ã€€//arrayCopy1是和array同一个NSArray对象(指向相同的对象),包括array里面的元素也是指向相同的指针

       ã€€ã€€NSLog(@"array1 retain count: %d",[array1 retainCount]);

       ã€€ã€€NSLog(@"array1 retain count: %d",[arrayCopy1 retainCount]);

       ã€€ã€€NSMutableArray *mArrayCopy1 = [array1 mutableCopy];

       ã€€ã€€//mArrayCopy1是array1的可变副本,指向的对象和array1不同,但是其中的元素和array1中的元素指向的是同一个对象。mArrayCopy1还可以修改自己的对象

       ã€€ã€€[mArrayCopy1 addObject:@"de"];

       ã€€ã€€[mArrayCopy1 removeObjectAtIndex:0];

       ã€€ã€€array1和arrayCopy1是指针复制,而mArrayCopy1是对象复制,mArrayCopy1还可以改变期内的元素:删除或添加。但是注意的是,容器内的元素内容都是指针复制。

       ã€€ã€€ä¸‹é¢ç”¨å¦ä¸€ä¸ªä¾‹å­æ¥æµ‹è¯•ä¸€ä¸‹ã€‚

       ã€€ã€€NSArray *mArray1 = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];

       ã€€ã€€NSArray *mArrayCopy2 = [mArray1 copy];

       ã€€ã€€NSLog(@"mArray1 retain count: %d",[mArray1 retainCount]);

       ã€€ã€€NSMutableArray *mArrayMCopy1 = [mArray1 mutableCopy];

       ã€€ã€€NSLog(@"mArray1 retain count: %d",[mArray1 retainCount]);

       ã€€ã€€//mArrayCopy2,mArrayMCopy1和mArray1指向的都是不一样的对象,但是其中的元素都是一样的对象——同一个指针

       ã€€ã€€//一下做测试

       ã€€ã€€NSMutableString *testString = [mArray1 objectAtIndex:0];

       ã€€ã€€//testString = @"1a1";//这样会改变testString的指针,其实是将@“1a1”临时对象赋给了testString

       ã€€ã€€[testString appendString:@" tail"];//这样以上三个数组的首元素都被改变了

       ã€€ã€€ç”±æ­¤å¯è§ï¼Œå¯¹äºŽå®¹å™¨è€Œè¨€ï¼Œå…¶å…ƒç´ å¯¹è±¡å§‹ç»ˆæ˜¯æŒ‡é’ˆå¤åˆ¶ã€‚如果需要元素对象也是对象复制,就需要实现深拷贝。

       ã€€ã€€NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"first"],[NSStringstringWithString:@"b"],@"c",nil];

       ã€€ã€€NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];

       ã€€ã€€NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:

       ã€€ã€€[NSKeyedArchiver archivedDataWithRootObject: array]];

       ã€€ã€€trueDeepCopyArray是完全意义上的深拷贝,而deepCopyArray则不是,对于deepCopyArray内的不可变元素其还是指针复制。或者我们自己实现深拷贝的方法。因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。除非你对容器内的元素重新赋值,否则指针复制即已足够。举个例子,[[array objectAtIndex:0]appendstring:@”sd”]后其他的容器内对象并不会受影响。[[array objectAtIndex:1]和[[deepCopyArray objectAtIndex:0]尽管是指向同一块内存,但是我们没有办法对其进行修改——因为它是不可改变的。所以指针复制已经足够。所以这并不是完全意义上的深拷贝,但是apple的官方文档将其列为deep copy了,并添加了copy和mutablity的关系说明,故在此做一说明

       g