Objective-C的協議(Protocol)——協議的實現
協議就是定義了一組方法,然後要求其他類去實現。
以下類的複製來舉例說明,遵守NSCopying協議的類是如何實現複製的。
0x01 NSCopying協議
NSCopying是物件拷貝的協議。
類的物件如果支援拷貝,則該類應遵守並實現NSCopying協議。
用NSCopying協議來舉例,是因為該協議中的方法只有一個,比較容易理解:
- (id)copyWithZone:(NSZone *)zone { Person *model = [[[self class] allocWithZone:zone] init]; model.firstName = self.firstName; model.lastName = self.lastName; //未公開的成員 model->_nickName = _nickName; return model; }
0x02 複製Engine類
為了能夠複製engine物件,Engine類需要採用NSCopying協議。
所以Engine類的介面更新如下:
//Engine類的介面
@interface Engine : NSObject <NSCopying>
...
@end // Engine
因為Engine類採用了NSCopying協議,所以必須實現協議中的copyWithZone:方法。
zone是NSZone類的一個物件,指向一塊可供分配的記憶體區域。
當你向一個物件傳送copy訊息時,該copy訊息實際上將會轉換成copyWithZone:方法。
Engine類的copyWithZone:方法實現如下:
//Engine類的實現
@implementation Engine
- (id) copyWithZone: (NSZone *) zone
{
Engine *engineCopy;
engineCopy = [[[self class] allocWithZone: zone] init];
return (engineCopy);
} // copyWithZone
...
copyWithZone
copyWithZone:方法首先獲得self引數所屬的類,然後向self物件所屬的類傳送allocWithZone:訊息以分配記憶體並建立一個該類的新物件。最後,copyWithZone:方法給這個物件傳送init:訊息使其初始化。
這裡也可以使用指定初始化函式來執行初始化工作。
allocWithZone
alloc是類方法,allocWithZone也是類方法。
allocWithZone:方法的最後一行會返回新建立的物件。
+ (id) allocWithZone: (NSZone *) zone;
為什麼這裡使用[[self class] allocWithZone: zone]而不是[Engine allocWithZone: zone]?
假如Engine類有個子類Slant6。
如果我們直接給Engine類傳送allocWithZone:訊息,建立的將是一個新的Engine類物件,而不是Slant6類物件。
通過使用[self class],allocWithZone:訊息會被正確地傳送給正在接收copy訊息的物件所屬的類。
0x03 複製Tire系列類
複製超類Tire
Tire類的複製工作比Engine類複雜些,因為Tire類有兩個例項變數,其子類AllWeatherRadial又引入了兩個例項變數。
既然是複製,那麼例項變數也是必須被複制的。
Tire類採用協議的新介面程式碼如下:
//Tire類的介面
@interface Tire : NSObject <NSCopying>
@property float pressure;
@property float treadDepth;
...
@end // Tire
Tire類的copyWithZone:方法實現如下:
//Tire類的實現
- (id) copyWithZone: (NSZone *) zone
{
Tire *tireCopy;
tireCopy = [[[self class] allocWithZone: zone] initWithPressure:pressure
treadDepth:treadDepth];
return (tireCopy);
} // copyWithZone
...
這裡因為Tire類需要複製兩個例項變數,所以使用指定初始化函式來完成初始化工作。
複製子類AllWeatherRadial
子類AllWeatherRadial的介面程式碼不需要變化:
//AllWeatherRadial類的介面
@interface AllWeatherRadial : Tire
// ... properties
// ... methods
@end // AllWeatherRadial
為什麼子類的介面不用寫<NSCopying>協議名?
因為子類繼承超類的時候,也包括獲得超類的所有屬性,遵守超類遵守的協議。
但是必須重寫copyWithZone:方法,因為AllWeatherRadial類中有兩個例項變數是新增的,必須同時被複制:
//子類AllWeatherRadial的實現
- (id) copyWithZone: (NSZone *) zone
{
AllWeatherRadial *tireCopy;
tireCopy = [super copyWithZone: zone];
//複製子類新增的例項變數
tireCopy.rainHandling = rainHandling;
tireCopy.snowHandling = snowHandling;
return (tireCopy);
} // copyWithZone
...
為什麼這裡用super而不用self?
因為AllWeatherRadial類繼承自Tire類,所以子類只需直接請求超類執行copy操作,並期望超類正確地複製以及在分配物件時使用[self class]。所以,這裡用super直接訪問超類。
剩下的工作就是把rainHandling和snowHandling兩個例項變數複製到新的物件tirecopy中去。
0x04 複製Car類
Car類沒有什麼特殊的,一樣要遵守NSCopying協議。介面程式碼修改如下:
//Car類的介面
@interface Car : NSObject <NSCopying>
// properties
// ... methods
@end // Car
修改後的Car類實現如下:
//Car類的實現
- (id) copyWithZone: (NSZone *) zone
{
Car *carCopy;
carCopy = [[[self class] allocWithZone: zone] init];
//複製車名
carCopy.name = self.name;
//複製Engine
Engine *engineCopy;
engineCopy = [engine copy];
carCopy.engine = engineCopy;
//複製Tire
for (int i = 0; i < 4; i++)
{
Tire *tireCopy;
tireCopy = [[self tireAtIndex: i] copy];
[carCopy setTire: tireCopy atIndex: i];
}
return (carCopy);
} // copyWithZone
...
為什麼這裡並沒有使用NSArray代替for迴圈?
因為NSArray的copy方法只會建立一個淺複製(shalldow copy)而不是深複製(deep copy)。
關於淺複製和深複製(deep copy)見另一篇筆記分析。