设计模式之原型模式(深入理解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操作符去创 ...
随机推荐
- MySQL 索引优化 Using where, Using filesort
用Explain分析SQL语句的时候,经常发现有的语句在Extra列会出现Using filesort,根据MySQL官方文档对他的描述: 引用 MySQL must do an extra pass ...
- 自动化安装smokeping-2.6.11脚本
自动化安装Smokeping-2.6.11脚本 一.目的 1.1 监控目的 为方便监测各数据中心网络状况,自定义全国各节点,从而发现网络异常,判断网络故障. 1.2 本文目的 快速部署Smokepin ...
- ceph-mon 代码解读
一.概述: ceph-mon代码必备知识:Paxos算法,参见此处 数据结构分析: monitor代码结构:
- MVC 3.0学习笔记(自定义控件)
现有控件: 例如ASP.NET MVC框架包括以下设置标准的HTML控件(部分控件): Html.ActionLink() Html.BeginForm() Html.CheckBox() Html. ...
- C# 程序打包Release版本
注意:DEBUG和RELEASE的区别,DEBUG下可以直接运行,而RELEASE不一定能直接运行,这并不是表示RELEASE版本有问题,而是表示两者需要操作不同.RELEASE版本要比DEBUG版本 ...
- nginx无法启动异常
Nginx安装过程中可能会报如下错误: /usr/local/nginx/sbin/nginx -t /usr/local/nginx/sbin/nginx: error while loading ...
- 关于 os模块的常用用法
作为常用模块中的os模块,需要掌握的用法是非常重要的,今天就在这里把它归纳总结总结,以便自己日后的使用 一.os模块 含义:提供程序与操作系统直接操作的各个功能 二.常用的几个用法 os.getcwd ...
- zookeeper注册中心安装(单机版)
下载zookeeper-3.4.9.tar.gz wget http://apache.fayea.com/zookeeper/zookeeper-3.4.9/zookeeper-3.4.9.tar. ...
- Java NIO.2 —— 文件或目录拷贝操作
拷贝整个文件树是可以递归每个目录和文件调用 Files.copy()方法.在使用的时候有一下注意事项. 在往目录拷贝文件之前,首先要保证目录已经存在.拷贝源目录(不论是否为空)都会生成目标目录.整个任 ...
- 公网用户接入NAT后面的freeswitch配置
大致网络示意和终端号码: 客户端侧: 终端号码(1019)终端IP(192.168.1.15)+ 网关(192.168.1.1) + 路由器公网IP(动态地址) 服务器侧: 防火墙(181.92.2. ...