Copy的那点事儿~

Copy的简单使用

copy 的效果

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

###OC中copy的方法

copy–>建立对象的副本

  • 如果对象有可变/不可变版本的区别,copy方法,只能拷贝出不可变的版本
  • 如果对象没有可变/不可变的区别,copy方法就是建立一个副本

mutableCopy
* 建立对象的可变副本(如果对象有”可变/不可变”版本的区别,才需要使用此方法)

###副本的特点
彼此的内容一样,具有相同的方法

可变版本对象的copy

/** 可变版本对象的copy
    对于可变对象的copy/mutableCopy都是深拷贝
 */
- (void)copyDemo1 {
    // arrayM本身为可变对象
    NSMutableArray *arrayM = [NSMutableArray array];
    NSLog(@"源对象\t\t%@\t内存地址%p", arrayM.class, arrayM);

    // copy ---> 不可变 地址变化 新的对象
    // 无论是可变对象,还是不可以变对象,copy之后都会编程 不可变
    id a = [arrayM copy];
    NSLog(@"copy后\t%@\t内存地址%p", [a class], a);

    // mutableCopy  => 可变 地址变化 新的对象,mutableCopy可以保持可变的特性
    id aM = [arrayM mutableCopy];
    NSLog(@"mutableCopy后%@\t内存地址%p", [aM class], aM);
}

运行结果:

源对象     __NSArrayM  内存地址0x7f91c2c1d940
copy__NSArrayI  内存地址0x7f91c2e09a00
mutableCopy后__NSArrayM  内存地址0x7f91c2d23660

不可变版本对象的copy

/** 不可变版本对象的copy */
- (void)copyDemo2 {
    NSArray *array = [NSArray array];
    NSLog(@"源对象%@ 内存地址%p", array.class, array);

    // copy ---> 不可变 地址没有变化 引用计数+1
    // 对于不可变对象的copy操作,进行的浅拷贝,系统并不会为之分配内存空间,仅仅是retainCount+1
    id a = [array copy];
    NSLog(@"copy后%@ 内存地址%p", [a class], a);

    // mutableCopy ---> 可变 地址变化 新的对象
    // 不可变的mutaleCopy的操作会变为可变对象
    id aM = [array mutableCopy];
    NSLog(@"mutableCopy%@ 内存地址%p", [aM class], aM);
}

执行结果:

 源对象__NSArrayI 内存地址0x7fb4e07010d0
 copy__NSArrayI 内存地址0x7fb4e07010d0
 mutableCopy__NSArrayM 内存地址0x7fb4e063daf0

小结:

  • 不要随随变变给可变对象做 copy 操作

  • 都会建立新的副本,深拷贝(只要有一个可以修改,就是深拷贝)

    可变  ----> 可变
    可变  ----> 不可变
    不可变 ----> 可变
  • 不会建立新的副本,只是引用计数+1,浅拷贝,指针拷贝(两个对象前后都不需要修改)

    不可变 =》 不可变

Copy属性

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

####开闭原则

-开:对内开放,向怎么改,就怎么改
-闭:对外封闭,只能用,不能改

  • 定义成 copy 属性,在设置数值的时候,会默认做一次 copy 操作

    • 如果设置的数值是可变的,做一次copy,会新建副本
    • 如果设置的数值是不可变的,做一次copy,只是引用计数+1,不会建立新的副本!跟strong类型一致的!

建议:如果属性是 NSString,建议使用 copy 属性

注意:可变字符串一定不要使用 copy 属性

// 头衔,如果区分可变和不可变版本,做一次copy操作得到的就是不可变的字符串!
@property (nonatomic, copy) NSMutableString *title;

对象的类型

  • 1> 一个对象的准确类型,是在给该对象”分配内存空间”的时候指定的类型
  • 2> 对象的”类型”,是程序员指定该对象的类型,指定类型之后,就可以具有该对象的方法!
  • 3> 能否使用对象的方法,取决于运行时,这个对象的类型是否真的正确!
  • 4> 如果类型不正确会出现 -[NSObject length]: unrecognized selector
NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];

    Person *p = [[Person alloc] init];
    p.name = strM;

    NSLog(@"%@", p.name);  // zhangsan

    [strM setString:@"lisi"];
    NSLog(@"===> %@", p.name);  // zhangsan

    // 问题:p.name的类型 NSString & NSMutableString
    // 答案:NSCFString --->  NSString
    // NSMutableString类型的数据做了一次copy后,会变为不可变的NSString类型
    id obj = p.name;
    NSLog(@"%@", [p.name class]);  // NSCFString
//    [obj setString:@"wangwu"];  // 报错,不应该对NSString进行修改
    NSLog(@"===> %@", p.name);  // wangwu
  NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
    NSString *str = @"haha";
    NSLog(@"%p", str);  // 0x10a182138

    Person *p = [[Person alloc] init];
//        p.name = strM;
    p.name = str;
    NSLog(@"%p", p.name); //0x10a182138  跟 str地址是一样滴~,p.name指向了str的空间

    NSLog(@"%@", p.name); // haha

    [strM setString:@"lisi"];
    NSLog(@"===> %@ %@", p.name, strM);  // p.name 和 strM不可能一样,strM的改变不会影响到p.name
  // 从网络获取到一个字符串
    NSMutableString *strM = [NSMutableString stringWithString:@"BOSS"];

    Person *p = [[Person alloc] init];
    p.title = strM;

    [strM setString:@"经理"];
    NSLog(@"===> %@ %@", p.title, strM);

    // Attempt to mutate immutable object with setString:
    // 试图使用 setString: 方法修改"不可变对象"?
    // setString方法,是title存在之后,修改title的内容!
    [p.title setString:@"jingli"];  // 程序会崩掉,p.title为不可变对象
    NSLog(@"!!!!> %@ %@", p.title, strM);

Copy的自定义对象

  • 在很多商业级应用程序或者第三方框架,在开发时的模型通常会支持 copy

    NSCache & NSMutableDictionary

    -NSCache 的 key strong 的
    -Dict 的 key 是 copy 的

    // NSMutableDictionary *dictM = [NSMutableDictionary dictionary];
    // dictM setObject:<#(id)#> forKey:<#(id<NSCopying>)#>

  • 如果自定义对象要当作字典的 key,需要支持 copy!

Person.h

/**
 要让自定义对象支持 copy,需要做两件事情

 1. 遵守 NSCopying 协议
 2. 实现 copyWithZone: 方法
 */
@interface Person : NSObject <NSCopying>
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
Person.m
/**
 有返回值 -》 copy 出来的新对象
 是一个对象方法 -> 将 self 建立一个副本

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

 在今天的开发中,zone几乎可以忽略

 如果对象没有 可变/不可变 的版本区别,只要实现 copyWithZone 方法即可
 */
- (id)copyWithZone:(NSZone *)zone {
    // copy 是要建立一个新的副本,和当前的对象具有相同的内容
    // 1. 实例化 person 对象
    Person *p = [[Person alloc] init];

    p.name = self.name;
    p.age = self.age;

    return p;
}

// 只需要写模型的description就可以了,返回对象的描述信息,便于调试使用,类似于 java 中的 toString()
- (NSString *)description {
    // JSON的格式和字典非常像
//    @{@"name": @"zhangsan", @"age": @(19)}
    return [NSString stringWithFormat:@"<%@: %p> {name: %@, age: %d}", self.class, self, self.name,self.age];
}

// 也可以输出调试信息的字符串,专门用来调试使用的
// 有的网站上的培训资料会提到这个方法,跟 description 方法非常类似!
// 但是:如果在应用程序中,使用了这个方法,应用程序无法上架!苹果会认为使用了私有API
//- (NSString *)debugDescription {
//
//}
- (void)viewDidLoad {
    [super viewDidLoad];

    Person *p = [[Person alloc] init];
    p.name = @"zhangsan";
    p.age = 19;
    NSLog(@"%@--- %p", p.name, p); // zhangsan--- 0x7fbacbf17120

    Person *p1 = [p copy];
    p1.name = @"xiaofang";

    // 注意,地址不同,说明实现了自定义对象的copy
    NSLog(@"%@--- %p", p1.name, p1); // xiaofang--- 0x7fbacbe13900


}

子类对象的Copy

#import "Person.h"

@interface Student : Person
// 学号
@property (nonatomic, copy) NSString *no;
@end

```objc





<div class="se-preview-section-delimiter"></div>

#import "Student.h"

@implementation Student

- (id)copyWithZone:(NSZone *)zone {
    // 执行父类的 copy 方法,会把父类中的属性完全 copy
    Student *s = [super copyWithZone:zone];

    // 在子类的copy方法中,只需要给子类特有的属性进行赋值即可!
    s.no = self.no;

    return s;
}

@end




<div class="se-preview-section-delimiter"></div>

在父类的copyWithZone方法中:
要写:

    Person *p = [[self.class alloc] init];

    p.name = self.name;
    p.age = self.age;




<div class="se-preview-section-delimiter"></div>

不能写 Person * p = [[Person alloc] init];这么干了,只能copy出Person对象,不对子类起作用。

测试Demo
// 一个对象的准确类型,取决分配内存空间指定的类型
    Person *p = [[Student alloc] init];
    p.name = @"zhangsan";
    p.age = 19;
    // 给对象指定的类型,决定了能够使用对象的哪些属性和方法
//    p.no = @"001";
//    NSLog(@"%@ %@", p, p.no);
    NSLog(@"%@", p); // {name: zhangsan, age: 19}

    // copy会执行父类的copy方法
    Student *p1 = [p copy];  // 虽然是父类的引用,但实际上copy的是子类
    p1.name = @"xiaofang";

    NSLog(@"%@ %@", p1, p1.no); // <Student: 0x7fb0f9443150> {name: xiaofang, age: 19} (null)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值