Objective-C的对象复制与拷贝选项

对象复制与拷贝

文章目录

copy与mutablecopy

copy与mutablecopy的简介

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

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

mutableCopy 方法:

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

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

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

示例:

不可变对象的复制
objective-c 复制代码
#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;
}

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

可变对象的复制
objective-c 复制代码
#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方法
objective-c 复制代码
#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不会产生影响。

objective-c 复制代码
#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指针指向的内容也会跟着改变

深拷贝(Deep Copy):

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

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

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

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

objective-c 复制代码
#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的字符串内容都发生了改变,想要解决这个问题我们可以画一个图来辅助我们进行理解。

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

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

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

objective-c 复制代码
- (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 方法中会执行深复制操作。这意味着在设置属性值时,会创建一个属性值的副本,而不是直接引用传入的对象。

考虑以下代码:

objective-c 复制代码
@property (nonatomic, copy) NSString *name;

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

objective-c 复制代码
- (void)setName:(NSString *)name {
    if (_name != name) {
        _name = [name copy];
    }
}

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

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

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

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

相关推荐
BigYe程普3 分钟前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
彭于晏6895 分钟前
Android广播
android·java·开发语言
弱冠少年31 分钟前
websockets库使用(基于Python)
开发语言·python·numpy
长天一色31 分钟前
C语言日志类库 zlog 使用指南(第五章 配置文件)
c语言·开发语言
一般清意味……43 分钟前
快速上手C语言【上】(非常详细!!!)
c语言·开发语言
卑微求AC44 分钟前
(C语言贪吃蛇)16.贪吃蛇食物位置随机(完结撒花)
linux·c语言·开发语言·嵌入式·c语言贪吃蛇
技术无疆1 小时前
【Python】Streamlit:为数据科学与机器学习打造的简易应用框架
开发语言·人工智能·python·深度学习·神经网络·机器学习·数据挖掘
金灰1 小时前
HTML5--裸体回顾
java·开发语言·前端·javascript·html·html5
爱上语文1 小时前
Java LeetCode每日一题
java·开发语言·leetcode
Манго нектар2 小时前
JavaScript for循环语句
开发语言·前端·javascript