OC语法补充之-Copy与MutableCopy
概念
- 拷贝的特点:
- 生成一个跟原对象一样的副本
- 修改副本并不会影响原对象,修改原对象也不会影响副本对象
- iOS开发中提供了两种拷贝的方法
copy
: 只会产生不可变的副本对象(如: NSString)MutableCopy
:只会产生可变的副本对象(如:NSMutableNSString
)
- 拷贝的深度分为两种
- 深拷贝: 产生新对象的拷贝
- 浅拷贝: 没有产生新对象的拷贝
- iOS中的拷贝原则都遵从第1条的2个特点.
深拷贝与浅拷贝
-
代码举例:
//不可变对象的拷贝 - (void)test { NSString *string = @"hhhh"; //没有产生新对象(浅拷贝) NSString *str1 = [string copy]; //产生新对象(深copy) NSMutableString *str2 = [string mutableCopy]; NSLog(@"string====%p===str1===%p==str2==%p",string,str1,str2); /* 打印结果 string====0x1038fa0c0===str1===0x1038fa0c0==str2==0x60400005fcb0 */ } //可变对象的拷贝 - (void)test2 { NSMutableString *string = [NSMutableString string]; [string appendString:@"hhhh"]; //产生新对象(深copy) NSString *str1 = [string copy]; //产生新对象(深copy) NSMutableString *str2 = [string mutableCopy]; NSLog(@"string====%p===str1===%p==str2==%p",string,str1,str2); /* 打印结果如下: string====0x60400005fb90===str1===0x10bdcc030==str2==0x60400005fbc0 */ }
- 根据test与test2总结:
- 当原对象是可变的,无论是copy还是mutablecopy,都会产生新对象,深拷贝.
- 当原对象是不可变的,mutablecopy会产生新对象(深拷贝),copy不会产生新对象(浅拷贝)
- 内存分配如下:
- copy在MRC下的内存管理:
-
如果是浅拷贝:不会生成新的对象,但是系统就会对原来的对象进行
retain
,所以需要对原来的对象进行一次 -
如果是深拷贝:会生成新的对象,系统不会对原来的对象进行
retain
,但是因为生成了新的对象,所以我们需要对新的对象进行release
-
- 下图
- copy在MRC下的内存管理:
- 总结如下:
如何让一个对象具有copy功能
- 通常
NSString/NSArray/NSDictionary
等都遵守了NSCopying
协议,并且实现了相应的方法,所以能使用copy NSObject
对象没有遵守,因此NSObject
要实现copy
,那就必须遵守,跟实现一些条件- 一个对象要想能够调用
copy
方法,应遵守copy
的条件:- 遵守
NSCopying
协议 - 实现
copyWithZone:(NSZone *)zone
方法
- 遵守
NSObject
对象没有必要去实现mutableCopy
,因为不存在可变这个概念-
当对象调用了
copy
方法时,内部就会调用copyWithZone:(NSZone *)zone
,如果没有实现,那么就会错:-[Person copyWithZone:]: unrecognized selector sent to instance ...
-
代码举例:
#import <Foundation/Foundation.h> @interface Person : NSObject<NSCopying> @property (nonatomic,assign) NSInteger age; //@property (nonatomic,strong) NSString *name; @property (nonatomic,copy) NSString *name; @end #import "Person.h" @implementation Person -(id)copyWithZone:(NSZone *)zone{ Person *p = [[Person allocWithZone:zone]init]; p.age = self.age; p.name = self.name; return self; } //strong中set方法 //-(void)setName:(NSString *)name{ // _name = name; //} //copy中set方法与strong的不同 -(void)setName:(NSString *)name{ _name = [name copy]; } @end //应用 //如何让一个对象实现copy功能 - (void)test3 { Person *p1 = [[Person alloc] init]; p1.age = 5; NSLog(@"p1的对象%p===age = %zd",p1,p1.age); Person *p2 = [p1 copy]; //改变P2并不会影响p1 p2.age = 10; NSLog(@"p2的对象%p===age = %zd",p2,p2.age); /* 打印结果: p1的对象0x6040000267a0===age = 5 p2的对象0x6040000267a0===age = 10 */ }
copy用于@property中的作用
-
代码举例:
//copy用于@property中的作用 - (void)test4 { NSMutableString *sttt = [NSMutableString stringWithFormat:@"gggg"]; Person *p1 = [[Person alloc] init]; p1.age = 5; p1.name = sttt; [sttt appendString:@"xxxxx"]; NSLog(@" sttt当前内容为:%@====p1.name= %@",sttt,p1.name); }
//此时如果name用的是strong修饰,set方法内部是直接赋值,p1.name直接指向了sttt这个对象,如果改变了sttt,p1.name也会跟着改变. //打印结果: sttt当前内容为:ggggxxxxx====p1.name= ggggxxxxx //但是name如果用的是copy修饰,set方法内部进行了copy,如果改变了sttt,p1.name不变. //打印结果: sttt当前内容为:ggggxxxxx====p1.name= gggg
-
什么时间用
strong
?什么时间用copy
呢?strong
: 对象的属性希望跟着外边的内容变化而变化copy
: 对象的属性不受外边的影响,具有保护对象属性值的作用.
copy用于block中
block
默认存储在栈中, 栈中的block
访问到了外界的对象, 不会对对象进行retain
block
如果在堆中, 如果在block
中访问了外界的对象, 会对外界的对象进行一次retain
-
代码举例:
Person *p = [[Person alloc] init]; //1 NSLog(@"retainCount = %lu", [p retainCount]); void (^myBlock)() = ^{ NSLog(@"%@", p); NSLog(@"retainCount = %lu", [p retainCount]); }; //1 myBlock(); // 将block转移到堆中 Block_copy(myBlock); //2 myBlock();
- 在
@property
中使用copy作用:- 并不是拷贝, 而是转移,将这个block从栈转移到堆中.
- 防止外界对象提前销毁,出现野指针错误.
-
代码举例
//Person对象 #import <Foundation/Foundation.h> typedef void (^myBlock)(); @property (nonatomic, copy) NSString *name; // 注意: 如果是block使用copy:并不是拷贝, 而是转移,将这个block从栈转移到堆中. @property (nonatomic, copy) myBlock pBlock; //@property (nonatomic,assign) myBlock pBlock; @end #import "Person.h" @implementation Person - (void)dealloc { // 只要给block发送一条release消息, block中使用到的对象也会收到该消息 Block_release(_pBlock); NSLog(@"%s", __func__); [super dealloc]; } @end //使用 // 避免以后调用block的时候, 外界的对象已经释放了,因此出现野指针错误 Dog *d = [[Dog alloc] init]; // 1 NSLog(@"retainCount = %lu", [d retainCount]); Person *p = [[Person alloc] init]; p.pBlock = ^{ //由于pBlock用的是copy修饰,因此这个block在堆中了,所以会对block中的对象+1 = 2 NSLog(@"%@", d); }; NSLog(@"retainCount = %lu", [d retainCount]); // 2 // 如果狗在调用block之前释放了, 那么程序就会崩溃(如果block没有用copy修饰而是用的assign,那么d就是1了,此时d就会释放了.) [d release]; // 1 p.pBlock(); [p release];
copy修饰block引发循环引用
- block引发循环引用的因素:
- 一个对象拥有一个block属性,然而这个block内部却又直接或间接使用了这个对象或者对象的属性.
- block的循环引用分为2种:
- 第一种: self(控制器)有一个block属性,而在block中却又使用了self的属性
- 解决办法:
__weak typeof(self) weakSelf = self
;
- 解决办法:
- 第二种: 如果不是self呢? 是一个P对象有一个block属性,而在block中却又使用了P的属性呢?
- 解决办法: 创建这个对象时用
__block
修饰,此时的作用是当block
内部使用了自己时,不让block
对自己retain
(计数器加1) -
代码举例:
//如果对象中的block又用到了对象自己, 那么为了避免内存泄露, 应该将对象修饰为__block __block Person *p = [[Person alloc] init]; // 1 p.name = @"lnj"; NSLog(@"retainCount = %lu", [p retainCount]); p.pBlock = ^{ NSLog(@"name = %@", p.name); // 1 }; NSLog(@"retainCount = %lu", [p retainCount]); p.pBlock(); [p release]; // 0 // [p release]; // 2B
- 解决办法: 创建这个对象时用
- 第一种: self(控制器)有一个block属性,而在block中却又使用了self的属性
面试题
@property (nonatomic,copy) NSMutableString *name;
这句话合适吗?- 不合适,因为属性
name
的真实类型是不可变的即NSString
类型 -
该类的
set
方法为:-(void)setName:(NSMutableString *)name{ _name = [name copy]; }
- 由于
copy
方法返回的是一个不可变对象,然而_name
却是NSMutableString
类型修饰!!! - 但是如果用
strong
修饰没有问题,name
是NSMutableString
的
- 不合适,因为属性
- 那些地方用到copy? 为什么?
- NSString用到
- 对象的属性不受外边的影响,具有保护对象属性值的作用.
- block用到了copy
- copy的作用不是复制,而是将这个block从栈转移到堆中.
- 栈中的block不会对其内部使用的对象自动计数器+1,但是堆中的block会.
- 防止外界对象(block内部使用外界的对象)提前销毁,出现野指针错误.
- NSString用到