OC Foundation框架 对象复制

Posted Billy Miracle

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OC Foundation框架 对象复制相关的知识,希望对你有一定的参考价值。

NSObject类提供了copy和mutableCopy方法,通过这两个方法即可复制已有对象的副本。

copy与mutableCopy方法

copy方法用于复制对象的副本。通常来说,copy方法总是返回对象的不可修改的副本,即使该对象本身是可修改的
mutableCopy方法用于复制对象的可变副本。通常来说,mutableCopy方法总是返回对象的可修改的副本,即使该对象本身是不可修改的
无论如何,它们返回的总是原对象的副本,当程序对复制的副本进行修改时,原对象通常不受影响。
示例程序:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableString* book = [NSMutableString
            stringWithString:@"Billy"];
        //复制book字符串的可变副本
        NSMutableString* bookCopy = [book mutableCopy];
        //修改副本,对原字符串没有任何影响
        [bookCopy replaceCharactersInRange:
            NSMakeRange(2, 3)
            withString:@"llboard"];
        //此处看到原字符串的值并没有改变
        NSLog(@"book的值为:%@", book);
        //字符串副本发生了改变
        NSLog(@"bookCopy的值为:%@", bookCopy);
        NSString* str = @"Amy";
        //复制不可变字符串str的可变副本
        NSMutableString* strCopy = [str mutableCopy];
        //向可变字符串后面追加字符串
        [strCopy appendString:@".hhh"];
        NSLog(@"%@", strCopy);
        //调用可变字符串book的copy方法,程序返回一个不可修改的副本
        NSMutableString* bookCopy2 = [book copy];
        //由于bookCopy2是不可修改的,因此下面的代码将会出现错误
        //[bookCopy2 appendString:@"aa"];
    }
    return 0;
}

可以看到输出结果为:

book的值为:Billy
bookCopy的值为:Billboard
Amy.hhh

NSCopying与NSMutableCopying协议

通过上面的程序我们发现使用copy和nutableCopy方法复制对象的副本使用起来确实很方便,那么自定义类是否可以调用copy与mutableCopy方法来复制副本呢?
示例:

//  Car.h

#import <Foundation/Foundation.h>

@interface Car : NSObject
@property (nonatomic, strong) NSMutableString* brand;
@property (nonatomic, strong) NSMutableString* type;
@property (nonatomic, assign) int price;
@end
//  main.m

#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car* car1 = [[Car alloc] init];
        car1.brand = [NSMutableString stringWithString:@"BMW"];
        car1.type = [NSMutableString stringWithString:@"M3"];
        car1.price = 90;
        Car* car2 = [car1 copy];
    }
    return 0;
}

程序编译不会有问题,但运行会出错:

-[Car copyWithZone:]: unrecognized selector sent to instance 0x1006440c0

提示Car类找不到copyWithZone:方法。
再试试:

Car* car3 = [car1 mutableCopy];

也会出错:

[Car mutableCopyWithZone:]: unrecognized selector sent to instance 0x1050c20e0

提示Car类找不到mutableCopyWithZone:方法。
通过上面的尝试可以看出,虽然NSObject提供了copy和mutableCopy方法,但是我们自定义的类并不能够直接调用这两个方法来复制自身。为了保证一个对象可以调用copy方法来复制自身的不可变副本,通常需要做如下事情:

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

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

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

下面在Car类的接口部分声明实现NSCopying协议,然后在Car类的实现部分增加如下copyWithZone: 方法。

//  Car.m
#import "Car.h"

@implementation Car
-(id)copyWithZone: (NSZone*) zone {
    NSLog(@"执行copyWithZone:");
    Car* car = [[[self class] allocWithZone: zone] init];
    car.brand = self.brand;
    car.type = self.type;
    car.price = self.price;
    return car;
}
@end

上面代码让Car实现了NSCopying协议,并实现了copyWithZone: 方法,在该方法中重新创建了一个Car对象,并让该对象的所有属性值与被复制对象的属性值相等,最后返回这个新创建的方法,也就是返回该对象的副本。copyWithZone: (NSZone*) zone方法中的zone参数与不同的储存区有关,通常无须过多地关心该参数,只要将zone参数传给copyWithZone:方法即可。
主程序:

#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car* car1 = [[Car alloc] init];
        car1.brand = [NSMutableString stringWithString:@"BMW"];
        car1.type = [NSMutableString stringWithString:@"M3"];
        car1.price = 90;
        Car* car2 = [car1 copy];
        car2.brand = [NSMutableString stringWithString:@"Alfa"];
        car2.type = [NSMutableString stringWithString:@"Giulia"];
        car2.price = 100;
        NSLog(@"car1\\'s brand: %@",car1.brand);
        NSLog(@"car1\\'s type: %@",car1.type);
        NSLog(@"car1\\'s price: %d",car1.price);
        NSLog(@"car2\\'s brand: %@",car2.brand);
        NSLog(@"car2\\'s type: %@",car2.type);
        NSLog(@"car2\\'s price: %d",car2.price);
    }
    return 0;
}

输出结果:

执行copyWithZone:
car1's brand: BMW
car1's type: M3
car1's price: 90
car2's brand: Alfa
car2's type: Giulia
car2's price: 100

为什么此处调用copy时返回的依然是一个可变的Car对象呢?因为此处的Car没有提供对应的不可变类,自然无法复制不可变的Car对象。如果Car提供了不可变类,还是应当让Car的copyWithZone: 返回不可变的Car对象。
如果重写copyWithZone: 方法时,其父类已经实现了NSCopying协议,并且重写过copyWithZone: 方法,那么子类重写copyWithZone: 方法应该先调用父类的copy方法复制从父类继承到的成员变量,然后对子类中定义的成员变量进行赋值。
格式如下:

-(id)copyWithZone:(NSZone*)zone {
	id obj = [super copy];
	//对子类定义的成员变量赋值
	...
	return obj;
}

浅复制与深复制

先看一段示例程序:

#import "Car.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Car* car1 = [[Car alloc] init];
        car1.brand = [NSMutableString stringWithString:@"BMW"];
        car1.type = [NSMutableString stringWithString:@"M3"];
        car1.price = 90;
        Car* car2 = [car1 copy];
        [car2.brand replaceCharactersInRange:NSMakeRange(0, 3) withString:@"Mazda"];
        NSLog(@"car1\\'s brand: %@",car1.brand);
        NSLog(@"car2\\'s brand: %@",car2.brand);
    }
    return 0;
}

上面程序调用了car1的copy方法复制了一个副本,并将该副本赋给car2变量,接下来修改car2对象的属性值。
输出:

执行copyWithZone:
car1's brand: Mazda
car2's brand: Mazda

可以看到,虽然只是修改了car2的brand属性值,但是为什么car1的brand也发生改变了呢?
创建了一个Car对象并使用car1指针指向了堆内存中的一片区域。接下来要复制一个Car对象,查看copyWithZone:代码,是如下三行:

car.brand = self.brand;
car.type = self.type;
car.price = self.price;

注意,name与type都只是指针变量,该变量中存放的其实是字符串的地址,而非存储字符串本身。这种赋值效果让两个对象的brand和type分别指向两个相同的字符串,效果如下:
在这里插入图片描述
从图中可以看出,此时car1与car2两个指针指向两个不同的Car对象,但brand和type属性都指针,可以看到,两个brand指向了同一个NSMutableString对象,type也是如此。这样一来,程序修改任何一个brand或type属性时,另一个对象的属性也会发生改变。
对于这种复制方式:当对象的属性是指针变量时,如果程序仅仅是复制该指针的地址,而不是复制指针所指的对象,这种方式被称为“浅复制”。也就是两个对象存在共用的部分。
深复制会采取不同的方式,深复制不仅会复制对象本身,而且会“递归”复制每个指针类型的属性,直到两个对象没有任何共用的部分。如果将上面的copyWithZone: 改为如下形式,即可实现深复制。

-(id)copyWithZone: (NSZone*) zone {
    NSLog(@"执行copyWithZone:");
    Car* car = [[[self class] allocWithZone: zone] init];
    car.brand = [self.brand mutableCopy];
    car.type = [self.type mutableCopy];
    car.price = self.price;
    return car;
}

上面程序没有直接将对象赋值,而是先将原对象的name属性值复制了一份可变副本,再将该副本的值赋给新对象的name属性,这样就实现了深复制。
一般来说,深复制的实现难度大很多,尤其是当该对象包含大量的指针类型属性时,如果某些属性所引用的对象再次包含指针类型的属性,那么实现深复制会更加复杂。
一般来说,Foundation框架中的类大部分都只实现了浅复制。

setter方法的复制选项

前面介绍合成setter方法和gatter方法时提到可以使用copy指示符,copy的作用是当程序调用setter方法复制时,将传入参数的副本赋给程序的实例变量。

//  Item.h

#import <Foundation/Foundation.h>

@interface Item : NSObject
@property (nonatomic , copy) NSMutableString* name;
@end
//  main.m

#import "Item.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Item* item = [Item new];
        item.name = [NSMutableString stringWithString: @"Billy"];
        [item.name appendString:@"hhh"];
    }
    return 0;
}

编译程序不会出现问题,但是运行会出错:

[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0x966d797a018becfe

提示不允许修改name属性值,这是因为程序定义name属性时使用的是copy指示符。也就是说,setName: 方法的代码如下:

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

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

以上是关于OC Foundation框架 对象复制的主要内容,如果未能解决你的问题,请参考以下文章

OC学习13——Foundation框架中的集合

OC Foundation框架 集合

oc67--NSString1

34-oc Foundation简介

OC Foundation Kit

OC Foundation Kit