1. 原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式其实就是从一个对象再创建另一个可定制的对象,而且不需知道任何创建的细节。

比如说,有一个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协议以及浅拷贝、深拷贝)的更多相关文章

  1. c# 24种设计模式5原型模式(Prototype)

    前言 原型模式其实C# Object中已经提供了一个Clone( )方法,平时很少用到,最近读Retrofit源码时候看到有这种使用方式. 定义 原型模式就是在系统clone()标记的基础上,对Clo ...

  2. swift设计模式学习 - 原型模式

    移动端访问不佳,请访问我的个人博客 设计模式学习的demo地址,欢迎大家学习交流 原型模式 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 定义 用原型实例指定创建对象的种类,并且通 ...

  3. 设计模式_11_原型模式(prototype)深拷贝、浅拷贝

    设计模式_11_原型模式(prototype) 浅拷贝: package designPatternOf23; /** * 定义:用原型实例,指定创建对象的种类,并通过拷贝这些原型创建新的对象 * P ...

  4. C#设计模式(6)——原型模式(Prototype Pattern)

    一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在 ...

  5. iOS设计模式之原型模式

    原型模式 基本理解 原型模式(Prototype),用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 原型模式其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节 ...

  6. C#设计模式之六原型模式(Prototype)【创建型】

    一.引言 在开始今天的文章之前先说明一点,欢迎大家来指正.很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象,这些对象其实都是原型的复制,不会使用内存.我认为这是不对的,因为拷贝出来的每一个对 ...

  7. C#设计模式之五原型模式(Prototype Pattern)【创建型】

    一.引言 在开始今天的文章之前先说明一点,欢迎大家来指正.很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象,这些对象其实都是原型的复制,不会使用内存.我认为这是不对的,因为拷贝出来的每一个对 ...

  8. C#设计模式(6)——原型模式(Prototype Pattern)(转)

    一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在 ...

  9. C#设计模式(6)——原型模式(Prototype Pattern) C# 深浅复制 MemberwiseClone

    C#设计模式(6)——原型模式(Prototype Pattern)   一.引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创 ...

随机推荐

  1. Git的图形化工具使用教程

    虽然感觉并没有什么暖用,但姑且还是写出来留作纪念好了 Git这种分布式版本控制系统最适合的就是单枪匹马搞开发的选手,不需要服务器,下载个git和图形工具,网速快十分钟就能搞定开始愉快的开发工作.我在搭 ...

  2. java 发送post请求参数中含有+会转化为空格的问题

    如题 原因分析:参数在传递过程中经历的几次编码和解码标准不同,导致加号.空格等字符的错误. 解决方案:将post请求的参数中 ,含有+号的,统统采用%2B 去替换,这是URL的协议问题.

  3. Kubernetes(k8s) docker集群搭建

    原文地址:https://blog.csdn.net/real_myth/article/details/78719244 一.Kubernetes系列之介绍篇   •Kubernetes介绍 1.背 ...

  4. python学习笔记——git的安装及使用

    1 git的基本介绍 git 是目前世界上最先进的分布式版本哦内阁制系统 详细信息可参考廖雪峰的官方网站中的Git教程 比git功能更加强大的有TortoiseGit和Tortoise SVN,具体安 ...

  5. mysql慢查询日志相关参数

    -- mysql慢查询日志相关参数 -- 慢查询日志时间 show variables like "long_query_time"; -- 将时间设置为2s ; -- 是否开启慢 ...

  6. Eclipse SQLExplorer插件的安装和使用

    from: http://blog.csdn.net/flashlm/archive/2007/06/30/1672836.aspx 插件名称: SQLExplorer 插件分类: SQL Edito ...

  7. 【转】Kotlin 和 Checked Exception

    Kotlin 和 Checked Exception 最近 JetBrains 的 Kotlin 语言忽然成了热门话题.国内小编们传言说,Kotlin 取代了 Java,成为了 Android 的“钦 ...

  8. java环境变量设置与java查看安装路径

    把jdk安装到计算机中之后,我们来进行设置使java环境能够使用. 首先右键点我的电脑.打开属性.然后选择“高级”里面的“环境变量”,在新的打开界面中的系统变量需要设置三个属性“JAVA_HOME”. ...

  9. mysql 5.5数据库主从配置步骤详解

    上次给大家介绍了mysql 5.1主从搭建配置教程,这次我们来实现mysql 5.5的主从复制,其实大体上配置是差不多的,只有点细微的差别. 系统:centos 5.x 需要的软件包:mysql-5. ...

  10. Java – Check if Array contains a certain value?

    Java – Check if Array contains a certain value?1. String Arrays1.1 Check if a String Array contains ...