美文网首页
原型模式(Prototype)

原型模式(Prototype)

作者: 傻傻小萝卜 | 来源:发表于2017-04-13 10:47 被阅读59次

    基本概念

    原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象

    从上图可以看到,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

    内存地址不发生变化,都是指向同一个

    像NSString、NSDictionary这些类,本身已经实现了copyWithZone:(NSZone *)zone方法,直接使用如[NSString copy]调用即可。在复制后得到的副本,又可以分为可变副本(mutable copy)和不可变副本(immutable copy)。通常在NSCopying协议规定的方法copyWithZone中返回不可变副本,在NSMutableCopying协议规定的方法mutableCopyWithZone中返回可变副本,然后调用copy和mutableCopy方法来得到相应的不可变和可变副本。

    参考链接:IOS设计模式浅析之原型模式(Prototype)

    相关文章

      网友评论

          本文标题:原型模式(Prototype)

          本文链接:https://www.haomeiwen.com/subject/czbmattx.html