Copy的那点事情
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)