原型模式(Prototype)

Diy_os發表於2017-06-08

基本概念

原型模式:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件


原型模式(Prototype)

從上圖可以看到,Prototype類中包括一個clone方法,Client呼叫其拷貝方法clone即可得到例項,不需要手工去建立例項。ConcretePrototype1和ConcretePrototype2為Prototype的子類,實現自身的clone方法,如果Client呼叫ConcretePrototype1的clone方法,將返回ConcretePrototype1的例項。簡單來理解就是根據這個原型建立新的物件,而且不需要知道任何建立的細節。打個比方,以前生物課上面,有一個知識點叫細胞分裂,細胞在一定條件下,由一個分裂成2個,再由2個分裂成4個……,分裂出來的細胞基於原始的細胞(原型),這個原始的細胞決定了分裂出來的細胞的組成結構。這種分裂過程,可以理解為原型模式。

淺複製和深複製

淺複製:只複製了指標值,並沒有複製指標指向的資源(即沒有建立指標指向資源的副本),複製後原有指標和新指標共享同一塊記憶體。

深複製:不僅複製了指標值,還複製了指標指向的資源。

下面的示意圖左邊為淺複製,右邊為深複製。


原型模式(Prototype)


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

記憶體地址不發生變化,都是指向同一個

像NSString、NSDictionary這些類,本身已經實現了copyWithZone:(NSZone *)zone方法,直接使用如[NSString copy]呼叫即可。在複製後得到的副本,又可以分為可變副本(mutable copy)和不可變副本(immutable copy)。通常在NSCopying協議規定的方法copyWithZone中返回不可變副本,在NSMutableCopying協議規定的方法mutableCopyWithZone中返回可變副本,然後呼叫copy和mutableCopy方法來得到相應的不可變和可變副本。

參考連結:IOS設計模式淺析之原型模式(Prototype)

相關文章