基本概念
原型模式:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件
從上圖可以看到,Prototype類中包括一個clone方法,Client呼叫其拷貝方法clone即可得到例項,不需要手工去建立例項。ConcretePrototype1和ConcretePrototype2為Prototype的子類,實現自身的clone方法,如果Client呼叫ConcretePrototype1的clone方法,將返回ConcretePrototype1的例項。簡單來理解就是根據這個原型建立新的物件,而且不需要知道任何建立的細節。打個比方,以前生物課上面,有一個知識點叫細胞分裂,細胞在一定條件下,由一個分裂成2個,再由2個分裂成4個……,分裂出來的細胞基於原始的細胞(原型),這個原始的細胞決定了分裂出來的細胞的組成結構。這種分裂過程,可以理解為原型模式。
淺複製和深複製
淺複製:只複製了指標值,並沒有複製指標指向的資源(即沒有建立指標指向資源的副本),複製後原有指標和新指標共享同一塊記憶體。
深複製:不僅複製了指標值,還複製了指標指向的資源。
下面的示意圖左邊為淺複製,右邊為深複製。
Cocoa Touch框架為NSObject的派生類提供了實現深複製的協議,即NSCopying協議,提供深複製的NSObject子類,需要實現NSCopying協議的方法(id)copyWithZone:(NSZone *)zone。NSObject有一個例項方法(id)copy,這個方法預設呼叫了[self copyWithZone:nil],對於引用了NSCopying協議的子類,必須實現(id)copyWithZone:(NSZone *)zone方法,否則將引發異常,異常資訊如下:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Prototype copyWithZone:]: unrecognized selector sent to instance 0x100114d50'
http://www.cnblogs.com/eagle927183/p/3462439.html
程式碼實現
定義了一個Persion類,並實現NSCopying的方法- (id)copyWithZone:(NSZone *)zone
@interface Persion : NSObject
@property (nonatomic, strong)NSString *name;
@end
#import "Persion.h"
@interface Persion()<NSCopying>
@end
@implementation Persion
- (instancetype)init
{
self = [super init];
if (self) {
self.name = @"小蘿蔔";
}
return self;
}
// 實現NSCopying中的方法
- (id)copyWithZone:(NSZone *)zone
{
return [[[self class] allocWithZone:zone] init];
}
@end
實現複製的程式碼
Persion *p0 = [[Persion alloc] init];
Persion *p1 = [p0 copy];// 這個操作,建立了一個新的指標,指標指向了一個新的地址,地址的內容同p0的內容一樣,但是他們的記憶體地址是不一樣的,深複製是將原記憶體的內容複製到一個新的記憶體當中
Persion *p2 = p0;// 這個操作,建立了一個新的指標,指標還是指向p0所指向的內容,指向的記憶體地址沒有發生變化
NSLog(@"未修改前:%p,name %@",p0,p0.name);
NSLog(@"深拷貝(指標指向的記憶體地址發生變化):%p,name %@",p1,p1.name);
NSLog(@"淺拷貝(指標指向的記憶體地址沒有發生變化):%p,name %@",p2,p2.name);
2017-04-13 09:47:18.926 原型模式[1043:27289] 未修改前:0x60000000d580,name 小蘿蔔
2017-04-13 09:47:18.926 原型模式[1043:27289] 深拷貝(指標指向的記憶體地址發生變化):0x60000000d5b0,name 小蘿蔔
2017-04-13 09:47:18.927 原型模式[1043:27289] 淺拷貝(指標指向的記憶體地址沒有發生變化):0x60000000d580,name 小蘿蔔
當對Persion中的name的值進行改版,觀察他們name值得變化更能說明他們的的記憶體地址是否是同一個,如果不是同一個的話,那麼他們之間是不會相互影響的,如果是同一個的話,一個進行改變,那另一個也會隨之發生變化,程式碼如下
NSLog(@"未修改前p0:%p,name %@",p0,p0.name);
p1.name = @"傻傻小蘿蔔";
NSLog(@"原來p0:%p,name %@",p0,p0.name);
NSLog(@"深拷貝(指標指向的記憶體地址發生變化)p1:%p,name %@",p1,p1.name);
p2.name = @"快樂的小蘿蔔";
NSLog(@"原來p0:%p,name %@",p0,p0.name);
NSLog(@"淺拷貝(指標指向的記憶體地址沒有發生變化)p2:%p,name %@",p2,p2.name);
2017-04-13 09:58:57.880 原型模式[1215:37135] 未修改前p0:0x60800000cbd0,name 小蘿蔔
2017-04-13 09:58:57.880 原型模式[1215:37135] 原來p0:0x60800000cbd0,name 小蘿蔔
2017-04-13 09:58:57.881 原型模式[1215:37135] 深拷貝(指標指向的記憶體地址發生變化)p1:0x60800000cbe0,name 傻傻小蘿蔔
2017-04-13 09:58:57.881 原型模式[1215:37135] 原來p0:0x60800000cbd0,name 快樂的小蘿蔔
2017-04-13 09:58:57.881 原型模式[1215:37135] 淺拷貝(指標指向的記憶體地址沒有發生變化)p2:0x60800000cbd0,name 快樂的小蘿蔔
通過程式碼我們可知:
(1)實現- (id)copyWithZone:(NSZone *)zone這個方法實現了深複製,p1 和p0 他們的記憶體地址不同,當修改p1中name的值時,p0的值不發生變化,更加證實了他們的記憶體地址通,這個可以判斷為深複製
(2)而p0和p2 他們指向的記憶體地址是相同的,當修改p2的name的值時,p0所指向的記憶體地址中的值也發生了相應的變化,說明只是指向記憶體地址的簡單複製,所以為淺複製
assign ,copy 和retain
通過一段程式碼,在Persion類中定義三個name屬性,分別為assign,copy,和retain三種
@interface Persion : NSObject
@property (nonatomic, assign)NSString *nameAssign;
@property (nonatomic, copy)NSString *nameCopy;
@property (nonatomic, strong)NSString *nameRetain;
@end
實現程式碼(特別注意:在ARC下是不能夠使用retainCount這個方法的,只能使用CFGetRetainCount((__bridge CFTypeRef)object) 來實現)
Persion *p = [[Persion alloc] init];
NSMutableString *name = [[NSMutableString alloc] initWithString:@"abc"];
NSLog(@"name retainCount:%ld name:%p %@",CFGetRetainCount((__bridge CFTypeRef)name),name,name);
p.nameAssign = name;
NSLog(@"After assign name retainCount:%ld name:%p %@ nameAssign %p",CFGetRetainCount((__bridge CFTypeRef)name),name,name,p.nameAssign);
p.nameCopy = name;
NSLog(@"After copy name retainCount:%ld name:%p %@ nameCopy %p",CFGetRetainCount((__bridge CFTypeRef)name),name,name,p.nameCopy);
p.nameRetain = name;
NSLog(@"After retain name retainCount:%ld name:%p %@ nameRetain %p",CFGetRetainCount((__bridge CFTypeRef)name),name,name,p.nameRetain);
輸出結果
2017-04-13 10:22:26.157 原型模式[1564:54820] name retainCount:1 name:0x61800006d4c0 abc
2017-04-13 10:22:26.158 原型模式[1564:54820] After assign name retainCount:1 name:0x61800006d4c0 abc nameAssign 0x61800006d4c0
2017-04-13 10:22:26.158 原型模式[1564:54820] After copy name retainCount:1 name:0x61800006d4c0 abc nameCopy 0xa000000006362613
2017-04-13 10:22:26.159 原型模式[1564:54820] After retain name retainCount:2 name:0x61800006d4c0 abc nameRetain 0x61800006d4c0
首先,NSMutableString *name = [[NSMutableString alloc] initWithString:@"abc"];這個程式碼實際上是做了兩個操作,(1)在棧上為name分配了一段記憶體,存放的是name這個指標指向的地址0x61800006d4c0(2)在堆上分配一段記憶體,地址為0x61800006d4c0,內容為abc
assign ,copy 和retain
assign:預設值,當使用了assign後,nameAssign和name指向同一個地址0x61800006d4c0,且retainCount的大小沒有發生變化,那麼nameAssign和name共同管理0x61800006d4c0地址的內容
copy:應用copy後,會在堆上重新分配一段記憶體0xa000000006362613用來存放nameCopy,他們的retainCount均為1,各自管理自己指向記憶體的內容
retain:應用retain後,retainCount加一,共同管理記憶體地址0x61800006d4c0的內容
想必這樣介紹完,大家對於這三個屬性應該是瞭解的比較清楚了。這裡再順便說一下atomic和nonatomic,這兩個屬性用來決定編譯器生成的getter和setter是否為原子操作。
atomic:預設值,提供多執行緒安全。在多執行緒環境下,原子操作是必要的,否則有可能引起錯誤的結果。加了atomic,setter函式在操作前會加鎖。
nonatomic:禁用多執行緒的變數保護,提高效能。
atomic是OC中使用的一種執行緒保護技術,用來防止在寫操作未完成的時候被另外一個執行緒讀取,造成資料錯誤。但是這種機制是耗費系統資源的,所以如果沒有使用多執行緒的通訊程式設計,那麼nonatomic是一個非常好的選擇。
當程式碼中的name,變為不可變型別NSString,輸出的結果:
2017-04-13 10:44:00.838 原型模式[1831:70444] name retainCount:1152921504606846975 name:0x10d807080 abc
2017-04-13 10:44:00.838 原型模式[1831:70444] After assign name retainCount:1152921504606846975 name:0x10d807080 abc nameAssign 0x10d807080
2017-04-13 10:44:00.839 原型模式[1831:70444] After copy name retainCount:1152921504606846975 name:0x10d807080 abc nameCopy 0x10d807080
2017-04-13 10:44:00.839 原型模式[1831:70444] After retain name retainCount:1152921504606846975 name:0x10d807080 abc nameRetain 0x10d807080
記憶體地址不發生變化,都是指向同一個