浅拷贝和深拷贝 & copy的内存管理

copy


本文旨在解决以下问题:

  • 1.区别浅拷贝和深拷贝
  • 2.copy的内存管理
  • 3.@property中的copy关键字的使用
  • 4.copy与block
  • 5.如何自定义类实现copy操作

copy基本概念

  • copy:创建不可变副本
    • 使用copy,该类需要遵守NSCopying协议, 实现copyWithZone:方法
  • mutableCopy:创建可变副本
    • 需要遵守NSMutableCopying协议,实现mutableCopyWithZone:方法

浅拷贝和深拷贝

  • 浅复制:浅拷贝,指针拷贝,shallow copy

    • 不产生新的对象,源对象和副本对象是同一对象
    • 源对象(即副本对象)引用计数器+1,相当于做了一次retain
  • 深复制:深拷贝,内容拷贝,deep copy

    • 产生新的对象,源对象和副本对象不是同一个对象
    • 源对象引用计数器不变,副本对象计数器为1(因为是新的)

深拷贝和浅拷贝区别:

  • 根本就是:在于产生的副本对象跟原对象会不会一个改动了,另一个跟着变
浅拷贝和深拷贝 & copy的内存管理_第1张图片
浅拷贝和深拷贝 & copy的内存管理_第2张图片
结论:只有当不可变的源对象通过copy方法创建了一个不可变的副本对象,才是浅拷贝。

copy的内存管理

  • 浅拷贝
    • 原有对象引用计数器+1
    • 必须对源对象(即副本对象)做一次release
NSString *str1 = [[NSString alloc] initWithFormat:@"erer"];       
NSString *str2 = [str1 copy];       
[str1 release];
  • 深拷贝
    • 源对象引用计数器不变,副本对象计数器为1
    • 需要对副本对象做一次release
NSString *str1 = [[NSString alloc] initWithFormat:@"erer"];       
NSMutableString *str2 = [str1 mutableCopy];       
[str2 release];

@property中copy修饰字符

----用retain修饰字符串的情况下
@interface Person : NSObject
@property (nonatomic, retain) NSString *name;
@end
NSMutableString *str = [NSMutableString stringWithFormat:@"erer"];

Person *p = [[Person alloc] init];
p.name = str;
// person中的属性会被修改
[str appendString:@" cool"];
NSLog(@"name = %@", p.name);
浅拷贝和深拷贝 & copy的内存管理_第3张图片
内存分析

问题一:当修改外界str时,p.name也跟着修改了,非常不安全。
问题二:此时Person对象不会释放

----用copy修饰字符串的情况下
@interface Person : NSObject
@property (nonatomic, retain) NSString *name;
@end
NSMutableString *str = [NSMutableString stringWithFormat:@"erer"];

Person *p = [[Person alloc] init];
p.name = str;
// person中的属性不会被修改
[str appendString:@" cool"];
NSLog(@"name = %@", p.name);
浅拷贝和深拷贝 & copy的内存管理_第4张图片
内存分析

@property中copy修饰block

----不用copy(用assign)修饰block对象时
Person *p = [[Person alloc] init];
p.name = @"erer";
Dog *d = [[Dog alloc] init];
d.age = 10;
NSLog(@"retainCount = %lu", [d retainCount]); // 1
p.pBlock = ^{
  // 报错, 调用之前就销毁了
  NSLog(@"age = %d", d.age);
};
[d release]; // 0
p.pBlock();
[p release];

我们不能确定什么时候调用block,而在block内部访问对象d时,对象可能已经释放,报野指针错误

----用copy修饰block对象时
Person *p = [[Person alloc] init];
p.name = @"erer";
Dog *d = [[Dog alloc] init];
d.age = 10;
NSLog(@"retainCount = %lu", [d retainCount]); // 1
p.pBlock = ^{
     //block在堆中,block访问外界对象,会对使用到的外界对象进行一次retain
    NSLog(@"age = %d", d.age);
    NSLog(@"retainCount = %lu", [d retainCount]); // 1
};
[d release]; // 1
p.pBlock();
[p release];

block可以保住对象的命,但会让对象计数器+1, 因此需要在对象的delloc方法中,给block发送一条release消息。

@implementation Person
- (void)dealloc
{
    // 只要给block发送一条release消息, block中使用到的对象也会收到该消息
    Block_release(_pBlock);
    [super dealloc];
}
@end

``

blcok补充
- block默认存储在栈中,栈中的block访问到外界的对象,不会对对象进行retain
- block如果在堆中,如果block访问了外界的对象,会对外界的对象进行一次retain


##### ----总结:@property内存管理策略选择

- 非ARC

    - copy : 只用于NSString\block
    - retain : 除NSString\block以外的OC对象
    - assign :基本数据类型、枚举、结构体(非OC对象),当2个对象相互引用,一端用retain,一端用assign
    
- ARC

    - copy : 只用于NSString\block
    - strong : 除NSString\block以外的OC对象
    - weak : 当2个对象相互引用,一端用strong,一端用weak
    - assgin : 基本数据类型、枚举、结构体(非OC对象)

### 自定义类实现copy操作

- 让类遵守NSCopying协议
- 实现 copyWithZone:方法,在该方法中返回一个对象的副本即可。
- 在copyWithZone方法中,创建一个新的对象,并设置该对象的数据与现有对象一致, 并返回该对象.

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

##### ----无父类实现

-(id)copyWithZone(NSZone *)zone{

CustomMode *custom = [[[self class] copyWithZone:zone] init];

Custom ->_a = [_a copyWithZone:zone];
Custom -> _c = _c;//不是对象的 直接赋值
Return custom;

}


##### ----有父类实现
![
![Snip20160331_9.png](http://upload-images.jianshu.io/upload_images/704596-4d15c7bef4d17d12.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)](http://upload-images.jianshu.io/upload_images/704596-18f4448954d1dde0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

- 不调用父类方法, 无法拷贝父类中继承的属性
- 不重新父类copyWithZone, 无法拷贝本来中的特有属性

-(id)copyWithZone(NSZone *)zone{

CustomModel *custom = [super copyWithZone:zone];

Return custom;

}

你可能感兴趣的:(浅拷贝和深拷贝 & copy的内存管理)