OC基础-拷贝

一、概念理解

拷贝是将原有的对象拷贝一份,获得一个新的对象。

copy和retain的区别:

如果retain一个对象,对象始终只有一个,修改的只是对象中的计数器,增加一个引用,多个引用指向同一内存空间,如果其中一个引用修改了对象的属性,则对象的属性发生改变。

如果copy一个对象,将获得一个新的对象,对新对象的[mutable]copy操作,和原对象没有关系。


拷贝分浅拷贝和深拷贝:

例如一个数组,包含多个对象

浅拷贝:拷贝一个新的数组对象,数组的元素所指向的内存空间和原来的对象所指向的内存空间是相同的。

深拷贝:拷贝一个新的数组对象,数组的元素所指向的内存空间也是原对象的元素所指向的内存空间的拷贝。


二、拷贝协议

1.NSCopying协议

 @protocol NSCopying
 -(id)copyWithZone:(NSZone *)zone;
 @end

任何拷贝功能都需要实现NSCopying协议。也就是说,如果想要使用copy方法,就必须实现该方法。该方法在调用copy方法的时候触发。

系统有些类已经实现了该协议,如:

NSString、NSMutableString、NSArray、NSMutableArray、NSDictionary、NSMutableDictionary

对于这些类的对象,我们可以直接使用copy方法

2.NSMutableCopying协议

@protocol NSMutableCopying
 -(id)mutableCopyWithZone:(NSZone *)zone;
 @end

该协议和上面的类似,区别在于一个可变。任何需要调用mutableCopy方法,都必须实现该方法。使用的情况很少。

系统有些类已经实现了该协议,同上,我们可以直接使用。

对于自定义的类,基本上都是只使用NSCopying协议,NSMutableCopying协议基本不用。


3.系统中已经实现了拷贝协议的一些类

不可变对象,通过copy,新对象不可变

不可变对象,通过mutableCopy,新对象可变

可变对象,通过copy,新对象不可变

可变对象,通过mutableCopy,新对象可变

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        //NSString调用mutableCopy获得的新对象是可变的
        NSString * s = [NSString stringWithFormat:@"hello"];
        NSString * s1 = [s copy];
        NSMutableString * s2 = [s mutableCopy];
        [s2 appendString:@" world!"];
        
        NSLog(@"s1 is %@", s1);
        NSLog(@"s2 is %@", s2);
        
        NSMutableString * ms = [NSMutableString stringWithFormat:@"happy"];
        
        //NSMutableString调用copy获得的是不可变字符串
        NSMutableString * ms1 = [ms copy];//ms1不能使用appendString
        NSMutableString * ms2 = [ms mutableCopy];
        [ms2 appendString:@" girls!"];
        
        NSLog(@"ms1 is %@", ms1);
        NSLog(@"ms2 is %@", ms2);
    }
    return 0;
}
输出结果:

2015-10-15 04:28:00.155 拷贝[1448:21914] s1 is hello
2015-10-15 04:28:00.156 拷贝[1448:21914] s2 is hello world!
2015-10-15 04:28:00.156 拷贝[1448:21914] ms1 is happy
2015-10-15 04:28:00.157 拷贝[1448:21914] ms2 is happy girls!

三、自定义类实现拷贝协议

现在假设自己创建一个类,需要调用copy方法,如何去做呢?下面是一个例子关于自定义类如何实现拷贝协议。

QFCar.h

#import <Foundation/Foundation.h>

@interface QFCar : NSObject <NSCopying,NSMutableCopying>
{
    NSString * _name;
    int _year;
}
@property(nonatomic, copy)NSString * name;
@property(nonatomic, assign)int year;

@end

创建一个类QFCar,实现了NSCoyping和NSMutableCopying协议。声明了两个属性,一个表示车的品牌,一个表示车上市年份。并对这些属性使用@property。其实NSMutableCopying协议的做法和NSCopying协议的做法是一样的,一般使用NSCopying完全足够了,这里为了全面理解,也实现了该协议。

QFCar.m

#import "QFCar.h"

@implementation QFCar

@synthesize name = _name;
@synthesize year = _year;

-(void)setName:(NSString *)name{
    if(_name != name){
        [_name release];
        _name = [name copy];
    }
}
//实现NSObject的方法,在调用[car copy]的时候触发
-(id)copyWithZone:(NSZone *)zone{
  //1.新分配一块内存空间,创建一个新的对象
    QFCar * c = [[[[self class] allocWithZone:zone] init] autorelease];
 //2.设置属性
    c.name = self.name;
    c.year = self.year;
//3.返回新的对象
    return c;
}

//实现NSObject的方法,在调用[car mutablecopy]时触发
-(id)mutableCopyWithZone:(NSZone *)zone{
    QFCar * c = [[[[self class] allocWithZone:zone] init] autorelease];
    c.name = self.name;
    c.year = self.year;
    return c;
}

//当调用%@输出该类的对象时,触发该方法。
-(NSString *)description{
    return [NSString stringWithFormat:@"Car name %@ year %d", self.name, self.year];
}

@end
在QFCar.h头文件中,我们实现了NSCopying协议和NSMutableCopying协议,在QFCar.m文件中,我们就可以实现copyWithZone方法和mutableCopyWithZone方法,这两个方法是NSObject的方法,我们只有在头文件中实现了协议,才能实现该方法。

在上面的代码中,可见copy和mutableCopy的实现是一样的,一般我们使用copy就足够了。

main.m

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        QFCar * car = [[QFCar alloc] init];
        car.name = @"wow";
        car.year = 2015;
        QFCar * car1 = [car copy];
        car1.name = @"qq";
        car1.year = 2014;
        //NSLog(@"car1 %@ is %d", car1.name, car1.year);
        NSLog(@"car1 is %@", car1);
        
        QFCar * car2 = [car mutableCopy];
        car2.name = @"qr";
        car2.year = 2013;
        //NSLog(@"car2 %@ is %d", car2.name, car2.year);
        NSLog(@"car2 is %@", car2);
        
        NSLog(@"car is %@", car);
    }
    return 0;
}
输出结果:

2015-10-15 05:07:38.694 拷贝[1937:34383] car1 is Car name qq year 2014
2015-10-15 05:07:38.696 拷贝[1937:34383] car2 is Car name qr year 2013
2015-10-15 05:07:38.696 拷贝[1937:34383] car is Car name wow year 2015
Program ended with exit code: 0
由输出结果可以看出:

1.对子定义的类的对象,copy和mutableCopy是一样的。

2.使用copy后,新对象的属性的改变和原对象没有关系。


四、深拷贝

1.概念

对于一些有多级子对象的拷贝,才有深拷贝和浅拷贝之分。

浅拷贝只拷贝最上层的对象,子对象不做任何拷贝,子对象和原子对象指向同一内存空间。

深拷贝会层层拷贝,获得的新对象和原对象完全不同。下面是一个数组的深拷贝示意图:

2.实现深拷贝的方法

有两种实现深拷贝的方法:

1⃣️使用copy协议来实现深拷贝

2⃣️使用归档来实现深拷贝


五、使用拷贝协议进行深拷贝

以系统默认实现深拷贝的类为例。NSString类是简单对象,无神拷贝。NSArray和NSDictionary有深拷贝,实现深拷贝的方法如下:

- (id) initWithArray: (NSArray *) array copyItems: (BOOL)flag;

- (id) initWithDictionary: (NSDictionary *) otherDictionary copyItems: (BOOL)flag;

调用该方法时,将flag开关打开。

1.NSArray浅拷贝

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray * _carList = [[NSMutableArray alloc] init];
        for(int i = 0; i < 10; i++){
            QFCar * c = [[QFCar alloc] init];
            c.name = [NSString stringWithFormat:@"WOW730%d",i];
            c.year = 2006+i;
            [_carList addObject:c];
        }
        //浅拷贝
        NSMutableArray * arr2 = [[NSMutableArray alloc] initWithArray:_carList];
        [[arr2 objectAtIndex:0] setName:@"QQ2103"];
        [[arr2 objectAtIndex:0] setYear:2103];
        
        NSLog(@"arr2 is %@", arr2);
        NSLog(@"carList is %@", _carList);
    }
    return 0;
}
输出结果:

2015-10-15 05:58:32.507 拷贝[2950:59616] arr2 is (
    "Car name QQ2103 year 2103",
    "Car name WOW7301 year 2007",
    "Car name WOW7302 year 2008",
    "Car name WOW7303 year 2009",
    "Car name WOW7304 year 2010",
    "Car name WOW7305 year 2011",
    "Car name WOW7306 year 2012",
    "Car name WOW7307 year 2013",
    "Car name WOW7308 year 2014",
    "Car name WOW7309 year 2015"
)
2015-10-15 05:58:32.509 拷贝[2950:59616] carList is (
    "Car name QQ2103 year 2103",
    "Car name WOW7301 year 2007",
    "Car name WOW7302 year 2008",
    "Car name WOW7303 year 2009",
    "Car name WOW7304 year 2010",
    "Car name WOW7305 year 2011",
    "Car name WOW7306 year 2012",
    "Car name WOW7307 year 2013",
    "Car name WOW7308 year 2014",
    "Car name WOW7309 year 2015"
)
Program ended with exit code: 0
由输出结果可见,浅拷贝只是拷贝了外层对象,子对象并没有拷贝,子对象和原对象指向同一内存空间,所以新对象的子对象修改了属性,原对象对应的子对象的属性也发生变化。

2.NSArray深拷贝

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray * _carList = [[NSMutableArray alloc] init];
        for(int i = 0; i < 10; i++){
            QFCar * c = [[QFCar alloc] init];
            c.name = [NSString stringWithFormat:@"WOW730%d",i];
            c.year = 2006+i;
            [_carList addObject:c];
        }
        
        NSMutableArray * arr3 = [[NSMutableArray alloc] initWithArray:_carList copyItems:YES];
        [[arr3 objectAtIndex:0] setName:@"QQ2103"];
        [[arr3 objectAtIndex:0] setYear:2103];
        NSLog(@"arr3 is %@", arr3);
        NSLog(@"carList is %@", _carList);
    }
    return 0;
}
输出结果:

2015-10-15 06:18:43.250 拷贝[3413:70327] arr3 is (
    "Car name QQ2103 year 2103",
    "Car name WOW7301 year 2007",
    "Car name WOW7302 year 2008",
    "Car name WOW7303 year 2009",
    "Car name WOW7304 year 2010",
    "Car name WOW7305 year 2011",
    "Car name WOW7306 year 2012",
    "Car name WOW7307 year 2013",
    "Car name WOW7308 year 2014",
    "Car name WOW7309 year 2015"
)
2015-10-15 06:18:43.251 拷贝[3413:70327] carList is (
    "Car name WOW7300 year 2006",
    "Car name WOW7301 year 2007",
    "Car name WOW7302 year 2008",
    "Car name WOW7303 year 2009",
    "Car name WOW7304 year 2010",
    "Car name WOW7305 year 2011",
    "Car name WOW7306 year 2012",
    "Car name WOW7307 year 2013",
    "Car name WOW7308 year 2014",
    "Car name WOW7309 year 2015"
)
Program ended with exit code: 0
由输出结果可以看出,深拷贝所获得的新对象和原来的对象完全没有关系,对新对象的子对象做任何修改都不会影响原对象。
备注:

在使用copy协议进行深拷贝时,外层对象和子对象都需要拷贝,所以都需要实现NSCopying协议,否则会报错。在上面的例子中,NSMutableArray已由系统实现了NSCopying协议,QFCar自定义类在前面的代码中也实现了NSCopying协议。


六、使用归档协议进行深拷贝

归档协议NSCoding的两个方法:归档和解档

-(void)encodeWithCoder: (NSCoder *) aCoder;

- (id) initWithCoder: (NSCoder *) aDecoder;

QFcar.h

#import <Foundation/Foundation.h>

@interface QFCar : NSObject <NSCoding>
{
    NSString * _name;
    int _year;
}
@property(nonatomic, copy)NSString * name;
@property(nonatomic, assign)int year;

@end
使用了归档协议,就无须使用copy协议了,两种协议任选一种,都可以实现深拷贝,都需要熟练。

QFcar.m

#import "QFCar.h"

@implementation QFCar

@synthesize name = _name;
@synthesize year = _year;

//使用@property和@synthesize会自动实现setter和getter方法,但是如果觉得某个setter或者getter方法需要做一些初始化操作,可以自己重写。
-(void)setName:(NSString *)name{
    //系统默认的写法
    /*
    if(_name != name){
        [_name release];
        _name = [name copy];
    }
     */
    _name = @"china";
}

//当调用%@输出该类的对象时,触发该方法。
-(NSString *)description{
    return [NSString stringWithFormat:@"Car name %@ year %d", self.name, self.year];
}

//NSCoding归档协议的两个方法
//存档
-(void) encodeWithCoder:(NSCoder *)aCoder{
    [aCoder encodeObject:self.name forKey:@"Name"];
    [aCoder encodeInt:self.year forKey:@"Year"];
    //这里存档的key可以任意,解档的key和存档的key保持一致。
}
//解档
-(id)initWithCoder:(NSCoder *)aDecoder{
    self = [super init];
    if(self){
        self.name = [aDecoder decodeObjectForKey:@"Name"];
        self.year = [aDecoder decodeIntForKey:@"Year"];
    }
    return self;
}

@end
存档的过程,就是以键值对形式,将对象转换成二进制数据,键为自定义的键,值就是对象的二进制数据。

解档时,根据键,将二进制数据还原成对象。

main.m

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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        NSMutableArray * _carList = [[NSMutableArray alloc] init];
        for(int i = 0; i < 10; i++){
            QFCar * c = [[QFCar alloc] init];
            c.name = [NSString stringWithFormat:@"WOW730%d",i];
            c.year = 2006+i;
            [_carList addObject:c];
        }
        //1.将数组转换成二进制数据
        NSData * data = [NSKeyedArchiver archivedDataWithRootObject: _carList];
        //2.将二进制数据还原成原来的数组
        NSMutableArray * arr = [NSKeyedUnarchiver unarchiveObjectWithData: data];
        [[arr objectAtIndex:0] setName:@"QQ2103"];
        [[arr objectAtIndex:0] setYear:2103];
        
        NSLog(@"arr is %@", arr);
        NSLog(@"carList is %@", _carList);
        
        //崩溃的原因是因为QFCar没有实现归档协议
        
    }
    return 0;
}
输出结果;

2015-10-15 06:44:48.202 拷贝[4028:84044] arr is (
    "Car name china year 2103",
    "Car name china year 2007",
    "Car name china year 2008",
    "Car name china year 2009",
    "Car name china year 2010",
    "Car name china year 2011",
    "Car name china year 2012",
    "Car name china year 2013",
    "Car name china year 2014",
    "Car name china year 2015"
)
2015-10-15 06:44:48.204 拷贝[4028:84044] carList is (
    "Car name china year 2006",
    "Car name china year 2007",
    "Car name china year 2008",
    "Car name china year 2009",
    "Car name china year 2010",
    "Car name china year 2011",
    "Car name china year 2012",
    "Car name china year 2013",
    "Car name china year 2014",
    "Car name china year 2015"
)
Program ended with exit code: 0
由上面的结果可于看出,使用归档协议也实现了深拷贝。


上面的例子中,无论是使用copy协议还是归档协议,都是针对只有一层子对象的对象进行深拷贝,如果是对多层次的对象进行深拷贝,实现原理是一样的,需要层层深入,操作还是挺复杂的,但按照原理来,也不会出错。

@诗未冷学习博客



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值