1. 程式人生 > >Objective-C的協議(Protocol)——協議的實現

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)見另一篇筆記分析。