深拷贝、浅拷贝

来源:互联网 发布:50量子计算机 ibm 知乎 编辑:程序博客网 时间:2024/05/16 18:58

关于深拷贝和浅拷贝的一些笔记

先讲讲写这篇博文的背景吧。
事情发生在一个风和日丽的下午。。。
需求大致如下:
从A页面跳转到B页面,然后跳转时传递了一个NSObject类的数据源(包含数组),在B页面中需要展示数据源中的数据,接着可能修改数据源数组中的某一项或者某几项数据。然后在点保存按钮时回传给A页面,点返回按钮时不改变数据源数据。
BUG描述如下:
1.A页面跳转到B页面,B页面显示的数据正确;
2.B页面修改过某项数据后,点保存按钮后回传给A页面,接着再跳转到B页面,此时数据显示正确;
3.经过2操作后再次修改B中数据,点击返回按钮,回到A页面(此时应该是不需要保存修改的数据),接着再调到B页面,发现B页面显示的是刚才不需要保存的数据。
分析出现BUG的原因
1.在最开始的需求下,我写传递的数据源是一个单利。
2.在数据的传递时,数据源中有数组的存在,在给数组赋值时用了addObjectFromArray这个方法进行数据的拷贝。

写个简单的点的代码意思一下吧。怕自己到时候也不记得自己上面说的是什么。

A.h

#import <UIKit/UIKit.h>@interface HAViewController : UIViewController{    NSArray* m_arrCollcations;    NSMutableArray* m_arrSelectedData;}@property (nonatomic,strong) NSArray* propCollcations;@property (nonatomic,strong) NSMutableArray* propSelectedData;@end

A.m

#import "HItemData.h"#import "HProductData.h"-(void)viewWillAppear:(BOOL)animated{    [super viewWillAppear:animated];     if (m_arrSelectedData && m_arrSelectedData.count > 0) {        HItemData* pItemData = [m_arrCollcations objectAtIndexCheck:m_nIndex];        [pItemData.propProducts removeAllObjects];        [pItemData.propProducts addObjectsFromArray:m_arrSelectedData];        [m_arrSelectedData removeAllObjects];        [m_pTableView reloadData];    }}//跳转方法-(void)ChangeBtnClick:(NSInteger)argIndex{    m_nProductIndex = argIndex;    HItemData* pItemData = [[HItemData alloc] init];    HItemData.propItemName = [[m_arrCollcations objectAtIndexCheck:m_n    Index] propItemName];    [pItemData.propProducts addObjectsFromArray:[[m_arrCollcations objectAtIndexCheck:m_nIndex] propProducts]];    HBViewController* pBVC = [[HBViewController alloc] init];    pBVC.propPushItemData = pItemData;    [self.navigationController pushViewController:pBVC animated:YES];}

B.h

@class HItemData;@interface HBViewController : UIViewController{    HItemData* m_pPushItemData;//传递过来的Data}@property (nonatomic,strong) HItemData * propPushItemData;

B.m

#import "HItemData.h"#import "HProductData.h"@interface HBViewController (){    HItemData* m_pItemData;}@end@implementation HBViewController@synthesize propPushItemData = m_pPushItemData;- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view.    m_pItemData = [[HItemData alloc] init];    [self PrepareItemData:m_pItemData withOldData:m_pPushItemData];   }- (void)PrepareItemData:(HItemData*)argNewData withOldData:(HItemData*)argOldData{    argNewData.propItemName = argOldData.propItemName;    [argNewData.propProducts addObjectsFromArray:argOldData.propProducts];}/**    然后在B.m的文件下写一些改变m_pItemData下的HProductData的属性,实在不愿编了,暂虑吧。*///确认按钮- (void)makeSureButtonClick{    for (HAViewController *temp in self.navigationController.viewControllers) {        if ([temp isKindOfClass:[HAViewController class]]) {            temp.propSelectedData = m_pItemData.propProducts;            [self.navigationController popToViewController:temp animated:YES];        }    }}//返回按钮(不保存数据)- (void)backButtonClick{    [self.navigationController popViewControllerAnimated:YES];}

HItemData.h

@interface HItemData : NSObject{    NSString* m_strItemName;    NSMutableArray* m_arrProducts;//里面存很多个HProductData}@property (nonatomic, copy ) NSString * propItemName;@property (nonatomic,strong) NSMutableArray* propProducts;

HItemData.m

@implementation HItemData@synthesize propItemName = m_strItemName;@synthesize propProducts = m_arrProducts;- (id)init{    if(self = [super init])    {        m_strItemName = @"";        m_arrProducts = [[NSMutableArray alloc] init];    }    return self;}

HProductData.h

@interface HProductData : NSObject{    NSString* m_strProductName;    NSString* m_strProductPrice;}@property (nonatomic, copy ) NSString * propProductName;@property (nonatomic, copy ) NSString * propProductPrice;

HProductData.m

@implementation HProductData@synthesize propProductName = m_strProductName;@synthesize propProductPrice = m_strProductPrice;- (id)init{    if(self = [super init])    {        m_strProductName = @"";        m_strProductPrice = @"";    }    return self;}

源代码不太方便传,所以上面的是自己现编的,大致意思就是这样。
然后在实际运行时,就有可能会出现篇头出现的bug,然后就解决呗,想来想去只有可能是addObjectFromArray只是浅拷贝,只拷贝了指针地址,所以在B页面改来改去改的还是同一个东西。
先说说我是怎么解决的吧。

在HProductData.m中

@interface HProductData : NSObject<NSCopying>

在HProductData.m页面里添加下面代码

- (id)copyWithZone:(nullable NSZone *)zone{    HProductData *model = [[[self class] allocWithZone:zone] init];    model.m_strProductName = m_strProductName;    model.m_strProductPrice = m_strProductPrice;    return model;}

然后将B.m中的PrepareItemData:withOldData:方法改下

- (void)PrepareItemData:(HItemData*)argNewData withOldData:(HItemData*)argOldData{    argNewData.propItemName = argOldData.propItemName;    NSArray* arrOldProducts = [[NSArray alloc] initWithArray:argOldData.propProducts copyItems:YES]    [argNewData.propProducts addObjectsFromArray:arrOldProducts];}

弱弱的说句,上面的实际运行不知道会不会出现我的bug,因为是现编的,见谅,但是我的解决方法呢,是我上面写的。勿喷!

由此接入一些自己的理解
addObjectsFromArray或者addObject这些数组添加数据的方法会让添加的数据引用计数加一,也就是retain操作(创建一个指针,然后复制指针),相反remove只是引用计数减一
这也就导致我出现BUG的原因,所以我需要内容拷贝。
而内容拷贝就是copy了,copy又有copy和mutableCopy之分。
举个例子
这里写图片描述

这里可以看到copy完后指向同一块内存区域(weak reference弱引用)
strMutableCopy则是我们所说的真正意义上的复制,系统为其分配了新内存,但指针所指向的字符串还是和string所指的一样。

总结如下:
1.retain:始终是浅复制。引用计数每次加一。返回对象是否可变与被复制的对象保持一致。
2.copy:对于可变对象为深复制,引用计数不改变;对于不可变对象是浅复制, 引用计数每次加一。始终返回一个不可变对象。 (但对于容器对象本身来说是深拷贝。)
3.mutableCopy:始终是深复制,引用计数不改变。始终返回一个可变对象。(对于容器对象本身来说也是深拷贝。)

好吧,一步步往前扒。
因为我的数据源是一个所谓的自定义对象,所以不能直接对我代码中的HProductData进行copy操作,那么我们自己要实现NSCopying,NSMutableCopying这样就能调用copy和mutablecopy了。也就有了HProductData.h类中的修改
接着就有了刚才改BUG时重写的copyWithZone方法了。
但是这里会想,我重写的copyWithZone方法里也没有copy操作或者mutablecopy操作。这是为什么呢。

因为
@property (nonatomic, copy ) NSString * propProductName;
已经替我们完成了。
@property即调用了
- (NSString*) propProductName
{
return propProductName;
}
- (void)setpropProductName:(NSString *)propProductName
{
_propProductName = [propProductName copy];
}

然后我在B.m中写了个initWithArray:copyItems:方法。这里解释下

集合的深复制有两种方法。可以用 initWithArray:copyItems: 将第二个参数设置为YES即可深复制,就如我上面的代码。
如果你用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。如果集合里的对象遵循 NSCopying 协议,那么对象就会被深复制到新的集合。如果对象没有遵循 NSCopying 协议,而尝试用这种方法进行深复制,会在运行时出错。copyWithZone: 这种拷贝方式只能够提供一层内存拷贝(one-level-deep copy),而非真正的深复制。

第二个方法是将集合进行归档(archive),然后解档(unarchive),如:

NSData* pData = [NSKeyedArchiver archivedDataWithRootObject:oldArray];NSArray *arrTrueDeepCopy = [NSKeyedUnarchiver unarchiveObjectWithData:pData];

如果在多层数组中,对第一层进行内容拷贝,其它层进行指针拷贝,这种情况苹果认为这种复制不是真正的深复制,而是将其称为单层深复制(one-level-deep copy)。
针对于上面来说有人会这么划分:
浅复制(shallow copy):在浅复制操作时,对于被复制对象的每一层都是指针复制。
深复制(one-level-deep copy):在深复制操作时,对于被复制对象,至少有一层是深复制。
完全复制(real-deep copy):在完全复制操作时,对于被复制对象的每一层都是对象复制。

完。

0 0
原创粉丝点击