基本概念
原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
从上图可以看到,Prototype类中包括一个clone方法,Client调用其拷贝方法clone即可得到实例,不需要手工去创建实例。ConcretePrototype1和ConcretePrototype2为Prototype的子类,实现自身的clone方法,如果Client调用ConcretePrototype1的clone方法,将返回ConcretePrototype1的实例。简单来理解就是根据这个原型创建新的对象,而且不需要知道任何创建的细节。打个比方,以前生物课上面,有一个知识点叫细胞分裂,细胞在一定条件下,由一个分裂成2个,再由2个分裂成4个……,分裂出来的细胞基于原始的细胞(原型),这个原始的细胞决定了分裂出来的细胞的组成结构。这种分裂过程,可以理解为原型模式。
浅复制和深复制
浅复制:只复制了指针值,并没有复制指针指向的资源(即没有创建指针指向资源的副本),复制后原有指针和新指针共享同一块内存。
深复制:不仅复制了指针值,还复制了指针指向的资源。
下面的示意图左边为浅复制,右边为深复制。
Cocoa Touch框架为NSObject的派生类提供了实现深复制的协议,即NSCopying协议,提供深复制的NSObject子类,需要实现NSCopying协议的方法(id)copyWithZone:(NSZone *)zone。NSObject有一个实例方法(id)copy,这个方法默认调用了[self copyWithZone:nil],对于引用了NSCopying协议的子类,必须实现(id)copyWithZone:(NSZone *)zone方法,否则将引发异常,异常信息如下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Prototype copyWithZone:]: unrecognized selector sent to instance 0x100114d50'
http://www.cnblogs.com/eagle927183/p/3462439.html
代码实现
定义了一个Persion类,并实现NSCopying的方法- (id)copyWithZone:(NSZone *)zone
@interface Persion : NSObject
@property (nonatomic, strong)NSString *name;
@end
#import "Persion.h"
@interface Persion()<NSCopying>
@end
@implementation Persion
- (instancetype)init
{
self = [super init];
if (self) {
self.name = @"小萝卜";
}
return self;
}
// 实现NSCopying中的方法
- (id)copyWithZone:(NSZone *)zone
{
return [[[self class] allocWithZone:zone] init];
}
@end
实现复制的代码
Persion *p0 = [[Persion alloc] init];
Persion *p1 = [p0 copy];// 这个操作,创建了一个新的指针,指针指向了一个新的地址,地址的内容同p0的内容一样,但是他们的内存地址是不一样的,深复制是将原内存的内容复制到一个新的内存当中
Persion *p2 = p0;// 这个操作,创建了一个新的指针,指针还是指向p0所指向的内容,指向的内存地址没有发生变化
NSLog(@"未修改前:%p,name %@",p0,p0.name);
NSLog(@"深拷贝(指针指向的内存地址发生变化):%p,name %@",p1,p1.name);
NSLog(@"浅拷贝(指针指向的内存地址没有发生变化):%p,name %@",p2,p2.name);
2017-04-13 09:47:18.926 原型模式[1043:27289] 未修改前:0x60000000d580,name 小萝卜
2017-04-13 09:47:18.926 原型模式[1043:27289] 深拷贝(指针指向的内存地址发生变化):0x60000000d5b0,name 小萝卜
2017-04-13 09:47:18.927 原型模式[1043:27289] 浅拷贝(指针指向的内存地址没有发生变化):0x60000000d580,name 小萝卜
当对Persion中的name的值进行改版,观察他们name值得变化更能说明他们的的内存地址是否是同一个,如果不是同一个的话,那么他们之间是不会相互影响的,如果是同一个的话,一个进行改变,那另一个也会随之发生变化,代码如下
NSLog(@"未修改前p0:%p,name %@",p0,p0.name);
p1.name = @"傻傻小萝卜";
NSLog(@"原来p0:%p,name %@",p0,p0.name);
NSLog(@"深拷贝(指针指向的内存地址发生变化)p1:%p,name %@",p1,p1.name);
p2.name = @"快乐的小萝卜";
NSLog(@"原来p0:%p,name %@",p0,p0.name);
NSLog(@"浅拷贝(指针指向的内存地址没有发生变化)p2:%p,name %@",p2,p2.name);
2017-04-13 09:58:57.880 原型模式[1215:37135] 未修改前p0:0x60800000cbd0,name 小萝卜
2017-04-13 09:58:57.880 原型模式[1215:37135] 原来p0:0x60800000cbd0,name 小萝卜
2017-04-13 09:58:57.881 原型模式[1215:37135] 深拷贝(指针指向的内存地址发生变化)p1:0x60800000cbe0,name 傻傻小萝卜
2017-04-13 09:58:57.881 原型模式[1215:37135] 原来p0:0x60800000cbd0,name 快乐的小萝卜
2017-04-13 09:58:57.881 原型模式[1215:37135] 浅拷贝(指针指向的内存地址没有发生变化)p2:0x60800000cbd0,name 快乐的小萝卜
通过代码我们可知:
(1)实现- (id)copyWithZone:(NSZone *)zone这个方法实现了深复制,p1 和p0 他们的内存地址不同,当修改p1中name的值时,p0的值不发生变化,更加证实了他们的内存地址通,这个可以判断为深复制
(2)而p0和p2 他们指向的内存地址是相同的,当修改p2的name的值时,p0所指向的内存地址中的值也发生了相应的变化,说明只是指向内存地址的简单复制,所以为浅复制
assign ,copy 和retain
通过一段代码,在Persion类中定义三个name属性,分别为assign,copy,和retain三种
@interface Persion : NSObject
@property (nonatomic, assign)NSString *nameAssign;
@property (nonatomic, copy)NSString *nameCopy;
@property (nonatomic, strong)NSString *nameRetain;
@end
实现代码(特别注意:在ARC下是不能够使用retainCount这个方法的,只能使用CFGetRetainCount((__bridge CFTypeRef)object) 来实现)
Persion *p = [[Persion alloc] init];
NSMutableString *name = [[NSMutableString alloc] initWithString:@"abc"];
NSLog(@"name retainCount:%ld name:%p %@",CFGetRetainCount((__bridge CFTypeRef)name),name,name);
p.nameAssign = name;
NSLog(@"After assign name retainCount:%ld name:%p %@ nameAssign %p",CFGetRetainCount((__bridge CFTypeRef)name),name,name,p.nameAssign);
p.nameCopy = name;
NSLog(@"After copy name retainCount:%ld name:%p %@ nameCopy %p",CFGetRetainCount((__bridge CFTypeRef)name),name,name,p.nameCopy);
p.nameRetain = name;
NSLog(@"After retain name retainCount:%ld name:%p %@ nameRetain %p",CFGetRetainCount((__bridge CFTypeRef)name),name,name,p.nameRetain);
输出结果
2017-04-13 10:22:26.157 原型模式[1564:54820] name retainCount:1 name:0x61800006d4c0 abc
2017-04-13 10:22:26.158 原型模式[1564:54820] After assign name retainCount:1 name:0x61800006d4c0 abc nameAssign 0x61800006d4c0
2017-04-13 10:22:26.158 原型模式[1564:54820] After copy name retainCount:1 name:0x61800006d4c0 abc nameCopy 0xa000000006362613
2017-04-13 10:22:26.159 原型模式[1564:54820] After retain name retainCount:2 name:0x61800006d4c0 abc nameRetain 0x61800006d4c0
首先,NSMutableString *name = [[NSMutableString alloc] initWithString:@"abc"];这个代码实际上是做了两个操作,(1)在栈上为name分配了一段内存,存放的是name这个指针指向的地址0x61800006d4c0(2)在堆上分配一段内存,地址为0x61800006d4c0,内容为abc
assign ,copy 和retain
assign:默认值,当使用了assign后,nameAssign和name指向同一个地址0x61800006d4c0,且retainCount的大小没有发生变化,那么nameAssign和name共同管理0x61800006d4c0地址的内容
copy:应用copy后,会在堆上重新分配一段内存0xa000000006362613用来存放nameCopy,他们的retainCount均为1,各自管理自己指向内存的内容
retain:应用retain后,retainCount加一,共同管理内存地址0x61800006d4c0的内容
想必这样介绍完,大家对于这三个属性应该是了解的比较清楚了。这里再顺便说一下atomic和nonatomic,这两个属性用来决定编译器生成的getter和setter是否为原子操作。
atomic:默认值,提供多线程安全。在多线程环境下,原子操作是必要的,否则有可能引起错误的结果。加了atomic,setter函数在操作前会加锁。
nonatomic:禁用多线程的变量保护,提高性能。
atomic是OC中使用的一种线程保护技术,用来防止在写操作未完成的时候被另外一个线程读取,造成数据错误。但是这种机制是耗费系统资源的,所以如果没有使用多线程的通讯编程,那么nonatomic是一个非常好的选择。
当代码中的name,变为不可变类型NSString,输出的结果:
2017-04-13 10:44:00.838 原型模式[1831:70444] name retainCount:1152921504606846975 name:0x10d807080 abc
2017-04-13 10:44:00.838 原型模式[1831:70444] After assign name retainCount:1152921504606846975 name:0x10d807080 abc nameAssign 0x10d807080
2017-04-13 10:44:00.839 原型模式[1831:70444] After copy name retainCount:1152921504606846975 name:0x10d807080 abc nameCopy 0x10d807080
2017-04-13 10:44:00.839 原型模式[1831:70444] After retain name retainCount:1152921504606846975 name:0x10d807080 abc nameRetain 0x10d807080
内存地址不发生变化,都是指向同一个
网友评论