天天看点

Objective-C Copy语法

在OC语法中,提供了Copy语法(Copy + MutableCopy)用于对象的拷贝。其中很容易混淆的是浅拷贝和深拷贝。

所谓浅拷贝,即是地址拷贝,并不产生新的对象,而是对原对象的引用计数值加1即调用所谓的retain。而深拷贝,即是对象拷贝,产生新的对象副本,计数器为1,调用copy或mutableCopy。

何时用copy, 何时用 mutableCopy?

1、不可变对象→可变对象的转换:

NSArray *array1 = [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSMutableArray *str2 = [array1 mutableCopy];
           
2、可变对象→不可变对象的转换:
NSMutableArray *array2 = [NSMutableArray arrayWithObjects:@"aa",@"bb",@"cc",@"dd",nil];
NSArray *array1=[array2 copy];
           
3、可变对象→可变对象的转换(不同指针变量指向不同的内存地址):
NSMutableArray *array1= [NSMutableArray arrayWithObjects:@"a",@"b", nil];
NSMutableArray *str2=[array1 mutableCopy];
           

通过上边的两个例子,我们可以很方便的将一个对象在可变和不可变之间转换,并且这里不用考虑内存使用原则(即引用计数的问题)。这就是深拷贝的魅力所在。

4、同类型对象之间的导向保持(不同指针变量指向同一块内存地址):

  a.

NSMutableString *str1=[NSMutableString stringWithString:@"hello world"];
NSMutableString *str2=[str1 retain];
[str1 release];
           
  b.
NSArray *array1= [NSArray arrayWithObjects:@"a",@"b",@"c",nil];
NSArray *str2=[array1 Copy];
[array1 release];
           

通俗的讲,就是甲在执行交通导航任务,但接到上级新的命令要执行新的任务,那么在甲执行新任务之前,需要有人替代甲继续执行交通导航任务。这时候就要用到浅拷贝了。

则简化为:

问:什么时候用到深浅拷贝?

答:深拷贝是在要将一个对象从可变(不可变)转为不可变(可变)或者将一个对象内容克隆一份时用到;浅拷贝是在要复制一个对象的指针时用到。

下面通过一个例子来分析一下这个比较容易乱的Copy:

1. 对于NSString/NSMutableString; NSArray/NSMutableArray...这些OC提供的类对象:

以NSString/NSMutableString为例:

对于copy,返回的一定是不可变类型;而mutableCopy,返回的一定是可变类型。

①对于 mutableCopy,一定是深拷贝。

//对于 mutableCopy,都是深拷贝:对象的拷贝,产生新的对象
void strMutableCopy(){
    
    NSString *str=[[NSString alloc]initWithFormat:@"abcd"];
    
    //产生了一个新的对象 计数器为1 原对象的计数器不变
    NSMutableString *str2=[str mutableCopy];
    
    //输出二者的地址,二者的地址是不同的
    NSLog(@"str --> %p",str);
    NSLog(@"str2 --> %p",str2);
}
           

②对于 copy:

如果是 NSString ---> NSString;则是浅拷贝;如果是 NSMutableString --->NSString;则是深拷贝。

如果是 NSString 、NSMutableString ---> NSMutableString;则是深拷贝。

Note :只有一种情况下是发生浅拷贝:不可变对象复制到不可变对象。

//浅拷贝:指针拷贝 不会产生新的对象,原对象计数器加1
void strCopy(){
    
    NSString *str=[[NSString alloc]initWithFormat:@"abcd"];
    //因为NSString对象本身就不可变,所以并没产生新的对象,而是返回对象本身,会做一次retain操作,所以原对象也会retain
    NSString *str2=[str copy];
    
    //输出二者的地址,二者的地址是相同的
    NSLog(@"str --> %p",str);
    NSLog(@"str2 --> %p",str2);
}
           
除了以上这种情形外,其他都是深拷贝。

2. 对于自定义对象的Copy:该类必须实现NSCopying协议,重写 copyWithZone方法。

同理,对于自定义对象的mutableCopy:必须实现 NSMutableCopying协议,重写 mutableCopyWithZone方法。

在NSCopying协议中,其实只有一个协议方法, 如下:

@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
           
在NSMutableCopying协议:
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(NSZone *)zone;
@end
           
下面给出一个例子:
//
//  Person.h
//  test_1
//
//  Created by lin on 14-8-26.
//  Copyright (c) 2014年 linshaolie. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface Person : NSObject<NSCopying>

@property(nonatomic,copy) NSString *name;
@property(nonatomic,assign) int age;

-(id)initWithName:(NSString*)name andAge:(int)age;

@end
           
//
//  Person.m
//  test_1
//
//  Created by lin on 14-8-26.
//  Copyright (c) 2014年 linshaolie. All rights reserved.
//

#import "Person.h"

@implementation Person

-(instancetype)initWithName:(NSString*)name andAge:(int)age
{
    if ( self = [super init] )
    {
        self.age = age;
        self.name = name;
    }
    return self;
}

-(id)copyWithZone:(NSZone *)zone
{
    Person* person = [[[self class] allocWithZone:zone] initwithName:self.name andAge:self.age];
    return person;
}

@end
           
testing...
//
//  main.m
//  test_1
//
//  Created by lin on 14-8-26.
//  Copyright (c) 2014年 linshaolie. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        
        Person *per1 = [[Person alloc] initWithName:@"张三" andAge:12];
        Person *per2 = [p1 copy];
        
        //输出两个 Person 对象的地址,二者是不同的
        NSLog(@"per1 --> %p",per1);
        NSLog(@"per2 --> %p",per2);
    }
    return 0;
}
           

问:如果加入对于某些自定义对象是不可变的,怎么办呢?

答:只需要直接返回self就行了,如下:

-(id)copyWithZone:(NSZone *)zone
{
    return self;
}
           
这样,输出的两个对象的地址就是相同的了。

下面了解一下关于如果某一个自定义类继承了 这个Person类的情况。

如果某一个子类继承了实现了NSCopying协议的基类,那么该子类也是会自动继承这个协议的方法。但是需要自己重新实现。

例如:有一个Student子类继承了这个Person类:

//
//  Student.h
//  test_1
//
//  Created by lin on 14-8-26.
//  Copyright (c) 2014年 linshaolie. All rights reserved.
//

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

@interface Student : Person

@property(nonatomic,copy)NSString *school;

-(id)initWithName:(NSString *)name andAge:(int)age andSchool:(NSString*)school;

@end
           
//
//  Student.m
//  test_1
//
//  Created by lin on 14-8-26.
//  Copyright (c) 2014年 linshaolie. All rights reserved.
//

#import "Student.h"
#import "Person.h"

@implementation Student

-(id)initWithName:(NSString *)name andAge:age andSchool:(NSString*)school
{
    self = [super initWithName:name andAge:name];
    if ( self != nil )
    {
        self.school = school;
    }
    return self;
}

-(id)copyWithZone:(NSZone *)zone
{
    Student *student = [super copyWithZone:zone];
    student.school = self.school;
    return student;
}

@end
           
testing...
//
//  main.m
//  test_1
//
//  Created by lin on 14-8-26.
//  Copyright (c) 2014年 linshaolie. All rights reserved.
//

#import <Foundation/Foundation.h>
#import "Person.h"
#import "Student.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        Student *student1 = [[Student alloc] initWithName:@"小明" andAge:20];
        
        Student *student2 = [student1 copy];
        //打印不同地址
        NSLog(@"student1 --> %p", student1);
        NSLog(@"student2 --> %p", student2);
    }
    
    return 0;
}
           
注意其中copyWithZone方法的实现。
最后,记录下另外一种实现深复制的方法:使用Foundation的归档技术。下面是代码:
NSArray *dataArray1 = @[@"aaa", @"bbb", @"ccc"];
        NSData *data = [NSKeyedArchiver archivedDataWithRootObject:dataArray1];

        NSMutableArray *dataArray2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
        
//    上述即实现了将dataArray1深复制给dataArray2,另外一种简便写法是:

        NSLog(@"dataArray1 --> %p ", dataArray1);
        NSLog(@"dataArray2 --> %p ", dataArray2);