设计模式之原型模式(深入理解OC中的NSCopying协议以及浅拷贝、深拷贝)
- 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式其实就是从一个对象再创建另一个可定制的对象,而且不需知道任何创建的细节。
比如说,有一个Person类,有firstName、lastName、friends这三个属性,代码如下:
#import <Foundation/Foundation.h> @interface ZYPerson : NSObject
{
NSMutableSet *_friends;
}
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName; - (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
- (void)addFriend:(ZYPerson *)person;
- (void)removeFriend:(ZYPerson *)person;
@end #import "ZYPerson.h" @implementation ZYPerson
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName
{
if (self = [super init]) {
_firstName = firstName;
_lastName = lastName;
_friends = [[NSMutableSet alloc] init];
}
return self;
} - (void)addFriend:(ZYPerson *)person
{
[_friends addObject:person];
} - (void)removeFriend:(ZYPerson *)person
{
[_friends removeObject:person];
}
@end
viewController里面的代码:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. ZYPerson *personOne = [[ZYPerson alloc] initWithFirstName:@"张" lastName:@"三"];
ZYPerson *personTwo = [[ZYPerson alloc] initWithFirstName:@"李" lastName:@"四"];
[personOne addFriend:personTwo]; }
现在有这样的一个需求,有一个人,也叫张三,也只有李四一个好友,如果不用原型模式,就会使下面的代码:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. ZYPerson *personOne = [[ZYPerson alloc] initWithFirstName:@"张" lastName:@"三"];
ZYPerson *personTwo = [[ZYPerson alloc] initWithFirstName:@"李" lastName:@"四"];
[personOne addFriend:personTwo]; ZYPerson *personThree = [[ZYPerson alloc] initWithFirstName:personOne.firstName lastName:personOne.lastName];
[personThree addFriend:personTwo]; }
这样,Person类只有两三个属性还好说,只是简单的写下,如果Person类有十几个属性,有上百个朋友,这代码量是很大的,而且这种代码也是没有必要的,谁上谁都会写,至少我是不愿意写这种垃圾代码的。
如此,原型模式就可以比较好的解决这样一个问题,在iOS开发中,原型模式依赖于NSCopying协议,需要实现-copyWithZone方法,Person类代码如下:
#import <Foundation/Foundation.h> @interface ZYPerson : NSObject <NSCopying>
{
NSMutableSet *_friends;
}
@property (nonatomic, copy, readonly) NSString *firstName;
@property (nonatomic, copy, readonly) NSString *lastName; - (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName;
- (void)addFriend:(ZYPerson *)person;
- (void)removeFriend:(ZYPerson *)person;
@end #import "ZYPerson.h" @implementation ZYPerson
- (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName
{
if (self = [super init]) {
_firstName = firstName;
_lastName = lastName;
_friends = [[NSMutableSet alloc] init];
}
return self;
} - (void)addFriend:(ZYPerson *)person
{
[_friends addObject:person];
} - (void)removeFriend:(ZYPerson *)person
{
[_friends removeObject:person];
}
- (id)copyWithZone:(NSZone *)zone
{
ZYPerson *copy = [[[self class] allocWithZone:zone] initWithFirstName:_firstName lastName:_lastName];
copy->_friends = [_friends mutableCopy];
return copy;
}
@end
viewController代码:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib. ZYPerson *personOne = [[ZYPerson alloc] initWithFirstName:@"张" lastName:@"三"];
ZYPerson *personTwo = [[ZYPerson alloc] initWithFirstName:@"李" lastName:@"四"];
[personOne addFriend:personTwo]; ZYPerson *personThree = [personOne copy]; NSLog(@"%@ %@",personThree.firstName, personThree.lastName);
}
可以看到,在初始化条件不发生改变的情况下,copy是最好的办法,既隐藏了对象创建的细节,对性能也是有着显著提高的,最主要的一点,就是没必要去重复写垃圾代码,浪费大量时间。
2. 浅拷贝(copy)与深拷贝(mutableCopy)
copy与mutableCopy的区别,如果学过c或者c++的朋友会知道指针这样一个概念,就是在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存
而深拷贝就是不仅增加了一个指针,并且还申请了一个新的内存,是这个新增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误。
上面的这句代码:
copy->_friends = [_friends mutableCopy];
使用了->语法,因为_friend并非属性,而是在内部使用的实例变量。这里有一个问题,为什么要拷贝_friend实例变量呢?不拷贝这个变量,直接让两个对象共享同一个可变的set是否更简单?
如果这么做了,那么再给personOne添加一个新的朋友之后,拷贝过来的那个对象,也就是personThree也会“神奇”的与之为朋友了。在我上面写的那个实例中,这不是我想要的效果。然而,如果那个set是不可变的,那么就无需复制,因为其中的内容不可能会改变,所以就不用担心会出现上面的问题。如果复制了,那么内存中会有两个一模一样的set,造成了浪费。
引申出这样一个问题(面试题):怎样使用copy关键字?
以前我是这么回答的:
一般使用retain或者strong修饰属性时,是使引用对象的指针指向同一个对象,即为同一块内存地址。只要其中有一个指针变量被修改时所有其他引用该对象的变量都会被改变。
而使用copy关键字修饰在赋值时是释放旧对象,拷贝新对象内容。重新分配了内存地址。以后该指针变量被修改时就不会影响旧对象的内容了。
copy只有实现NSCopying协议的对象类型才有效。
常用于NSString和Block。
有一定错误,应该这样修正:
一般使用retain或者strong修饰属性时,是使引用对象的指针指向同一个对象,即为同一块内存地址。只要其中有一个指针变量被修改时所有其他引用该对象的变量都会被改变。
而copy关键字修饰时,如果新的对象是不可变的,那么它是直接引用新对象的内存地址,并不重新分配内存地址,如果新对象是可变的,那么在赋值时是释放旧对象,拷贝新对象内容。重新分配了内存地址。以后该指针变量被修改时就不会影响旧对象的内容了。
copy只有实现NSCopying协议的对象类型才有效。
常用于NSString和Block。
设计模式之原型模式(深入理解OC中的NSCopying协议以及浅拷贝、深拷贝)的更多相关文章
- c# 24种设计模式5原型模式(Prototype)
前言 原型模式其实C# Object中已经提供了一个Clone( )方法,平时很少用到,最近读Retrofit源码时候看到有这种使用方式. 定义 原型模式就是在系统clone()标记的基础上,对Clo ...
- swift设计模式学习 - 原型模式
移动端访问不佳,请访问我的个人博客 设计模式学习的demo地址,欢迎大家学习交流 原型模式 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 定义 用原型实例指定创建对象的种类,并且通 ...
- 设计模式_11_原型模式(prototype)深拷贝、浅拷贝
设计模式_11_原型模式(prototype) 浅拷贝: package designPatternOf23; /** * 定义:用原型实例,指定创建对象的种类,并通过拷贝这些原型创建新的对象 * P ...
- C#设计模式(6)——原型模式(Prototype Pattern)
一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在 ...
- iOS设计模式之原型模式
原型模式 基本理解 原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节 ...
- C#设计模式之六原型模式(Prototype)【创建型】
一.引言 在开始今天的文章之前先说明一点,欢迎大家来指正.很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象,这些对象其实都是原型的复制,不会使用内存.我认为这是不对的,因为拷贝出来的每一个对 ...
- C#设计模式之五原型模式(Prototype Pattern)【创建型】
一.引言 在开始今天的文章之前先说明一点,欢迎大家来指正.很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象,这些对象其实都是原型的复制,不会使用内存.我认为这是不对的,因为拷贝出来的每一个对 ...
- C#设计模式(6)——原型模式(Prototype Pattern)(转)
一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在 ...
- C#设计模式(6)——原型模式(Prototype Pattern) C# 深浅复制 MemberwiseClone
C#设计模式(6)——原型模式(Prototype Pattern) 一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创 ...
随机推荐
- SaltStack入门到精通第一篇:安装SaltStack
SaltStack入门到精通第一篇:安装SaltStack 作者:纳米龙 发布日期:2014-06-09 17:50:36 实际环境的设定: 系统环境: centos6 或centos5 实验机 ...
- cmd.exe_参数_启动参数 cmd加启动运行参数 命令
cmd.exe_参数_启动参数 /k指定运行后面的String命令,多个命令用&或&&连接,这样||不行&&&都能行,示例: cmd /k cd D:\ ...
- O'Reilly总裁提姆-奥莱理:什么是Web 2.0
O'Reilly总裁提姆-奥莱理:什么是Web 2.0 译者序:Web 2.0这一概念,由O'Reilly媒体公司总裁兼CEO提姆·奥莱理提出.他是美国IT业界公认的传奇式人物,是“开放源码”概念的缔 ...
- 进阶之路(基础篇) - 020 放弃Arduino IDE,拥抱Sublime Text 3
本帖转载:Arduino讨论区相信大家对Arduino IDE的不能输入中文,排版不方便,没有行号,界面难看......深恶痛绝.我也是.经过vs2012,eclipse等IDE的试用,配置麻烦,ID ...
- 终极 Shell
在开始今天的 MacTalk 之前,先问两个问题吧: 1.相对于其他系统,Mac 的主要优势是什么?2.你们平时用哪种 Shell?…… 第一个童靴可以坐下了,Mac 的最大优势是 GUI 和命令行的 ...
- Redis 集群_主从配置_哨兵模式
首先:slaveof 可以在[从]服务器启动一个service服务,直接将[从]服务器定义为[从Redis] redis-server --slaveof <master-ip> < ...
- 实例应用 自定义页面taglib标签
关于继承TagSupport与BodyTagSupport的区别说明 * <code>TagSupport</code>与<code>BodyTagSupport& ...
- ADF_Starting系列3_使用ADF开发富Web应用程序之开发User Interface
内容中包含 base64string 图片造成字符过多,拒绝显示
- linux文件系统 - 初始化(二)
加载initrd(上) 一.目的 本文主要讲述linux3.10文件系统初始化过程的第二阶段:加载initrd. initrd是一个临时文件系统,由bootload负责加载到内存中,里面包含了基本的可 ...
- htmlentities、addslashes 、htmlspecialchars的使用
1.html_entity_decode():把html实体转换为字符. Eg:$str = "just atest & 'learn to use '";echo htm ...