深拷贝、浅拷贝

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

先讲讲写这篇博文的背景吧。
事情发生在一个风和日丽的下午。。。
需求大致如下:
从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):在完全复制操作时,对于被复制对象的每一层都是对象复制。

完。

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页