【OC】对象复制

copy和mutableCopy方法

  copy方法用于复制对象的副本,该方法总是返回对象的不可修改的副本,即使被复制的对象本身是可修改的。例如调用NSMutableString的copy方法将会返回不可修改的字符串对象。
  mutableCopy方法也用于复制对象的副本,不同的是,该方法总是返回该对象的可修改副本,即使被复制的对象本身是不可修改的。例如调用mutableCopy方法复制NSString的,返回的是一个NSMutableString对象。
  这两个方法的共同点是:都是用于复制对象的副本;都返回的是原对象的副本;对复制的副本进行修改时,原对象通常不受影响

下面对这两个方法的使用给出具体的代码示例:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) 
{
    @autoreleasepool {
        NSMutableString* game = [NSMutableString stringWithString:@"原神启动!"];
        //复制game字符串的可变副本
        NSMutableString* gameCopy = [game mutableCopy];
        //修改副本,对原字符串没有任何影响
        [gameCopy replaceCharactersInRange:NSMakeRange(0, 2) withString:@"崩铁"];
        //此处可以看到原字符串的值并没有改变
        NSLog(@"game的值为 %@", game);
        //此处可以看到字符串副本发生了改变
        NSLog(@"gameCopy的值为 %@", gameCopy);
        NSString* str = @"艾尔海森";
        //复制str(不可变字符串)的可变副本
        NSMutableString* strCopy = [str mutableCopy];
        //向可变字符串后面追加字符串
        [strCopy appendString:@"是教令院的书记官。"];
        NSLog(@"strCopy的值为 %@", strCopy);
        //调用game(可变字符串)的copy方法,程序返回一个不可修改的副本
        NSMutableString* gameCopy2 = [game copy];
        //如果此时试图对gameCopy2进行修改操作(例如修改、添加等),编译时不会报错,但运行后代码就会出错
        //[gameCopy2 appendString:@"原神是一款探索冒险游戏"];
        //(运行后代码就会出错 笔者在这里直接就注释掉了)
    }
    return 0;
}

运行结果:

在这里插入图片描述

NSCopying与NSMutableCopy协议

  上面我们说了可以调用copy方法和mutableCopy方法来复制对象的副本,那么现在思考一下:我们自定义的类可以调用copy方法和mutableCopy方法来复制对象副本吗

  答案是不可以的!因为当程序调用对象的copy(mutableCopy)方法复制自身时,程序底层需要调用copyWithZone:(mutableCopyWithZone:)方法来完成实际的复制工作,copy(mutableCopy)方法返回的实际上就是copyWithZone:(mutableCopyWithZone:)方法的返回值。而我们的自定义类中并没有事先对copyWithZone:(mutableCopyWithZone:)进行实现,所以在调用时会出现找不到copyWithZone:(mutableCopyWithZone:)的错误提示。

为了保证一个对象可调用 copy 方法来复制自身的不可变副本,通常需要做如下事情:

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

同理,为了保证一个对象可以调用 mutableCopy 方法来复制自身的可变副本,通常需
要做如下事情:

  • 让该类实现 NSMutableCopying 协议。
  • 让该类实现 mutableCopyWithZone:方法。

由此,我们如果要在自定义类里调用copy方法和mutableCopy方法来复制对象副本,就需要在自定义类的接口部分声明NSCopying(NSMutableCopying)协议,然后在自定义类的实现部分增加copyWithZone:(mutableCopyWithZone:)方法

下面给出在自定义类里调用copy方法和mutableCopy方法来复制对象副本的代码示例:

接口部分:

#import <Foundation/Foundation.h>
 
NS_ASSUME_NONNULL_BEGIN
 
@interface FKGame:NSObject<NSCopying>
 
@property(nonatomic, strong)NSMutableString* name;
@property(nonatomic, assign)int age;
 
@end
 
NS_ASSUME_NONNULL_END

实现部分:

#import "FKGame.h"
 
@implementation FKGame
 
@synthesize name;
@synthesize age;
//在FKGame的实现部分增加copyWithZone:方法
- (id)copyWithZone:(NSZone *)zone {
    NSLog(@"--执行copyWithZon--");
    //使用zone参数创建FKGame对象
    FKGame* game = [[[self class]allocWithZone:zone]init];
    game.name = self.name;
    game.age = self.age;
    return game;
}
 
@end

主函数:

#import <Foundation/Foundation.h>
#import "FKGame.h"
 
int main(int argc, const char* argv[]) 
{
    @autoreleasepool {
        //创建一个game对象
        FKGame* game1 = [FKGame new];
        game1.name = [NSMutableString stringWithString:@"原神"];
        game1.age = 4;
        //复制副本(不添加对应协议这里会报错)
        FKGame* game2 = [game1 copy];
        game2.name = [NSMutableString stringWithString:@"崩铁"];
        game2.age = 3;
        NSLog(@"game1的名字是:%@,已经发布了 %d 年了", game1.name, game1.age);
        NSLog(@"game2的名字是:%@,已经发布了 %d 年了", game2.name, game2.age);
    }
    return 0;
}

运行结果:

在这里插入图片描述

浅复制与深复制

浅复制

  当对象的实例变量是指针变量时,如果程序只是复制该指针的地址,而不是真正复制指针所指的对象,这种复制方法就被称为浅复制。
  对于浅复制而言,程序是在内存中复制了两个对象,这两个对象的指针变量将会指向同一个变量,即两个对象依然存在共用的部分。

  下面会用代码进行演示:(该部分代码的接口部分和实现部分与刚才NSCopying与NSMutableCopy协议的大致相同,在这里就不再给出)

主函数:

#import <Foundation/Foundation.h>
#import "FKGame.h"
 
int main(int argc, const char* argv[])
{
    @autoreleasepool {
        //创建一个game1对象
        FKGame* game1 = [FKGame new];
        game1.name = [NSMutableString stringWithString:@"原神"];
        game1.age = 4;
        //复制副本(不添加对应协议这里会报错)
        FKGame* game2 = [game1 copy];
        //修改game2的name属性
        [game2.name replaceCharactersInRange:NSMakeRange(0, 2) withString:@"崩铁"];
        //查看game1、game2的name属性
        NSLog(@"game1的name是:%@", game1.name);
        NSLog(@"game2的name是:%@", game2.name);
    }
    return 0;
}

运行结果:

在这里插入图片描述

  从上述代码,我们可以看出来,程序调用了game1的copy方法复制了一个副本,并将该副本赋给了game2变量,然后程序对game2的name属性进行了修改,输出game1和game2的name属性,发现两者都被修改了。这是因为name本身只是一个指针变量,里面存放的是字符串的地址,并不是字符串本身,这样赋值的效果是让game1和game2的name属性指向同一个字符串,这样无论程序修改哪个name实例变量值,另一个也会随着改变

深复制

  深复制与浅复制相反,它不仅会复制对象本身,而且会“递归”复制每个指针类型的实例变量,直到两个对象没有任何共用的部分。

要想实现深复制,只需将实现部分的copyWithZone:改为如下形式即可。

- (id)copyWithZone:(NSZone *)zone {
    NSLog(@"--执行copyWithZon--");
    //使用zone参数创建FKGame对象
    FKGame* game = [[[self class]allocWithZone:zone]init];
    game.name = [self.name mutableCopy];
    game.age = self.age;
    return game;
}

代码的接口部分和其余实现部分与刚才NSCopying与NSMutableCopy协议的大致相同,主函数部分与浅复制的大致相同,这里就不给出了。

运行结果:

在这里插入图片描述
  由上述copyWithZone:程序和运行结果可以看出来,深复制是先将原对象的name实例变量复制了一份可变副本,再将可变副本的值赋给新对象的name实例变量。这样就保证了两个FKGame对象之间没有任何共用的部分,成功实现了深复制。

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

setter方法的复制选项

  前面说到在合成setter和getter方法的时候可以使用copy指示符,该指示符就是指定当程序调用setter方法复制的时候,实际上是将传入参数的副本赋给程序的实例变量。

下面进行代码示例:

接口部分:

#import <Foundation/Foundation.h>
 
NS_ASSUME_NONNULL_BEGIN
 
@interface FKItem:NSObject
@property (nonatomic, copy) NSMutableString* name;
 
@end
 
NS_ASSUME_NONNULL_END

实现部分:

#import "FKItem.h"
 
@implementation FKItem
@synthesize name;
 
@end

主函数:

#import <Foundation/Foundation.h>
#import "FKItem.h"
 
int main(int argc, const char* argv[]) 
{
    @autoreleasepool {
        FKItem* item = [FKItem new];
        item.name = [NSMutableString stringWithString:@"疯狂iOS讲义"];
        [item.name appendString: @"fkit"];  
    }
    return 0;
}

运行结果:

在这里插入图片描述

  运行后,我们会发现程序出现以上报错,意为,代码尝试使用 appendString: 方法来修改一个 NSString 对象,而该对象是一个不可变的对象。出现这种错误的原因是,我们在定义name属性时使用了copy指示符,该指示符指定调用setName:方法时(通过点语法赋值时,实际上是调用对应的setter方法)程序实际上会使用参数的副本对name实例变量复制。

这里的setter方法相当于在实现部分有如下代码:

- (void)setName:(NSMutableString *) aname 
{
            name = [aname copy];
}

  copy方法默认复制该对象的不可变副本,虽然程序传入的NSMutableString,但程序调用该参数的copy方法得到的是不可变副本。所以,程序赋给FKItem对象的name实例变量的值依然是不可变字符串。

  定义合成 getter、setter 方法时并没有提供 mutableCopy 指示符。因此,即使定义实例变量时使用了可变类型,但只要使用 copy 指示符,实例变量实际得到的值总是不可变对象。

  • 25
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值