Objective-C——深复制和浅复制

一. 深复制和浅复制

  1. 只要是拷贝出来的对象, 拷贝出来的对象中的内容和以前对象中的内容一致。

  2. 什么时候生成新的对象,什么时候不生成新对象?

    (1). 生成新对象:拷贝出来的对象和以前的对象,只要两者中有一个是可变的对象,就必须要生成新的对象

    (2). 不生成新对象:拷贝出来的对象和以前的对象,只要两者都是不可变的对象,就不用生成新的对象

  3. 正是因为调用copy方法有时候会生成一个新的对象, 有时候不会生成一个新的对象,才有深复制和浅复制的概念
    (1).如果没有生成新的对象, 称为浅拷贝, 本质就是指针拷贝
    (2).如果生成了新的对象, 称为深拷贝, 本质就是会创建一个新的对象

  4. 代码验证

    //1.由一个不可变的字符串,用mutableCopy拷贝出来的是一个可变的字符串,要生成一个新的字符串对象
    //原因:拷贝前的字符串是不可变的,拷贝后的字符串是可变的,所有它们必须要生成一个新的字符串对象。
    NSString *srcStr = @"String";
    NSMutableString *copyStr = [srcStr mutableCopy];
    NSLog(@"srcStr = %@\tcopyStr = %@",srcStr,copyStr);
    NSLog(@"srcStr = %p\tcopyStr = %p",srcStr,copyStr);

----------

    //2.由一个可变的字符串,用mutableCopy拷贝出来的是一个可变的字符串,也要生成一个新的字符串对象
    //因为:因为原来的字符串和拷贝出来的字符串都是可变的,它们其中的任何一个改变都不能影响对方,所以拷贝后要生成新的字符串
    NSMutableString *srcStr = [NSMutableString stringWithString:@"String"];
    NSMutableString *copyStr = [srcStr mutableCopy];
    [copyStr appendString:@" copy"];
    NSLog(@"srcStr = %@\tcopyStr = %@",srcStr,copyStr);
    NSLog(@"srcStr = %p\tcopyStr = %p",srcStr,copyStr);

----------

    //3.由一个可变的字符串,用copy拷贝出来的是一个不可变的字符串,要生成一个新的字符串对象
    //原因:拷贝前的字符串是可变的,拷贝后的字符串是不可变的,所有它们必须要生成一个新的字符串对象。
    NSMutableString *srcStr = [NSMutableString stringWithString:@"String"];
    NSString *copyStr = [srcStr copy];
    [srcStr appendString:@" old"];
    NSLog(@"srcStr = %@\tcopyStr = %@",srcStr,copyStr);
    NSLog(@"srcStr = %p\tcopyStr = %p",srcStr,copyStr);

----------

    //4.由一个不可变的字符串,用copy拷贝一个不可变的字符串,不用生成新的字符串对象
    //原因:原来的对象和拷贝出来的对象都不能修改, 所以永远不能影响到另外一个对象, 已经符合OC为了对内存进行优化需所以就不会生成一个新的对象
    NSString *srcStr = @"String";
    NSString *copyStr = [srcStr copy];
    NSLog(@"srcStr = %@\tcopyStr = %@",srcStr,copyStr);
    NSLog(@"srcStr = %p\tcopyStr = %p",srcStr,copyStr);

二. Copy内存管理

  1. 浅复制:不会生成新的对象,但是系统会给原来的对象一次retain,所有最后要对原对象两次release

  2. 深复制:会生成新对象,不会对原对象retain,但最后要对拷贝出来的对象和原对象各一次release

  3. 总结:内存管理的原则, 有加就有减;一次alloc/retain/copy 对应一次 release

  4. 代码验证

    //通过C语言的字符数组转换成一个不可变的字符串,retainCount不加1
    NSString *oldStr = @"String";
    char *ch = "new String";
    oldStr = [NSString stringWithUTF8String:ch];
    NSLog(@"oldStr retainCount = %lu",[oldStr retainCount]);
    //浅拷贝,不会产生新对象, 会对原有对象进行一次retain
    NSString *newStr = [oldStr copy];
    NSLog(@"oldStr retainCount = %lu",[oldStr retainCount]);
    NSLog(@"newStr retainCount = %lu",[newStr retainCount]);
    //因为浅拷贝会对对象进行一次retain, 那么我们就需要对拷贝出来的对象进行一次release
    [oldStr release];
    [oldStr release];

----------

    //深拷贝,会生成新对象, 不会对原有对象进行retain
    char *ch = "new String";
    NSString *oldStr = [NSString stringWithUTF8String:ch];
    NSMutableString *newStr = [oldStr mutableCopy];
    NSLog(@"oldStr retainCount = %lu",[oldStr retainCount]);
    NSLog(@"newStr retainCount = %lu",[newStr retainCount]);
    //因为深拷贝生成了新对象, 所以我们就需要对拷贝出来的对象进行一次release
    [oldStr release];
    [newStr release];

三. copy与property的使用

1.声明一个类

----------Person.h文件
#import <Foundation/Foundation.h>

typedef void(^myBlock)();

@interface Person : NSObject

@property (nonatomic,copy) NSString *name;
@property (nonatomic,copy) myBlock pMyBlock;

@end

2.当对象的属性参数用retain时,即“@property (nonatomic,retain) NSString *name;”当改变外面的字符串时,赋给对象的字符串也会跟着改变,为了防止外界修改内部的数据,Property的参数要使用copy

    NSMutableString *name = [NSMutableString stringWithString:@"xiaoming"];
    Person *per = [[Person alloc] init];
    per.name = name;
    NSLog(@"per.name = %@",per.name);
    //在可变字符串后添加字符串
    [name appendString: @"ming"];
    NSLog(@"per.name = %@",per.name);

3.block默认存储在栈中, 栈中的block访问到了外界的对象, 不会对对象进行retain

    Person *per = [[Person alloc] init];
    NSLog(@"per retainCount = %lu",[per retainCount]);
    myBlock pBlock = ^{
      NSLog(@"%@",per);
      NSLog(@"per retainCount = %lu",[per retainCount]);
    };
    pBlock();

4.block在堆中时, 如果在block中访问了外界的对象, 会对外界的对象进行一次retain

    Person *per = [[Person alloc] init];
    NSLog(@"per retainCount = %lu",[per retainCount]);
    myBlock pBlock = ^{
      NSLog(@"%@",per);
      NSLog(@"per retainCount = %lu",[per retainCount]);
    };
    Block_copy(pBlock);//这里的不是复制,是将block转移到堆中
    pBlock();

5.可以在用@Property声明属性Block变量时,使用copy参数,这样就可以保证在block访问block外的对象时,对象的引用计数器加1,防止还没操作完对象,对象就提前被释放,程序会崩溃。

    Dog *dog = [[Dog alloc] init];
    Person *per = [[Person alloc] init];
    per.pMyBlock = ^{
      NSLog(@"%@",dog);
    };
    NSLog(@"dog retainCount = %lu",[dog retainCount]);
    [dog release];
    per.pMyBlock();
    [dog release];

6.注意点: copy block之后引发循环引用;如果对象中的block又用到了对象自己, 那么为了避免内存泄露, 应该将对象修饰为__block

    __block Person *per =[[Person alloc] init];
    per.name = @"xiaoming";
    NSLog(@"retainCount = %lu", [per retainCount]);
    per.pMyBlock = ^{
      NSLog(@"%@",per.name);
    };
    NSLog(@"retainCount = %lu", [per retainCount]);
    per.pMyBlock();
    [per release];

7.特别注意:@property (nonatomic, copy) NSMutableString *name;这句代码有问题!
(1).copy拷贝的对象永远是不可变的,也就是name对象是不可变的,因为它内部赋值方式是_name = [name copy];
(2).使用NSMutableString声明可变的指针指向不可变的name字符串对象,极其不合理,外界以为是NSMutableString类型,一旦调用appendString:方法,会直接报错,因为name对象本质是不可变的。
(3). 注意:copy 后面要跟不可变的属性
(4).代码验证

----------Person.h文件

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,copy) NSMutableString *name;
@end

----------Person.m文件

#import "Person.h"

@implementation Person

@end

----------main.m文件

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

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

    Person *per = [[Person alloc] init];
    per.name = @"小";
    [per.name appendString: @"明"];//此句报错
    //在[per.name appendString: @“明”];语句上,编译器会提示:”No visible @interface for ‘NSString’ declares the selector ‘appendString:’”,说明,编译器实例变量定义的类型是NSString
  }
    return 0;
}

四. 自定义类实现Copy

  1. 想让自定义的对象能够被copy只需要遵守NSCopying协议,并实现- (id)copyWithZone:(NSZone *)zone方法即可。

  2. 想让自定义的对象能够被mutableCopy只需要遵守NSMutableCopying协议,并实现- (id)mutableCopyWithZone:(NSZone *)zone方法即可。

  3. 一般没有必要使用mutableCopy拷贝自定义对象,因为使用copy方法拷贝的自定义对象也可以是可变的,两个方法的作用一样,一般使用copy拷贝自定义对象

  4. 重写- (id)copyWithZone:(NSZone *)zone方法
    (1).获取当前类:获取当前对象的类[self class],如果不用self,当子类调用- (id)copyWithZone:方法重写子类的- (id)copyWithZone:时,新开辟的空间是用父类创建的,会报错。
    (2).创建一个新对象:用allocWithZone开辟空间,并用init初始化。
    (3).给新对象赋值,如果新对象是用id类型定义,只能用Setter方法设置值
    (4).返回新对象

  5. - (id)copyWithZone:方法和 - (id)mutableCopyWithZone:方法重写的内容是一样的!

  6. 代码演示

----------Person.h文件

#import <Foundation/Foundation.h>

@interface Person : NSObject < NSCopying,NSMutableCopying>

@property (nonatomic,copy) NSString *name;

@property (nonatomic,assign) int age;

@end

----------Person.m文件

#import "Person.h"

@implementation Person

- (id)copyWithZone:(NSZone *)zone {
  //一、原始写法
  //1.获取当前类
  Class selfClass = [self class];
  //2.创建一个新对象
  Person *person = [[selfClass allocWithZone:zone] init];
  //3.给新对象赋值
  person.age = _age;
  person.name = _name;
  //4.返回新对象
  return person;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
  //二、改进写法
  //1.创建一个新对象
  id person = [[[self class] allocWithZone:zone] init];
  //2.给新对象赋值
  [person setAge:_age];
  [person setName:_name];
  //3.返回新对象
  return person;
}

- (NSString *)description
{
  return [NSString stringWithFormat:@"name = %@, age = %i", _name, _age];
}
@end

----------Student.h文件

#import "Person.h"

@interface Student : Person

@property (nonatomic,) double height;

@end

----------Student.m文件

#import "Student.h"

@implementation Student

- (id)copyWithZone:(NSZone *)zone {
   //一、原始写法
   // 1.创建副本
   id obj = [[self class] allocWithZone:zone];

   // 2.设置数据给副本
   [obj setAge:[self age]];
   [obj setName:[self name]];
   [obj setHeight:_height];

   // 3.返回副本
   return obj;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
  //二、改进写法
  // 1.创建副本:调用父类的copyWithZone:方法创建副本
  id obj = [super copyWithZone:zone];

  // 2.设置数据给副本
  [obj setHeight:_height];

  // 3.返回副本
  return obj;
}

- (NSString *)description
{
  return [NSString stringWithFormat:@"name = %@, age = %i, height = %f", [self name], [self age], _height];
}
@end

----------main.m文件

#import "Student.h"

@implementation Student

- (id)copyWithZone:(NSZone *)zone {
   //一、原始写法
   // 1.创建副本
   id obj = [[self class] allocWithZone:zone];

   // 2.设置数据给副本
   [obj setAge:[self age]];
   [obj setName:[self name]];
   [obj setHeight:_height];

   // 3.返回副本
   return obj;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
  //二、改进写法
  // 1.创建副本:调用父类的copyWithZone:方法创建副本
  id obj = [super copyWithZone:zone];

  // 2.设置数据给副本
  [obj setHeight:_height];

  // 3.返回副本
  return obj;
}

- (NSString *)description
{
  return [NSString stringWithFormat:@"name = %@, age = %i, height = %f", [self name], [self age], _height];
}
@end
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值