疯狂iOS学习笔记-对象复制

1 copy与mutableCopy方法

  1. copy方法总是返回对象不可修改的副本,即使该对象本身是可修改的。
  2. mutableCopy总是返回对象可修改的副本,即使该对象本身是不可修改的。

无论如何,它们返回的总是原对象的副本,当程序对复制的副本进行修改时,原对象通常不会受到影响。

copy示例:

#import <Foundation/Foundation.h>

int main(void) {
    @autoreleasepool {
        NSMutableString* str = [NSMutableString stringWithString:@"《疯狂iOS讲义》"];
        // 复制str不可修改的副本
        NSMutableString* strCopy = [str copy];
        // 打印副本
        NSLog(@"%@", strCopy);
        // 因为strCopy是不可修改的,因此下面被注释的代码会报错
        // [strCopy appendString:@"aa"];
    }
}

运行结果:

2022-05-30 22:51:18.550294+0800 oc.programme[6064:3740118] 《疯狂iOS讲义》
Program ended with exit code: 0

mutableCopy方法示例:

#import <Foundation/Foundation.h>

int main(void) {
    @autoreleasepool {
        NSMutableString* str = [NSMutableString stringWithString:@"《疯狂iOS讲义》"];
        // 复制str可修改的副本
        NSMutableString* strCopy = [str mutableCopy];
        // 尝试修改副本
        [strCopy replaceCharactersInRange:NSMakeRange(3, 3)
                               withString:@"Android"];
        // 修改副本对原字符串无影响
        NSLog(@"%@", str);
        // 副本已被修改
        NSLog(@"%@", strCopy);
    }
}

运行结果:

2022-05-31 14:37:39.868271+0800 oc.programme[10398:3885118] 《疯狂iOS讲义》
2022-05-31 14:37:39.869079+0800 oc.programme[10398:3885118] 《疯狂Android讲义》
Program ended with exit code: 0

2 NSCopying与NSMutableCopy协议

虽然NSObject提供了copy和mutableCopy方法,但自定义类并不能直接调用这两个方法来复制自身。
为了保证一个对象可调用copy和mutableCopy方法,通常需要做如下事情。

  • 让该类实现NSCopying与NSMutableCopy协议。
  • 让该类实现copyWithZone:与mutableCopyWithZone:方法。

当程序调用copy与mutableCopy方法时,程序底层需要调用copyWithZone:与mutableCopyWithZone:方法来完成实际上的复制工作,copy与mutableCopy返回的实际上是copyWithZone:与mutableCopyWithZone:方法的返回值

我们尝试建一个FKDog类,并让其可以调用copy方法复制自己。
该类的接口部分如下:

#import <Foundation/Foundation.h>

@interface FKDog : NSObject
@property (nonatomic, strong) NSMutableString* name;
@property (nonatomic, assign) int age;
@end

该类实现部分如下:

#import "FKDog.h"

@implementation FKDog
- (id)copyWithZone:(NSZone*) zone {
    // 使用zone参数创建FKDog对象
    FKDog* dog = [[[self class] allocWithZone:zone] init];
    dog.name = self.name;
    dog.age = self.age;
    return dog;
}
@end

上面的程序让FKDog实现了NSCopying协议,并实现了copyWithZone:方法,该方法返回了一个该对象的副本。
测试程序如下:

#import "FKDog.h"

int main(void) {
    @autoreleasepool {
        FKDog* dog1 = [[FKDog alloc] init];
        dog1.name = [NSMutableString stringWithString:@"大黄"];
        dog1.age = 12;
        // 复制副本
        FKDog* dog2 = [dog1 copy];
        // 尝试输出副本
        NSLog(@"dog2: %@ %d", dog2.name, dog2.age);
        // 更改副本(替换name)
        dog2.name = [NSMutableString stringWithString:@"dahuang"];
        dog2.age = 14;
        // 尝试输出
        NSLog(@"dog1: %@ %d", dog1.name, dog1.age);
        NSLog(@"dog2: %@ %d", dog2.name, dog2.age);
    }
}

运行结果:

2022-05-31 15:57:19.862912+0800 oc.programme[11705:3920974] dog2: 大黄 12
2022-05-31 15:57:19.863121+0800 oc.programme[11705:3920974] dog1: 大黄 12
2022-05-31 15:57:19.863143+0800 oc.programme[11705:3920974] dog2: dahuang 14
Program ended with exit code: 0

此处copy方法返回的依然是一个可变的FKDog对象,这是因为FKDog类并没有提供对应的不可变类,自然也就无法复制不可变的FKDog对象。


3 深复制与浅复制

  • 浅复制:当对象的实例变量是指针变量时,程序只是复制该地址的指针,而不是真正的复制对象。
  • 深复制:不仅复制对象本身,而且会“递归”复制每个指针类型的实例变量。

浅复制

上面的例子就是一个浅复制,我们将测试代码更改为:

#import "FKDog.h"

int main(void) {
    @autoreleasepool {
        FKDog* dog1 = [[FKDog alloc] init];
        dog1.name = [NSMutableString stringWithString:@"大黄"];
        dog1.age = 12;
        // 复制副本
        FKDog* dog2 = [dog1 copy];
        // 尝试输出副本
        NSLog(@"dog2: %@ %d", dog2.name, dog2.age);
        // 更改副本
        [dog2.name replaceCharactersInRange:NSMakeRange(0, 2)
                                 withString:@"snoopy"];
        dog2.age = 14;
        // 尝试输出
        NSLog(@"dog1: %@ %d", dog1.name, dog1.age);
        NSLog(@"dog2: %@ %d", dog2.name, dog2.age);
    }
}

编译运行,我们会发现,dog1的name属性随dog2的name属性变化发生变化:

2022-06-01 18:22:46.611275+0800 oc.programme[13209:3986627] dog2: 大黄 12
2022-06-01 18:22:46.611499+0800 oc.programme[13209:3986627] dog1: snoopy 12
2022-06-01 18:22:46.611519+0800 oc.programme[13209:3986627] dog2: snoopy 14
Program ended with exit code: 0

因为上面的copyWithZone:方法是直接将dog1的name赋值给dog2的name,但是name只是一个指针变量,该变量储存的只是字符串的地址,这样赋值的结果是dog1的name和dog2的name指向同一个字符串。

深复制

我们对上面的copyWithZone:方法进行更改:

- (id)copyWithZone:(NSZone*) zone {
    // 使用zone参数创建FKDog对象
    FKDog* dog = [[[self class] allocWithZone:zone] init];
    dog.name = [self.name mutableCopy];
    dog.age = self.age;
    return dog;
}

上面程序先将原对象的name实例变量复制了一份可变副本,再将可变副本赋值给新对象的name实例变量。这样就保证了原对象与新对象没有任何共用部分,这就实现了深复制。
运行测试代码结果:

2022-06-01 18:42:39.010794+0800 oc.programme[13407:3994508] dog2: 大黄 12
2022-06-01 18:42:39.011213+0800 oc.programme[13407:3994508] dog1: 大黄 12
2022-06-01 18:42:39.011231+0800 oc.programme[13407:3994508] dog2: snoopy 14
Program ended with exit code: 0

一般来说,深复制的实现难度大很多,Foundation框架中的类大部分都只实现了浅复制。


4 setter方法的复制选项

前面介绍合成stter和getter方法可以使用copy,copy指示符指定调用setXxx方法时,程序实际上会使用参数的副本对Xxx实例变量复制。也就是说setXxx:方法代码如下

- (void) steXxx: (类名*) aXxx {
	Xxx = [aXxx copy];
}

copy方法默认是复制该对象的不可变副本,因此就算程序传入的是可变的对象,但程序调用该参数的copy方法得到的是不可变副本。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值