一. 深复制和浅复制
只要是拷贝出来的对象, 拷贝出来的对象中的内容和以前对象中的内容一致。
什么时候生成新的对象,什么时候不生成新对象?
(1). 生成新对象:拷贝出来的对象和以前的对象,只要两者中有一个是可变的对象,就必须要生成新的对象
(2). 不生成新对象:拷贝出来的对象和以前的对象,只要两者都是不可变的对象,就不用生成新的对象
正是因为调用copy方法有时候会生成一个新的对象, 有时候不会生成一个新的对象,才有深复制和浅复制的概念
(1).如果没有生成新的对象, 称为浅拷贝, 本质就是指针拷贝
(2).如果生成了新的对象, 称为深拷贝, 本质就是会创建一个新的对象代码验证
//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内存管理
浅复制:不会生成新的对象,但是系统会给原来的对象一次retain,所有最后要对原对象两次release
深复制:会生成新对象,不会对原对象retain,但最后要对拷贝出来的对象和原对象各一次release
总结:内存管理的原则, 有加就有减;一次alloc/retain/copy 对应一次 release
代码验证
//通过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
想让自定义的对象能够被copy只需要遵守NSCopying协议,并实现- (id)copyWithZone:(NSZone *)zone方法即可。
想让自定义的对象能够被mutableCopy只需要遵守NSMutableCopying协议,并实现- (id)mutableCopyWithZone:(NSZone *)zone方法即可。
一般没有必要使用mutableCopy拷贝自定义对象,因为使用copy方法拷贝的自定义对象也可以是可变的,两个方法的作用一样,一般使用copy拷贝自定义对象
重写- (id)copyWithZone:(NSZone *)zone方法
(1).获取当前类:获取当前对象的类[self class],如果不用self,当子类调用- (id)copyWithZone:方法重写子类的- (id)copyWithZone:时,新开辟的空间是用父类创建的,会报错。
(2).创建一个新对象:用allocWithZone开辟空间,并用init初始化。
(3).给新对象赋值,如果新对象是用id类型定义,只能用Setter方法设置值
(4).返回新对象- (id)copyWithZone:方法和 - (id)mutableCopyWithZone:方法重写的内容是一样的!
代码演示
----------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