Copy

主要内容:

  • copy的基本使用
  • 自定义对象的copy属性
  • 支持copy的自定义对象

 

 

1. copy的基本使用

♠ copy的效果:

  对源对象进行copy,建立出新的副本,彼此修改互不干扰!

♠ OC中有两种copy方式

  1> copy

    如果对象有可变/不可变之分,copy只能copy出不可变版本,如果没有此区分,copy方法就是建立一个副本。

  2> mutableCopy

    建立对象的可变副本(仅仅是当对象有可变和不可变版本时才需要是要本方法)

 

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    [self copyDemo1];
    NSLog(@"------------------");
    [self copyDemo2];
}

- (void)copyDemo1{
    NSString *str1 = @"copy1";
    NSLog(@"%@ %p",[str1 class],str1);
    // copy   => 不可变  不产生新对象 相当于引用计数器+1
    id  obj = [str1 copy];
    NSLog(@"%@ %p",[obj class],obj);
    // mutableCopy  => 可变  产生新对象
    id obj2 = [str1 mutableCopy];
    NSLog(@"%@ %p",[obj2 class],obj2);
}

- (void)copyDemo2{
    NSMutableString *str2 = [NSMutableString stringWithString:@"copy2"];
    NSLog(@"%@ %p",[str2 class],str2);
    // copy   => 不可变 产生新对象
    id obj1 = [str2 copy];
    NSLog(@"%@ %p",[obj1 class],obj1);
    // mutableCopy  => 可变,产生新对象
    id obj2 = [str2 mutableCopy];
    NSLog(@"%@ %p",[obj2 class],obj2);
    
}
@end

♠ 小结:

  可变    ---> 不可变

  可变    ---> 可变

  不可变 ---> 可变                      以上3种都会产生新的对象(深拷贝)

 

  不可变 ---> 不可变                   不会产生新对象,仅仅是对计数器+1(浅拷贝)

 


2. 自定义对象的copy属性

♠ 当一个对象的NSString类型属性使用strong 修饰

@property (nonatomic,strong) NSString *name;

 如果对该属性做如下设置

person *p = [[person alloc]init];
    
    NSMutableString *str = [NSMutableString stringWithString:@"aaaa"];
    
    p.name = str;
    
    NSLog(@"%@",[p.name class]);   // 输出结果:__NSCFString
    
    [str setString:@"bbbb"];
    
    NSLog(@"%@",[p.name class]);   // 输出结果:__NSCFString

从打印输出可以发现:

p.name属性的类型已经变为NSMutableString类型,而与我们当初定义的类型不一致,为什么?

  因为一个对象的准确类型是在给改对象“分配内存空间”的时候制定的类型,而程序员指定属性为某对象的类型之后就可以具有该对象的方法,而能否运行成功取决于该属性的实际类型,如果使用了实际类型不存在的方法,将会报"unrecognized selector send to instance"

例如:

id aa  = [[NSObject alloc]init];
    
    NSString *string = aa;
    
    NSLog(@"%zd",string.length);

  此代码块中,定义了NSString类型的变量,试图指向了NSObject的对象,当调用NSString的length方法时,编译可以通过,但是由于string的实际类型是NSObject类型,并不存在该方法,所有会报方法不存在错误:

unrecognized selector sent to instance 0x7f9628c8b7f0'

 

发现问题:

  当我们使用strong修饰成员属性时,将会有多个指针指向相同的对象,并可对其进行操作,而在程序设计过程中,有些时候我们的操作并不希望影响源数据,此时可能就需要改变变量的修饰为copy

 

♠ 面向对象程序开发中,有一个非常重要的原则

开闭原则:

  - 开:对内开放,想怎么改,就怎么改

  - 闭:对外封闭,只能用,不能改

定义成copy的属性,在设置时会默认进行一次copy操作;

  -> 对可变属性进行copy ———— 新建副本

  -> 对不可变属性进行copy ———— 不会创建新的对象,只是计数器+1,跟strong类型一致的。

使用注意:

对于有可变与不可变之分类型的属性而言,由于copy操作得到的将是不可变类型,所有对于可变类型的属性,不应该使用copy去修饰。


 

3. 支持copy的自定义对象

先来看程序,有自定义类,有两个成员属性

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

控制器中实例化对象,并尝试使用copy

person *p1 = [[person alloc] init];
    p1.name = @"aaa";
    p1.age  = 12;
    
    person *p2 = [p1 copy];
    p2.name = @"bbb";
    p2.age  = 14;

运行可发现:

-[person copyWithZone:] 系统将提示没有实现copyWithZone方法;

 

那么问题来了,copyWithZone:与copy方法有什么关系?

苹果官方文档中描述:

Returns the object returned by copyWithZone:,

This is a convenience method for classes that adopt the NSCopyingprotocol. An exception is raised if there is no implementation for copyWithZone:.

NSObject does not itself support the NSCopyingprotocol. Subclasses must support the protocol and implement the copyWithZone: method. A subclass version of the copyWithZone: method should send the message to super first, to incorporate its implementation, unless the subclass descends directly from NSObject.

我们可以从中得到一些信息:
1> copy 是 copyWithZone的简化形式,如果没有实现copyWithZone方法而调用copy会出现异常;

2> NSObject 类本身并没有遵守NSCopying协议,子类想要使用copy方法,必须遵守协议,并且实现copyWithZone方法;

3> 子类实现copyWithZone:方法必须先调用父类的copyWithZone,除非子类直接继承于NSObject

也就是说若要是的我们自定义类能够拷贝,有两个条件:

  * 类遵守NSCopying协议

  * 类实现copyWithZone:方法

      Zone: 分配对象是需要内存空间的,如果指定了zone,就可以指定新对象的内存空间,但是zone是一个非常古老的技术,为避免在堆中出现碎片而使用的,如今已几乎不用。

- (id)copyWithZone:(NSZone *)zone{
    person *p = [[self.class alloc]init];
    p.name = self.name;
    p.age  = self.age;
    
    return p;
}

self.class 的原因:

  1> copyWithZone: 是一个对象方法,self.class 获得类对象

  2> 保证创建的对象都是person类或者子类对象

如果父类也实现了copyWithZone:方法,必须调用父类copyWithZone:方法。

 


 4. copy修饰的结构体

  问题:block类型的变量为什么要用copy修饰?

    因为在ARC下,编译器底层对block做了一些优化,可以防止出现内存泄露,如果使用了strong,相当于强引用了一个栈区变量,从内存管理的角度而言,程序员需要管理的仅仅是堆区,所有在对block类型变量进行复制时要使用copy,对值进行一次copy操作,将其copy到堆区。