对象复制与拷贝

copy与mutablecopy

copy与mutablecopy的简介

copymutablecopy都是NSObeject所提供用来复制对象的方法,但它们有着不同的行为,主要区别如下:

  1. copy 方法:
  • copy 方法用于创建一个不可变的副本,无论原始对象是可变的还是不可变的。
  • 对于不可变对象,copy 方法仅仅是增加了引用计数,返回的仍然是原始对象本身。
  • 对于可变对象,copy 方法会创建一个新的不可变对象,内容与原始对象相同。

mutableCopy 方法:

  • mutableCopy 方法用于创建一个可变的副本,无论原始对象是可变的还是不可变的。
  • 对于不可变对象,mutableCopy 方法会创建一个新的可变对象,内容与原始对象相同。
  • 对于可变对象,mutableCopy 方法会创建一个新的可变对象,内容与原始对象相同。

总结一下,主要区别在于:

  • copy 返回的是不可变副本,而 mutableCopy 返回的是可变副本。
  • copy 对不可变对象和可变对象的行为不同,而 mutableCopy 则对所有对象的行为都一致。

示例:

不可变对象的复制
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSString* str1 = @"deer";
        NSString* str2 = str1;
        NSString* strcopy = [str1 copy];
        NSString* strmutablecopy = [str1 mutableCopy];
        NSLog(@"%p",str1);
        NSLog(@"%p",str2);
        NSLog(@"%p",strcopy);
        NSLog(@"%p",strmutablecopy);
    }
    return 0;
}

image-20240507192642271

不难看出来,str1,str2, strcopy ,指向的都为同一内存区域,为浅拷贝,由于mutablecopy生成了新的NSMutableString所以系统为其重新开辟一块空间,进行了深拷贝。

可变对象的复制
#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableString *mstr = [NSMutableString stringWithString:@"hello"];
        NSString *mstrcopy1 = [mstr copy];
        NSMutableString *mstrcopy2 = [mstr copy];
        NSMutableString *mstrmutablecopy = [mstr mutableCopy];
        [mstr appendString:@",aa"];
        //[mstrcopy2 appendString:@",bb"];
        [mstrmutablecopy appendString:@",cc"];
        NSLog(@"%p",mstr);
        NSLog(@"%p",mstrcopy1);
        NSLog(@"%p",mstrcopy2);
        NSLog(@"%p",mstrmutablecopy);
    }
    return 0;
}

对于注释的那一句代码,由于mstrcopy是通过copy方法得来,类型为NSString是不可变的,所以后面无法进行追加。可以发现通过copy得到的字符串指向的是同一片地址,但是复制得到的地址相对于原来的mstr都是不同的,所以我们可以知道对于可变对象进行copymutablecopy都是进行深拷贝。

NSCopying和NSMutableCopying协议

虽然NSObject虽然都给出了copymutablecopy的方法,但是不一定在任何时候都能够使用,为了确保一个对象可以调用copy方法复制得到自身的不可变副本,我们需要做如下事情

  • 让该类实现NSCopying协议
  • 让该类实现copyWithZone方法

对于mutablecopy也是同理

  • 实现NSMutableCopying协议
  • 让该类实现mutableCopyWithZone方法
#import <Foundation/Foundation.h>

@interface MyObject : NSObject <NSCopying>

@property (nonatomic, strong) NSMutableString *name;
@property (nonatomic, assign) int age;

@end

@implementation MyObject

- (id)copyWithZone:(NSZone *)zone {
    MyObject *copy = [[[self class] allocWithZone:zone] init];
    // 递归复制子对象
    copy.name = self.name;
    copy.age = self.age;
    return copy;
}

-(NSString*)description {
    return [NSString stringWithFormat:@"name = %@, age = %d", self.name, self.age];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建原始对象
        MyObject *org = [MyObject new];
        org.name = [NSMutableString stringWithString:@"aa"];
        org.age = 10;
        NSLog(@"%@",org);
        MyObject *copy1 = [org copy];
        NSLog(@"%@",copy1);
        copy1.name = [NSMutableString stringWithString:@"bb"];
        copy1.age = 20;
      	NSLog(@"%@",org);
        NSLog(@"%@",copy1);
    }
    return 0;
}

从以上程序我们看出来,我们对org进行一次副本复制,对得到的copy的成员变量进行赋值,对org不会产生影响。

image-20240507221647973

#import <Foundation/Foundation.h>

@interface MyObject : NSObject <NSCopying>

@property (nonatomic, strong) NSMutableString *name;
@property (nonatomic, assign) int age;

@end

@implementation MyObject

- (id)copyWithZone:(NSZone *)zone {
    MyObject *copy = [[[self class] allocWithZone:zone] init];
    // 递归复制子对象
    copy.name = self.name;
    copy.age = self.age;
    return copy;
}

-(NSString*)description {
    return [NSString stringWithFormat:@"name = %@, age = %d", self.name, self.age];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建原始对象
        MyObject *org = [MyObject new];
        org.name = [NSMutableString stringWithString:@"aa"];
        org.age = 10;
        NSLog(@"%@",org);
        MyObject *copy1 = [org copy];
        NSLog(@"%@",copy1);

    }
    return 0;
}

深复刻和浅复刻

深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是在编程中经常遇到的两个概念,它们描述了在复制对象时复制的内容的不同程度。

浅拷贝(Shallow Copy):

浅拷贝是指将一个对象复制到一个新的对象中,即将对内存指针的复制,并增加其引用计数。如果对象包含其他对象的引用,浅拷贝将不会复制这些引用的对象,而是将其引用复制到新对象中。因此,新对象和原对象中的引用指向相同的对象。如果其中一个对象修改了共享对象,另一个对象也会受到影响。当使用A指针改变内容时,B指针指向的内容也会跟着改变

image-20240507191217818

深拷贝(Deep Copy):

深拷贝是指将一个对象递归复制到一个新的对象中,开辟一片新的空间,并且复制所有引用的对象,直至没有任何共用的部分。这意味着,新对象中的每个对象都是原始对象的副本,而不是共享的。因此,如果其中一个对象修改了它指针所指向的对象,另一个对象不会受到影响。

image-20240507191437571

对于上图来说,分配了新的内存,改变A指针指向的值,不会影响到指针B指向值的内容。

通常情况下,浅拷贝的效率比深拷贝高,因为它不需要递归地复制所有引用的对象。但是,在需要保持对象之间的独立性时,深拷贝是必需的。

我们再来探究一下我们刚刚看到的例子,

#import <Foundation/Foundation.h>

@interface MyObject : NSObject <NSCopying>

@property (nonatomic, strong) NSMutableString *name;
@property (nonatomic, assign) int age;

@end

@implementation MyObject

- (id)copyWithZone:(NSZone *)zone {
    MyObject *copy = [[[self class] allocWithZone:zone] init];
    // 递归复制子对象
    copy.name = self.name;
    copy.age = self.age;
    return copy;
}

-(NSString*)description {
    return [NSString stringWithFormat:@"name = %@, age = %d", self.name, self.age];
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建原始对象
        MyObject *org = [MyObject new];
        org.name = [NSMutableString stringWithString:@"aa"];
        org.age = 10;
        NSLog(@"%@",org);
        MyObject *copy1 = [org copy];
        NSLog(@"%@",copy1);
        [copy1.name replaceCharactersInRange:NSMakeRange(0,2) withString:@"bb"];
        copy1.age = 20;
        NSLog(@"%@",copy1);
        NSLog(@"%@",org);
    }
    return 0;
}



我们的程序输出以下结果。

似乎很奇怪,我们通过repalce的函数去修改字符串,使得orgcopy的字符串内容都发生了改变,想要解决这个问题我们可以画一个图来辅助我们进行理解。

image-20240508200450863

对于即将被复制的org来说,变量name之中存储的是字符串的地址指针,而不是字符串本身,如果进行copy.name = self.name; copy.age = self.age;,那么只是将字符串的地址赋给copy之中的name属性,实际上两者指针都指向一个相同的NSMutableString类型字符串。

image-20240508201004905

对于这种复制模式我们就称之为浅复制,在内存中复制一个对象,但是原来的和复制得到的内容指向一个相同的对象,也就是说两个对象都存在依然存在的部分

那如果要实现深复制呢?前面说过深复制不仅会对对象本身进行复制,而且还会"递归"复制每一个指针的属性,直到两者没有共同的部分。我们将copyWithZone的代码进行一些修改就能实现深复制的原理。

- (id)copyWithZone:(NSZone *)zone {
    MyObject *copy = [[[self class] allocWithZone:zone] init];
    // 递归复制子对象
    copy.name = [self.name mutableCopy];
    copy.age = self.age;
    return copy;
}

我们将原对象的属性值复制了一份可变的副本,再将新的可变副本的内容赋值给新对象的name属性,与原先的对象再无共用的部分,实现了深复制

一般来说,实现深复制的难度很大,对于包含大量指针属性的对象来说,更或者对象中的变量中又嵌套了一层指针的话,那会更加复杂

setter方法的复制选项

当属性声明为 copy 时,在 setter 方法中会执行深复制操作。这意味着在设置属性值时,会创建一个属性值的副本,而不是直接引用传入的对象。

考虑以下代码:

@property (nonatomic, copy) NSString *name;

对应的 setter 方法会类似于这样:

- (void)setName:(NSString *)name {
    if (_name != name) {
        _name = [name copy];
    }
}

在这个 setter 方法中,首先进行了对象的比较,确保新值和旧值不是同一个对象。然后,使用 copy 方法来创建新值的副本,并将副本赋值给属性 _name。这样做可以确保属性值的安全性,防止外部对象对属性值的意外修改。

copy 操作实际上是执行了浅复制,它只会复制对象的指针,而不会复制对象本身。这意味着它会创建一个新的指针,指向相同的内存地址,而不是创建一个完全独立的副本。

对于不可变对象,由于其内容是不可变的,因此可以安全地使用 copy 属性。对于可变对象而言,使用copy可能会导致意外的行为。因为修改一个对象会影响到所有指向该对象的指针。这就是为什么使用 copy 属性时需要小心,特别是。另外,如果属性值是可变对象,那么每次设置属性值时都会创建一个新的不可变副本,这样会导致性能开销增加。

需要注意的是,copy 属性通常适用于容器类对象和其他可复制的对象,但对于自定义类对象,需要确保其实现了 NSCopying 协议。否则,编译器会报错。

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐