iOS浅拷贝和深拷贝,copy和mutableCopy的区别

深拷贝和浅拷贝都是针对复合类型(对象,结构体指针)

一、浅拷贝和深拷贝

  1. 浅拷贝
    新对象是拷贝了原对象的地址(引用),指向的还是原来的对象,修改其中一个另一个也会跟着改变

  2. 深拷贝
    新对象是开辟了了一块新的地址,然后把原对象的值赋值给新对象,修改任意一个不会影响另一个对象(这也是区分浅拷贝和深拷贝地方)

苹果官方文档中的图解


object_copying_2x.png

二、copy和mutableCopy

copymutableCopy是foundation框架下的两种拷贝方式;

copy:返回一个不可变对象:
mutableCopy:返回一个可变对象

分别对应如下两个方法

需要分别遵守两个协议NSCopying,NSMutableCopying然后实现:

- (nonnull id)copyWithZone:(nullable NSZone *)zone {
    //...
}
- (nonnull id)mutableCopyWithZone:(nullable NSZone *)zone {
  //...
}

不然直接调用会crash,好在常用的字符串,数组等 苹果帮我门实现了这两个协议.

copymutableCopy分为可变和不可变,容器和非容器的情况

三、容器类,非容器类

容器是指实例为其他类的对象的集合的类,否则就是非容器类

iOS中的容器类包含:
NSArray,NSDictionary,NSSet
以及他们的可变版本
非容器类除了以上几种之外的都是非容器类
NSString

四、可变和不可变

大多数可可对象都是可变的,这意味着您可以更改其封装的值,但是有些是不可变的,并且在创建它们之后就无法更改其封装的值。不可变对象的主要好处是可以确保在使用它们时它们的值不会改变。一旦创建了一个不可变的对象,该对象表示的值在其整个运行期间将保持不变。但是您可以随时更改可变对象的封装值。您可以通过替换值的方法(“ setter”访问器方法)或以增量方式修改它们的方法来执行此操作

object_mutability_2x.png

在iOS中主要可变和不可变是针对容器类,和字符串,data而言的:
可变类如下:
NSMutableArray,NSMutableDictionary,NSMutableSet,NSMutableString...
可变:可变类都可以进行对应的集合操作(增,删,改,);
例如:数组

- (void)addObject:(ObjectType)anObject;//追加
- (void)insertObject:(ObjectType)anObject atIndex:(NSUInteger)index;//插入
- (void)removeLastObject;//删除最后一个
- (void)removeObjectAtIndex:(NSUInteger)index;//删除对应下标元素
- (void)replaceObjectAtIndex:(NSUInteger)index withObject:(ObjectType)anObject;//修改
......

不可变类不能进行以上对应的集合操作,执行相关操作会crash
不可变数组NSArray,有追加操作,但是追加之后产生一个新数组原来的数组还是没变

- (NSArray<ObjectType> *)arrayByAddingObject:(ObjectType)anObject;

五、copy和mutableCopy对应的几种情况:

1.1 不可变 - 非容器类

对可变字符串str1,分别copy, mutableCopy

    NSString *str1 = @"非容器类不可变";//常量字符串
    NSString *str2 = str1.copy;//浅拷贝-常量字符串
    NSString *str3 = str1.mutableCopy;//深拷贝-可变字符串

输出:

    str1:0x100002040,class:__NSCFConstantString//常量不可变字符串
    str2:0x100002040,class:__NSCFConstantString//常量不可变字符串
    str3:0x102300100,class:__NSCFString//可变字符串

结论:

copy本来就是产生一个不可变对象, str1被 copy之后产生的对象str2指向str1(浅拷贝),因为str1和str2都不能修改
mutableCopy要产生一个可变对象,但是原来的对象str1不可变,所以只能产生一个新的可变对象str3(深拷贝)

非容器-不可变对象 用copy的结果:发生浅拷贝,拷贝的对象不可变
非容器-不可变对象 用mutableCopy的结果:发生深拷贝,深拷贝的对象可变

1.2 可变-非容器类

对可变字符串mutStr1,分别copy, mutableCopy

NSMutableString *mutStr1 = [NSMutableString stringWithString:@"非容器类可变"];//可变字符
NSMutableString *mutStr2 = [mutStr1 copy];//深拷贝-不可变字符串
NSMutableString *mutStr3 = [mutStr1 mutableCopy];//深拷贝-可变字符串

输出:

mutStr1:0x100602040,class:__NSCFString //深拷贝-可变
mutStr2:0x100601180,class:__NSCFString //深拷贝-不可变
mutStr3:0x100600630,class:__NSCFString //深拷贝-可变

copy 产生一个不可变对象,mutStr1是一个可变字符串copy之后的mutStr2要不可变只能产生一个新的对象让其不可变(深拷贝)
对mutStr2执行可变操作会crash

'Attempt to mutate immutable object with appendString:'

mutableCopy 产生一个可变对象,mutStr1是一个可变字符串mutableCopy之后的mutStr3可变, 生成一个新的可变对象

结论:

非容器-可变对象 调用copy 后发生深拷贝,拷贝的对象不可变
非容器-可变对象 调用mutableCopy 后发生深拷贝,拷贝的对象可变

2.1容器类-不可变

对不可变数组array1,分别copy, mutableCopy

NSArray *array1 = @[@"1",@"2",@"3"];//不可变数组
NSArray *array2 = array1.copy;//浅拷贝,不可变数组
NSArray *array3 = array1.mutableCopy;//深拷贝,可变数组

输出:

array1:0x100748b00,class:__NSArrayI //浅拷贝-不可变
array2:0x100748b00,class:__NSArrayI //浅拷贝-不可变
array3:0x100748bd0,class:__NSArrayM //深拷贝-可变

分析过程同上:
容器类中保存的也是对象,这些对象也会跟着变为对应的浅拷贝和深拷贝吗?

for (id item in array1) {
        NSLog(@"array1-%p",item);
}
for (id item in array2) {
        NSLog(@"array2-%p",item);
}
for (id item in array3) {
        NSLog(@"array3-%p",item);
}

输出:

原对象
array1-0x100002048 //1
array1-0x100002068 //2
array1-0x100002088 //3
copy的对象
array2-0x100002048 //copy-1
array2-0x100002068 //copy-2
array2-0x100002088 //copy-3
mutableCopy的对象
array3-0x100002048 //mutableCopy-1
array3-0x100002068 //mutableCopy-2
array3-0x100002088 //mutableCopy-3

分析:原对象array1,copy对象array2,mutableCopy对象array3 数组保存的对象地址都是一样的,所以不可变容器的二层拷贝都是浅拷贝的结果

结论:

容器类-不可变对象:调用copy 发生浅拷贝,拷贝的对象不可变
容器类-不可变对象:调用mutableCopy 发生深拷贝,拷贝的对象可变
容器里的对象都是浅拷贝的

2.2容器类-可变

对可变数组array1,分别copy, mutableCopy

NSMutableArray *mutArray1 = [NSMutableArray arrayWithArray:@[@"1",@"2",@"3"]];//可变
NSMutableArray *mutArray2 = mutArray1.copy;//深拷贝-不可变
NSMutableArray *mutArray3 = mutArray1.mutableCopy;//深拷贝-可变

输出:

mutArray1:0x102d00200,class:__NSArrayM //深拷贝-可变
mutArray2:0x102d00310,class:__NSArrayI //深拷贝-不可变
mutArray3:0x102d00350,class:__NSArrayM //深拷贝-可变

分析同上:
通过打印各个对象的地址,数组内的对象都是浅拷贝(代码同上),看起来apple没有考虑容器的二层拷贝情况

结论:

容器类-可变:调用copy深拷贝 拷贝后产生的对象不可变
容器类-可变:调用mutableCopy深拷贝 拷贝后产生的对象可变
容器里的元素当然也是浅拷贝的,验证方法和2.1.1是一样的

可变和不可变对象之间发生拷贝是会创建新对象,所以会深拷贝

把以上分析结果整理成表格:

- 非容器-不可变对象 非容器-可变对象 容器-不可变对象 容器-可变对象
copy 浅拷贝-不可变 深拷贝-不可变 浅拷贝-不可变 深拷贝-不可变
mutableCopy 深拷贝-可变 深拷贝-可变 深拷贝-可变 深拷贝-可变

3.1. 自定义对象支持copy, mutableCopy

initWithArray: copyItems:会使NSArray中元素均执行copy方法,如果是未实现NSCopying定义的方法,就会发生Crash

///比如NSArray, flag参数为YES将执行下层深拷贝
- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;

NSCopying和NSMutableCopying都只有一个方法,实现如下:

- (nonnull id)copyWithZone:(nullable NSZone *)zone {
    
    id copyObject = [[self class] allocWithZone:zone];
    
    //如果有对应的属性,还需要对属性赋值
    //...
    return copyObject;
}

比较省事的写法:

- (nonnull id)copyWithZone:(nullable NSZone *)zone {
    
    id copyInstance = [[self class] allocWithZone:zone];
    //runtime 需要导入runtime库
    size_t instanceSize = class_getInstanceSize([self class]);
    
    memcpy((__bridge void *)(copyInstance), (__bridge const void *)(self), instanceSize);
    
    return copyInstance;
}

在Objective-C中,copy和mutableCopy方法由所有对象继承,用于执行复制;后者用于创建原始对象的可变类型。这些方法依次调用copyWithZone和mutableCopyWithZone方法来执行复制。对象必须实现相应的copyWithZone方法才能进行复制。

关于容器的浅拷贝和深拷贝苹果给的一张图解:


shallow_deep_copy_image.png

六、copy修饰符

6.1 用copy修饰NSMutableArray的情况

@property(nonatomic,copy)NSMutableArray *array;

默认起况下通过set方法赋值的时候,系统会在底层执行copy的行为,copy会产生一个不可变的深拷贝对象,所以我们在对数组array进行删除,修改,插入等操作使就会异常;

copy修饰的属性,赋值使系统会执行copy行为,参考底层源码:

static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}

解决办法有:

  1. 重写set方法,
  2. 懒加载初始化不要调用copy
  3. 用strong替换copy修饰符

6.2 copy不等于浅拷贝,mutableCopy也不等于深拷贝

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容