原型模式
原型模式是非常簡單的一種模式,在我們的實際開發中經常用到這種模式,例如你創建的可變字典、可變字符串調用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:第一次寫文章,想要改變一下自己,之前習慣做筆記,現在也要分享各位同學,這篇文章參考了好多的這位哥們的這篇博客,我寫的有什么不對的地方希望大家多多指正!