原型模式
原型模式是非常简单的一种模式,在我们的实际开发中经常用到这种模式,例如你创建的可变字典、可变字符串调用copy来生成新的对象,那么你在这个过程中已经使用了原型模式。还有比如你在写论文的时候,给老师交的初稿,退回来之后再修改你肯定不是在初稿上修改的,也不是再从新敲一遍,而是复制一份初稿再修改,这个拷贝初稿生成终稿的过程就叫原型模式。
举个例子:你创建了一个Student类,属性有性别sex、名字name、年级className、班主任teacher、年龄years。然后开始创建学生对象:小江、小帅、小虎等多个对象,在创建的过程中发现这些人是同班学生、除了性别和名字有差别之外其他的信息基本一样,如果初始化这些学生对象然后给每个属性赋值,显然这些代码都是重复性的代码。so通过让Student类遵守NSCopying的协议,就能通过copy快速的初始化对象,然后根据差异简单的改变sex和那么即可。(ps:例子只是为了说明这种模式,相信大家还有很多更好的办法,我现在在学习GoF的《Objective-C的编程之道,iOS设计模式解析》所以现在以简书的形式来做个笔记供大家相互学习和指正,之后会不断的将在这本书理解到的东西转到简书上)
原型模式的定义:使用原型实例指定创建对象的种类,并通过复制这个原型创建新的对象。《设计模式》(Addison-Weslet,1994)
何时使用原型模式:
1、需要创建的对象应独立于其类型与创建方式。
2、要实例化的类是在运行时决定的。
3、不想要与产品层次相对应的工厂层次。
4、不同类的实例间的差异仅是状态的若干组合。因此复制相应数量的原型比手工实例化更加方便。
5、类不容易创建,比如每个组件可把其他组件作为子节点的组合对象。复制已有的组合对象并对副本进行修改会更加容易。
在GoF的书中有句话很好:从功能的角度来看,不管什么对象,只要复制本身比手工实例化要好,那么都可以是原型对象。使用设计模式更像艺术行为而非科学行为。
深拷贝&浅拷贝
浅拷贝:只是复制指针,且复制的指针的指向还是原来的内存资源,并没有在内存中开辟新的资源,和原来的指针指向一块内存资源。
深拷贝:不但拷贝了指针,而且也将原指针指向的资源进行了拷贝,相当于开辟了新的内存,把原来的资源也拷贝了一份并且各自的指针指向各自的资源。
Coco Touch 框架中根类(NSOblect)的衍生类提供了实现深复制的协议(NSCopying)。NSObject有一个实例方法(id)copy,这个方法默认调用了[self copyWithZone:nil],对于引用了NSCopying协议的子类,必须实现(id)copyWithZone:(NSZone *)zone方法,否则将引发异常。(输出如下)
assign©&retain
直接看例子
@interface IDStudent : NSObject
@property (assign, nonatomic, getter=isMale)BOOL male;
@property (copy, nonatomic)NSString *name;
@property (copy, nonatomic)NSString *className;
@property (copy, nonatomic)NSString *teacher;
@property (assign, nonatomic)NSUInteger years;
@property (assign, nonatomic)NSMutableString *name1;
@property (retain, nonatomic)NSMutableString *name2;
@property (copy, nonatomic)NSMutableString *name3;
+ (instancetype)studentWithName:(NSString *)name male:(BOOL)male className:(NSString *)className teacher:(NSString *)teacher yeas:(NSUInteger)years;
@end
NSMutableString *str =[NSMutableString stringWithFormat:@"小帅"];
IDStudent *student =[IDStudent new];
student.name1 =str;
student.name2 =str;
student.name3 =str;
NSLog(@"\n str的地址:%p\n name1的地址:%p(assign) \n name2的地址:%p(retain)\n name3的地址:%p(copy)\n",str,student.name1,student.name2,student.name3);
输出结果
str的地址:0x60c000240e10
name1的地址:0x60c000240e10(assign)
name2的地址:0x60c000240e10(retain)
name3的地址:0x60c000036760(copy)
结论:
解析一下代码:NSMutableString *str =[NSMutableString stringWithFormat:@"小帅"];
1、在栈区开辟内存来存str,比如地址为:0xFFFF,内容为0x60c000240e10
2、在堆区开辟内存来存str的内容@"小帅",地址为0x60c000240e10,内容为@"小帅"
assing:如果在MRC的情况下打印retainCount,它的值不会加1,(自己可以试试),且在堆区的地址还是和str的地址一样,说明assign只是一个str的影子
retain:这个接触到MRC的情况下,它的值会加1,虽然它的地址还是和str的一样,但是它会在栈区开辟新的空间比如0xWWWW,但是内容还是0x60c000240e10,相当于经过retain的name2在栈区开辟了新的空间,都是强指向在堆区地址为0x60c000240e10的内容,共同管理。
copy:使用copy后堆区的地址改变,说明这个过程在栈区开辟了新的空间地址为0xBBBB,储存的内容为0x60c000036760,同样也在堆区开辟了新的空间,地址为:0x60c000036760内容为@“小帅”。跟原来的str没有了关系,说明进行了深拷贝。
下边对NSString在进行一下实验,看是否和NSMutableString一样的储存方式
NSString *name =[NSString stringWithFormat:@"name"];
NSString *nameCopy =[name copy];
NSMutableString *mName =[name mutableCopy];
NSLog(@"\nname:%p\nnameCopy:%p\nmName:%p\n",name,nameCopy,mName);
NSMutableString *str =[[NSMutableString alloc]initWithString:@"test"];
NSMutableString *copyStr =[str copy];
NSLog(@"\n------------------------------------\nstr:%p\ncopyStr:%p",str,copyStr);
输出结果:
2017-12-22 16:34:02.566672+0800 desgin_原型模式[25958:11425087]
name:0xa000000656d616e4
nameCopy:0xa000000656d616e4
mName:0x60c00024c360
2017-12-22 16:34:02.566808+0800 desgin_原型模式[25958:11425087]
------------------------------------------------------------------------
str:0x60c00024ca50
copyStr:0xa000000747365744
结论:根据输出结果NSString的不可变字符串在copy的情况地址一致,由于copy返回的是不可变副本,系统只生成一份内存资源,此时的copy只是浅拷贝,和retain作用一样。
通过mutableCopy地址不一致,生成的是可变副本,开辟了新的内存空间,是深拷贝。而NSMutableString的copy会开辟新的空间。
ps:第一次写文章,想要改变一下自己,之前习惯做笔记,现在也要分享各位同学,这篇文章参考了好多的这位哥们的这篇博客,我写的有什么不对的地方希望大家多多指正!