- 原型模式:用原型實例指定創建對象的種類,並且通過拷貝這些原型創建新的對象。原型模式其實就是從一個對象再創建另一個可定制的對象,而且不需知道任何創建的細節。
比如說,有一個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。