原型模式解決的問題
對象的創(chuàng)建特別復雜,當兩個對象在抽象邏輯完全一致,只在實例化的細節(jié)略有差異,如果要重新創(chuàng)建一個對象,不如拷貝自己再去修改。
原型模式,在多數(shù)情況下可被理解為一種深拷貝的行為。在 OC 中使用原型模式,首先要遵循 NSCoping 協(xié)議。
原型模式在實際開發(fā)中的應用場景:
1.對象不能直接通過初始化函數(shù)創(chuàng)建出來,且創(chuàng)建過程不具有普遍性還比較復雜。
2.不同的實例間的差距很小(僅僅幾個屬性值不同),因此復制相應數(shù)量的原型比重新實例化創(chuàng)建更方便,代碼更少。
3.類的依賴過多,創(chuàng)建條件比較嚴格的時候也可以考慮使用。
NSCopying
NScopying是一個與對象拷貝有關的協(xié)議。如果想讓一個類的對象支持拷貝,就需要讓該類實現(xiàn)NSCopying協(xié)議。NSCopying協(xié)議中的聲明的方法只有一個- (id)copyWithZone:(NSZone *)zone。如果想讓一個對象支持拷貝,就要實現(xiàn)這個方法。(NSMutableCopying 與 NSCopying類似)。
#import <Foundation/Foundation.h>
@interface Student : NSObject<NSCopying>
@property (nonatomic, copy)NSString* name;
@property (nonatomic, copy)NSString* sex;
@property (nonatomic, copy)NSString *height;
- (id)initWithDictionary:(NSDictionary *)dictionary;
@end
#import "Student.h"
@implementation Student
- (id)initWithDictionary:(NSDictionary *)dictionary
{
self = [super init];
if (self) {
self.name = dictionary[@"name"];
self.sex = dictionary[@"sex"];
self.height = dictionary[@"height"];
}
return self;
}
- (id)copyWithZone:(nullable NSZone *)zone
{
Student *student = [[Student alloc] init];
student.name = self.name;
student.sex = self.sex;
student.height = self.height;
return student;
}
@end
下面我們針對Student這個類做一些測試。
@property (nonatomic, copy) Student *copStudent;
NSDictionary *dictionary = @{@"name": @"楊千嬅染了紅頭發(fā)", @"sex": @"男", @"height": @"182cm"};
Student *student1 = [[Student alloc] initWithDictionary:dictionary];
NSLog(@"student1 name: %@, sex: %@, height: %@", student1.name, student1.sex, student1.height);
NSLog(@"student1的地址: %p", student1);
Student *student2 = student1;
NSLog(@"student2 name: %@, sex: %@, height: %@", student2.name, student2.sex, student2.height);
NSLog(@"student2的地址: %p", student2);
Student *student3 = student1.copy;
NSLog(@"student3 name: %@, sex: %@, height: %@", student3.name, student3.sex, student3.height);
NSLog(@"student3的地址: %p", student3);
self.copStudent = student1;
NSLog(@"self.copStudent name: %@, sex: %@, height: %@", self.copStudent.name, self.copStudent.sex, self.copStudent.height);
NSLog(@"self.copStudent的地址: %p", self.copStudent);
打印結果
student1 name: 楊千嬅染了紅頭發(fā), sex: 男, height: 182cm
student1的地址: 0x608000030000
student2 name: 楊千嬅染了紅頭發(fā), sex: 男, height: 182cm
student2的地址: 0x608000030000
student3 name: 楊千嬅染了紅頭發(fā), sex: 男, height: 182cm
student3的地址: 0x60800002ff80
self.copStudent name: 楊千嬅染了紅頭發(fā), sex: 男, height: 182cm
self.copStudent的地址: 0x608000030240
打印結果顯示:
student2(student2 = student1)的地址與student1地址是相同的。[0x608000030000]
student3(student3 = student1.copy)[0x60800002ff80]和self.copStudent(self.copStudent = student1)[0x608000030240]的地址是與student1[0x608000030000]的地址不同的。
探究下原因:
Student *student2 = student1;
這個=代表的一個賦值的操作,在Objective-C有兩類數(shù)據(jù)類型,一種是基本數(shù)據(jù)類型,另一種是NSObject對象。
對于基本數(shù)據(jù)類型,賦值操作會創(chuàng)建一個源對象的副本,一個新的對象。
對于NSObject對象,賦值操作相當于復制了指針而非對象,賦值操作使得源指針和新指針都指向了同一個NSObject對象。
這就是student1與student2地址一樣的原因,更嚴格的說不是student1與student2的地址一樣,是student1與student2都指向了同一個地址。在Object-C中,我們創(chuàng)建對象通常用這樣的方式NSObject *objc = [[NSObject alloc] init];習慣性將objc稱為NSObject的實例化對象,但是實際上objc是一個指針變量,它存放著[[NSObject alloc] init]這個對象的地址,或者說objc指向這個對象,當我們要訪問所創(chuàng)建的對象時,先要讀取指針變量objc存儲的目標對象的值,再通通過該地址訪問目標對象的內(nèi)存單元,這是一個間接訪問。
NSLog(@"student1: %x", (int)&student1);
NSLog(@"student2: %x", (int)&student2);
打印結果:
student1: 591c97e0
student2: 591c97d8
這說明student1、student2兩個指針自身的地址是不一樣的,但是它們都指向了[[Student alloc] initWithDictionary:dictionary];存放的地址。
Student *student3 = student1.copy;
self.copStudent = student1;
student3與self.copStudent的地址雖然都和student1的地址不一樣,但是兩者還是有區(qū)別的。
Student *student3 = student1.copy;這行代碼的執(zhí)行順序是這樣的:
1.student1.copy先執(zhí)行Student.h文件里面我們已經(jīng)實現(xiàn)的NSCopying協(xié)議(- (id)copyWithZone:(nullable NSZone *)zone),生成一個新的Student實例對象。
2.將上一步生成的新的實例對象賦值給student3。
@property (nonatomic, copy) Student *copStudent;
self.copStudent = student1;
@property = ivar + getter + setter
ivar 是實例變量,編譯器會幫我們自動生成名字為_屬性名這樣的實例變量,同時也會自動生成setter與getter方法。
copy是@property的語義設置關鍵字,不同的關鍵字,屬性的setter、getter的內(nèi)部實現(xiàn)不一樣。
self.copStudent = student1;
這里就是調(diào)用copStudent的setter方法(語義關鍵字是copy),這就是兩者的區(qū)別。
最后要提一點:
copy可以分為淺拷貝、深拷貝、完全拷貝。
淺拷貝(shallow copy): 在淺拷貝操作時,對于被拷貝的對象的每一層都是指針拷貝。
深拷貝(one-level-deep copy): 在深拷貝操作時,對于被拷貝的對象,至少一層是深拷貝。
完全拷貝(real-deep copy): 在完全拷貝操作時,對于被拷貝對象的每一層都是深拷貝。