复制对象(二)<NSCopying>协议和属性的copy特性

如果要用copy或mutableCopy方法复制自己定义的类对象,那么该类必须要实现<NSCopying>或协议。否则将会导致程序崩溃:



控制台输出为:

2014-02-01 01:11:09.087 Chocolate[951:303] -[Desserts copyWithZone:]: unrecognized selector sent to instance 0x1001099e0
2014-02-01 01:11:09.089 Chocolate[951:303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Desserts copyWithZone:]: unrecognized selector sent to instance 0x1001099e0'

实现<NSCopying>协议就必须要实现copyWithZone方法,例如B= [A copy],其实现过程为:

首先调用[A copyWithZone:zone];方法创建对象的副本及该对象的引用obj,然后将obj返回并赋值给B。

如果没有实现copyWithZone:方法,copy方法将会发送copyWithZone:nil消息给类,这样将导致程序的崩溃。


如果要复制的类的父类实现了<NSCopying>协议,那么在子类实现复制协议时,必须在copyWithZone方法中首先调用父类的copyWithZone方法。


下面来看个例子:

Desserts类文件

#import <Foundation/Foundation.h>

@interface Desserts : NSObject <NSCopying, NSMutableCopying>

@property (strong, nonatomic) NSMutableString *producer;

@property (assign, nonatomic) NSUInteger price;

- (void)setProducer:(NSMutableString *)theProducer Price:(NSUInteger)thePrice;

@end
#import "Desserts.h"

@implementation Desserts

- (void)setProducer:(NSMutableString *)theProducer Price:(NSUInteger)thePrice {
    self.producer = theProducer;
    self.price    = thePrice;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"Producer = %@, Price = %lu", self.producer, self.price];
}

- (id)copyWithZone:(NSZone *)zone {
    Desserts *desserts = [[Desserts allocWithZone:zone] init];
    desserts.producer  = self.producer;
    desserts.price     = self.price;
    return desserts;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    Desserts *desserts = [[Desserts allocWithZone:zone] init];
    desserts.producer  = self.producer;
    desserts.price     = self.price;
    return desserts;
}

@end


main.m文件

#import <Foundation/Foundation.h>
#import "Desserts.h"

int main(int argc, const char * argv[])
{

    @autoreleasepool {
        
        Desserts *desserts = [[Desserts alloc] init];
        NSMutableString *huizhou = [NSMutableString stringWithString:@"huizhou"];
        [desserts setProducer:huizhou Price:100];
        
        Desserts *sweets = [desserts copy];
        
        [desserts.producer appendString:@" hello factory"];
        desserts.price++;
        
        NSLog(@"About desserts:%@", desserts);
        NSLog(@"About sweets:  %@", sweets);
        
    }
    return 0;
}

控制台输出:

2014-01-31 17:56:40.178 Chocolate[3093:303] About desserts:Producer = huizhou hello factory, Price = 101
2014-01-31 17:56:40.179 Chocolate[3093:303] About sweets:  Producer = huizhou hello factory, Price = 100


需要特别注意的是,在copy协议中:

desserts.producer = self.producer;
desserts.price    = self.price;

这里的producer是strong特性的对象,而price是assing特性的基本数据类型变量。

在复制后,sweets和desserts指向两个不同的Desserts对象。

由于price是基本数据类型变量,所以这里的赋值使得sweets和desserts的price属性在内存中是不同的变量。所以desserts.price++并不会对sweets的price造成影响。

由于producer是指针变量,而在copy时,复制成员producer只是简单的赋值,因此desserts.producer和sweets.producer指向同一个NSMutableString对象,故两个对象的producer输出一致。

明显,这并不能达到我们想要的深复制对象内容的目的,因此对于可变对象,应该在复制属性时进行copy操作:

//    desserts.producer = self.producer;
    desserts.producer = [self.producer copy];

修改后,由于NSMutableString对象的copy是深复制,所以复制后的desserts和sweets指向两个不同的对象:

2014-01-31 18:01:14.803 Chocolate[3106:303] About desserts:Producer = huizhou hello factory, Price = 101
2014-01-31 18:01:14.805 Chocolate[3106:303] About sweets:  Producer = huizhou, Price = 100

对desserts.producer指向的对象的修改并不会影响到sweets.producer指向的对象。

如果属性是不可变对象,如NSString,NSArray等,由于这些对象不可更改,所以我们不用担心这些对象被修改,而且额外的对象会增加内存的开销,所以我们只需要简单的赋值就可以了,而不需要调用copy方法。

对于mutableCopyWithZone方法同理。


实现copyWithZone方法时复制属性小结:

1.基本数据类型的变量复制时直接赋值。

2.对于不可变对象,直接赋值。

3.对于strong特性的对象:如果要求浅复制,直接赋值。如果要求深复制,则要调用copy或mutableCopy方法(当然对于数组或字典来说还是浅复制)。对于copy特性的属性,使用self.property = theProperty会默认调用copy方法进行复制。


下面新建一个Chocolate类,该类继承自Desserts类。如果要实现Chocolate类对象的复制,同样要实现<NSCopying>或<NSMutableCopying>协议中的方法。由于该类继承自Desserts类,所以Chocolate类的copyWithZone方法必须首先调用父类的copyWithZone方法:

#import "Desserts.h"

@interface Chocolate : Desserts <NSCopying>

@property (copy, nonatomic) NSMutableString *brand;

@property (strong, nonatomic) NSString *details;

- (void)setProducer:(NSMutableString *)theProducer
              Price:(NSUInteger)thePrice
              Brand:(NSMutableString *)theBrand
            Details:(NSString *)theDetails;

@end
#import "Chocolate.h"

@implementation Chocolate

- (void)setProducer:(NSMutableString *)theProducer
              Price:(NSUInteger)thePrice
              Brand:(NSMutableString *)theBrand
            Details:(NSString *)theDetails {
    [super setProducer:theProducer Price:thePrice];
    self.brand   = theBrand;
    self.details = theDetails;
}

- (NSString *)description {
    return [NSString stringWithFormat:@"\n\tProducer = %@\n\tPrice = %lu\n\tBrand = %@\n\tDetails = %@", self.producer, self.price, self.brand, self.details];
}

- (id)copyWithZone:(NSZone *)zone {
    [super copyWithZone:zone];
    
    Chocolate *chocolate = [[Chocolate allocWithZone:zone] init];
    chocolate.producer   = [self.producer copy];
    chocolate.price      = self.price;
    chocolate.brand 	 = self.brand;
    chocolate.details 	 = [self.details copy];
    return chocolate;
}

@end


对于copy特性的属性,与@synthesize一起使用

@property (copy, nonatomic) NSMutableString *brand;

其合成的setter方法会调用copy方法,其实现类似于:

- (void)setBrand:(NSMutableString *)theBrand {
    if (brand != theBrand) {
        brand = [theBrand copy];
    }
}

由于调用的是copy方法而不是mutableCopy方法,所以创建的是不可变副本,这样就不用担心属性的值被修改,对于copyWithZone方法中的属性变量直接赋值就可以了。

对于可变类型的属性(如NSMutableString,NSMutableArray等),copy特性比strong特性更加安全。而对于不可变对象如NSString,NSArray等,如果赋值源是可变对象,那么也可能会改变NSString的值,如果希望保护该属性不被修改就用copy特性,否则用strong特性。

例如,对于Desserts的producer属性,它是strong特性:

@property (strong, nonatomic) NSMutableString *producer;
        Desserts *desserts = [[Desserts alloc] init];
        [desserts setProducer:[NSMutableString stringWithString:@"Huizhou"] Price:100];
        NSMutableString *mstr = desserts.producer;
        NSLog(@"%@", desserts.producer);
        
        [mstr appendString:@" hello factory"];
        
        NSLog(@"%@", desserts.producer);

控制台输出:

2014-01-31 18:54:07.013 Chocolate[3480:303] Huizhou
2014-01-31 18:54:07.015 Chocolate[3480:303] Huizhou hello factory


以上代码中,将desserts.producer赋值给一个用于临时任务的mstr,并且对mstr进行修改,那么desserts.producer将同样被修改。

如果我们不希望类中的属性被修改,可以将producer的特性改为copy:

@property (copy, nonatomic) NSMutableString *producer;

随后用getter方法获取producer的值并进行修改,将会导致程序崩溃:

2014-01-31 18:56:02.206 Chocolate[3503:303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendString:'

这样可以达到保护属性不被修改的目的。

为Teacher类实现NSCopying协议,并实现Teacher对象的深复制的步骤如下: 1. 让Teacher类继承NSCopying协议。 2. 在Teacher类的代码实现部分重写继承自NSCopying协议CopyWithZone:方法,该方法返回一个该类的不可变对象副本。 具体实现代码如下: ```objective-c @interface Teacher : NSObject <NSCopying> @property (nonatomic, copy) NSString *name; @property (nonatomic, assign) NSInteger age; @end @implementation Teacher - (id)copyWithZone:(NSZone *)zone { Teacher *teacher = [[Teacher allocWithZone:zone] init]; teacher.name = [self.name copy]; teacher.age = self.age; return teacher; } @end ``` 在上述代码中,我们重写了NSCopying协议copyWithZone:方法,该方法返回了一个Teacher类的不可变对象副本。在该方法中,我们使用allocWithZone:方法来创建一个新的Teacher对象,然后将原对象属性值赋值给新对象属性,最后返回新对象。 接下来,我们可以创建一个Teacher对象,并对其进行深复制,然后输出原对象和副本的地址和内容,具体代码如下: ```objective-c Teacher *teacher = [[Teacher alloc] init]; teacher.name = @"Tom"; teacher.age = 30; Teacher *copyTeacher = [teacher copy]; NSLog(@"teacher address: %p, name: %@, age: %ld", teacher, teacher.name, teacher.age); NSLog(@"copyTeacher address: %p, name: %@, age: %ld", copyTeacher, copyTeacher.name, copyTeacher.age); ``` 在上述代码中,我们首先创建了一个Teacher对象,并设置了其属性值。然后,我们对该对象进行深复制,并将副本赋值给copyTeacher变量。最后,我们使用NSLog输出了原对象和副本的地址和内容。 运行上述代码,可以得到如下输出结果: ``` teacher address: 0x7f8d5a402b20, name: Tom, age: 30 copyTeacher address: 0x7f8d5a402b40, name: Tom, age: 30 ``` 可以看到,原对象和副本的地址不同,且它们的属性值相同,说明我们成功地实现了Teacher对象的深复制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值