原型模式(Prototype)

基本概念

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


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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容