iOS 数组拷贝深度解析

作者: huoshe2019 | 来源:发表于2018-12-29 15:59 被阅读11次

    这几天公司上线一个项目,改bug过程中,就遇到一个数组拷贝问题,废了半天劲儿才解决掉,特此详细研究了一下。其场景大概如下:

    A数组中存放着好多个自定义模型Person,Person模型中又有一个数组属性modelArray,modelArray数组又包含另外一个模型Son。此时,需要将A数组元素拷贝到B数组中,并且修改B数组中元素,不能影响到A数组。

    首先,先不管上面的场景如何解决,因为文章末尾会给出具体解决方案。这里我们将由浅入深,从深拷贝和浅拷贝概念,到简单数组元素拷贝,再到模型数组元素拷贝,逐步进行分析。

    一、深拷贝和浅拷贝概念

    这里引入官方到一段话:

    There are two kinds of object copying: shallow copies and deep copies. The normal copy is a shallow copy that produces a new collection that shares ownership of the objects with the original. Deep copies create new objects from the originals and add those to the new collection.

    大致意思是:
    共有两种类型的对象拷贝:浅拷贝和深拷贝。普通拷贝是浅拷贝,它生成一个新集合,该集合与原有集合共同持有对象。深拷贝会从原有集合中生成新的对象,并把这些对象添加到新的集合中。
    简而言之,浅拷贝不会产生新的对象,深拷贝会产生新的对象

    图1 Shallow copies and deep copies

    二、装有基本类型元素的数组拷贝

    2.1、NSArray与copy

    NSArray *normalArray = [[NSArray alloc] initWithObjects:@"1",@"2",@"3", nil];
    id tempArray = [normalArray copy];
    [tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组"); 
    

    打印断点、查看结果如下:


    图2

    `结论:不可变数组进行copy,不会开辟新的内存空间,生成一个不可变对象,指向同一个数组

    2.2、NSMutableArray与copy

    NSMutableArray *normalArray = [[NSMutableArray alloc] initWithObjects:@"1",@"2",@"3", nil];
    id tempArray = [normalArray copy];
    [tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    normalArray[0] = @"1000";//修改数组元素
    NSLog(@"normalArray内存地址 = %p",normalArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempArray);
    NSLog(@"normalArray = %@",normalArray);
    NSLog(@"tempArrayOne = %@",tempArray);
    

    打印断点、查看结果如下:


    图3

    结论:可变数组进行copy,会开辟新的内存空间,生成一个新的不可变数组,两个数组之间不受任何影响

    2.3、NSArray与mutableCopy

    NSArray *normalArray = [[NSArray alloc] initWithObjects:@"1",@"2",@"3", nil];
    id tempArray = [normalArray mutableCopy];
    [tempArray isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    tempArray[0] = @"1000";//改变数组
    NSLog(@"normalArray内存地址 = %p",normalArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempArray);
    NSLog(@"normalArray内存地址 = %p",normalArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempArray); 
    

    打印断点、查看结果如下:

    图4
    结论:不可变数组进行mutableCopy,会开辟新的内存空间,生成一个可变数组、两个数组之间相互不影响

    2.4、NSMutableArray与mutableCopy

    NSMutableArray *normalArray = [[NSMutableArray alloc] initWithObjects:@"1",@"2",@"3", nil];
    id tempArrayOne = [normalArray mutableCopy];
    [tempArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    normalArray[0] = @"1000";
    NSLog(@"normalArray = %@",normalArray);
    NSLog(@"normalArray内存地址 = %p",normalArray);
    NSLog(@"tempArrayOne = %@",tempArrayOne);
    NSLog(@"tempArrayOne内存地址 = %p",tempArrayOne); 
    

    打印断点、查看结果如下:


    图5

    结论:可变数组进行mutableCopy,会开辟新的内存空间、生成一个可变数组、两个数组之间相互不影响

    小结:
    针对上述情况,在网上找到一个绘制好的列表,总结的很好,这里直接借用一下,在此谢过。

    图6

    三、装有模型元素的数组拷贝

    这里可以明确告诉大家,数组仍然遵循上述规则,但是模型是不拷贝的,要不然也不用这么费劲儿写这篇文章了。
    这里我们用装有模型的可变数组的mutableCopy进行验证,代码如下:

    //1、初始数组
    Person *onePerson = [[Person alloc] init];
    onePerson.name = @"onePerson";
    onePerson.age = 1;
    NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
    [normalModelArray addObject:onePerson];
    //2、拷贝数组
    id tempModelArrayOne = [normalModelArray mutableCopy];
    [tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    //3、打印模型信息
    NSLog(@"normalArray = %@",normalModelArray);
    NSLog(@"tempArrayOne = %@",tempModelArrayOne);
    NSLog(@"normalArray内存地址 = %p",normalModelArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne);
    

    打印断点、查看结果如下:


    图7

    结论:数组拷贝仍然遵循上面的规则,但是里面的模型是同一个对象,也就是说对象没有进行深拷贝

    四、模型深度拷贝最终解决方案

    4.1、方案介绍

    数组里面的模型不能拷贝,我们该怎么办?放心,官方已经给出答案:

    There are two ways to make deep copies of a collection. You can use the collection’s equivalent of initWithArray:copyItems: with YES as the second parameter. If you create a deep copy of a collection in this way, each object in the collection is sent a copyWithZone: message. If the objects in the collection have adopted the NSCopying protocol, the objects are deeply copied to the new collection, which is then the sole owner of the copied objects. If the objects do not adopt the NSCopying protocol, attempting to copy them in such a way results in a runtime error. However, copyWithZone: produces a shallow copy. This kind of copy is only capable of producing a one-level-deep copy. If you only need a one-level-deep copy, you can explicitly call for one as in Listing 2.
    Listing 2 Making a deep copy
    NSArray *deepCopyArray = [[NSArray alloc]initWithArray:someArray copyItems:YES];
    This technique applies to the other collections as well. Use the collection’s equivalent of initWithArray:copyItems: with YES as the second parameter.
    If you need a true deep copy, such as when you have an array of arrays, you can archive and then unarchive the collection, provided the contents all conform to the NSCoding protocol. An example of this technique is shown in Listing 3.
    Listing 3 A true deep copy
    NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

    大致意思如下:
    这里有两种方法可以实现深拷贝,第一种方法是initWithArray:copyItems: with YES as the second parameter。用这个方法进行拷贝,集合里面的模型就要实现copyWithZone:方法;如果不实现的话,就会报运行时错误。因为copyWithZone:是浅拷贝,所以这里只会对模型的基本属性进行拷贝。换句话说,模型本身的属性都会进行深拷贝,但是如果模型属性还包含模型,那这个方法就无济于事了。此时,只能用NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];

    4.2、方案实践

    看了官方的介绍,貌似有点茅塞顿开的感觉,可是不试一试怎么能够知道呢。
    这里我们将从三个方面对两个方法进行对比分析。

    4.2.1、initWithArray:copyItems

    • 1>基本模型
      代码如下:
    //1、初始数组
    Person *onePerson = [[Person alloc] init];
    onePerson.name = @"onePerson";
    NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
    [normalModelArray addObject:onePerson];
    //2、拷贝数组
    id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
    [tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    //3、改变其中一个元素信息
    onePerson.name = @"我改变了哈";
    //4、打印模型信息
    NSLog(@"normalArray = %@",normalModelArray);
    NSLog(@"tempArrayOne = %@",tempModelArrayOne);
    NSLog(@"normalArray内存地址 = %p",normalModelArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne); 
    

    打印结果如下:


    图8 copyItems(基本元素)

    结论:生成新的Person模型,开辟新的存储空间,Person模型基本元素相互不影响

    • 2>模型中含有模型
      代码如下:
     //1、初始数组
    Person *onePerson = [[Person alloc] init];
    onePerson.name = @"onePerson";
    onePerson.son.name = @"张三";
    NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
    [normalModelArray addObject:onePerson];
    //2、拷贝数组
    id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
    [tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    //3、改变其中一个元素信息
    onePerson.name = @"我改变了哈";
    onePerson.son.name = @"李四";
    //4、打印模型信息
    NSLog(@"normalArray = %@",normalModelArray);
    NSLog(@"tempArrayOne = %@",tempModelArrayOne);
    NSLog(@"normalArray内存地址 = %p",normalModelArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne);
    

    打印结果如下:


    图9 copyItems(模型中的模型).png

    结论:生成新的Son模型,开辟新的存储空间,Son模型基本元素相互不影响

    • 3>模型中的数组属性含有模型
      代码如下:
    //1、初始数组
    Person *onePerson = [[Person alloc] init];
    onePerson.name = @"onePerson";
    Son *son = [[Son alloc] init];
    son.name = @"儿子";
    [onePerson.modelArray addObject:son];
    NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
    [normalModelArray addObject:onePerson];
    //2、拷贝数组
    id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:normalModelArray copyItems:YES];
    [tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    //3、改变其中一个元素信息
    son.name = @"改变儿子";
    //4、打印模型信息
    NSLog(@"normalArray = %@",normalModelArray);
    NSLog(@"tempArrayOne = %@",tempModelArrayOne);
    NSLog(@"normalArray内存地址 = %p",normalModelArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne); 
    

    打印结果如下:


    图10 copyItems(模型中的数组).png

    结论:主模型数组中的模型不会开辟新的内存空间,仍然是同一个对象

    4.2.2、归档和解档

    • 1>基本模型
    //1、初始数组
    Person *onePerson = [[Person alloc] init];
    onePerson.name = @"onePerson";
    NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
    [normalModelArray addObject:onePerson];
    //2、归档和解档
    NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:normalModelArray]];
    id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:trueDeepCopyArray copyItems:YES];
    [tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    //3、改变其中一个元素信息
    onePerson.name = @"我改变了哈";
    //4、打印模型信息
    NSLog(@"normalArray = %@",normalModelArray);    NSLog(@"tempArrayOne = %@",tempModelArrayOne);
    NSLog(@"normalArray内存地址 = %p",normalModelArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne); 
    

    打印结果:


    图11 归档(基本元素).png

    结论:生成新的Person模型,开辟新的存储空间,Person模型基本元素相互不影响

    • 2>模型中含有模型
      代码如下:
    //1、初始数组
    Person *onePerson = [[Person alloc] init];
    onePerson.name = @"onePerson";
    onePerson.son.name = @"张三";
    NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
    [normalModelArray addObject:onePerson];
    //2、归档和接档
    NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:normalModelArray]];
    id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:trueDeepCopyArray copyItems:YES];
    [tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    //3、改变其中一个元素信息
    onePerson.name = @"我改变了哈";
    onePerson.son.name = @"李四";
    //4、打印模型信息
    NSLog(@"normalArray = %@",normalModelArray);
    NSLog(@"tempArrayOne = %@",tempModelArrayOne);
    NSLog(@"normalArray内存地址 = %p",normalModelArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne);
    

    打印结果如下:


    图12 归档(模型中的模型).png

    结论:生成新的Son模型,开辟新的存储空间,Son模型基本元素相互不影响

    • 3>模型中的数组属性含有模型
      代码如下:
    //1、初始数组
    Person *onePerson = [[Person alloc] init];
    onePerson.name = @"onePerson";
    Son *son = [[Son alloc] init];
    son.name = @"儿子";
    [onePerson.modelArray addObject:son];
    NSMutableArray *normalModelArray = [[NSMutableArray alloc] init];
    [normalModelArray addObject:onePerson];
    //2、归档和解档
    NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:normalModelArray]];
    id tempModelArrayOne = [[NSMutableArray alloc] initWithArray:trueDeepCopyArray copyItems:YES];
    [tempModelArrayOne isKindOfClass:[NSMutableArray class]] ? NSLog(@"tempArrayOne可变数组"):NSLog(@"tempArrayOne不可变数组");
    //3、改变其中一个元素信息
    son.name = @"改变儿子";
    //4、打印模型信息
    NSLog(@"normalArray = %@",normalModelArray);
    NSLog(@"tempArrayOne = %@",tempModelArrayOne);
    NSLog(@"normalArray内存地址 = %p",normalModelArray);
    NSLog(@"tempArrayOne内存地址 = %p",tempModelArrayOne);
    

    打印结果:


    图13 归档(模型中的数组).png

    结论:主模型数组中的模型会开辟新的内存空间,模型之间相互不影响

    五、总结

    5.1、方案

    通过initWithArray:copyItems归档解档可以对模型进行拷贝,具体如下:

    //方案1
    NSArray *deepCopyArray=[[NSArray alloc] initWithArray:someArray copyItems:YES];
    //方案2
    NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
    

    使用initWithArray:copyItems需要模型遵守NSCopying或NSMutableCopying协议并重写以下方法:

    - (id)copyWithZone:(NSZone *)zone
    {
        Person *person = [[[self class] allocWithZone:zone] init];
        person.name = [self.name copy];
        person.age = self.age;
        person.son = [self.son copy];
        person.modelArray = [self.modelArray copy];
        return person;
    }
    
    - (id)mutableCopyWithZone:(NSZone *)zone
    {
        Person *person = [[[self class] allocWithZone:zone] init];
        person.name = [self.name mutableCopy];
        person.age = self.age;
        person.son = [self.son mutableCopy];
        person.modelArray = [self.modelArray mutableCopy];
        return person;
    }
    

    使用归档和解档需要模型遵守NSCopying或NSMutableCopying协议并重写以下方法:

    - (id)initWithCoder: (NSCoder *)coder
    {
        if (self = [super init])
        {
            self.name = [coder decodeObjectForKey:@"name"];
            self.age = (int)[coder decodeIntegerForKey:@"age"];
            self.son = [coder decodeObjectForKey:@"son"];
            self.modelArray = [coder decodeObjectForKey:@"modelArray"];
        }
        return self;
    }
    
    - (void) encodeWithCoder: (NSCoder *)coder
    {
        [coder encodeObject:self.name forKey:@"name"];
        [coder encodeInteger:self.age forKey:@"age"];
        [coder encodeObject:self.son forKey:@"son"];
        [coder encodeObject:self.modelArray forKey:@"modelArray"];
    }
    

    备注: 如果模型中有嵌套子模型,子模型也需要实现上述方法,否则会报运行时错误。

    5.2、方案区别

    二者都可以对模型进行深拷贝,但是initWithArray:copyItems只能对一级模型进行深拷贝,也就是模型中含有数组模型,它就无能为力了。而利用归档和解档则不存在这样问题,无论模型嵌套多少层。

    参考网址:

    苹果官方文档

    iOS 图文并茂的带你了解深拷贝与浅拷贝

    相关文章

      网友评论

        本文标题:iOS 数组拷贝深度解析

        本文链接:https://www.haomeiwen.com/subject/pusjlqtx.html