2013-08-22 70 views
4

我從關於著名Appledoc閱讀本(或臭名昭著的?)init方法在init方法中取代自己是一種不好的做法嗎?

在某些情況下,init方法可能會返回一個替代對象。因此,您必須在隨後的代碼中始終使用由init返回的對象,而不是由alloc或allocWithZone:返回的對象。

所以說我有這兩個類

@interface A : NSObject 
@end 

@interface B : A 
@property (nonatomic, strong) NSArray *usefulArray; 
@end 

與以下實現

@implementation A 
+(NSMutableArray *)wonderfulCache { 
    static NSMutableArray *array = nil; 
    if (!array) 
     array = [NSMutableArray array]; 
    return array; 
} 

-(id)init { 
    if (self=[super init]) { 
     // substituting self with another object 
     // A has thought of an intelligent way of recycling 
     // its own objects 
     if ([self.class wonderfulCache].count) { 
      self = [self.class wonderfulCache].lastObject; 
      [[self.class wonderfulCache] removeLastObject]; 
     } else { 
      // go through some initiating process 
      // .... 
      if (self.canBeReused) 
       [[self.class wonderfulCache] addObject:self]; 
     } 
    } 
    return self; 
} 

-(BOOL) canBeReused { 
    // put in some condition 
    return YES; 
} 
@end 

@implementation B 
-(id)init { 
    if (self=[super init]) { 
     // setting the property 
     self.usefulArray = [NSArray array]; 
    } 
    return self; 
} 
@end 

當B調用init,該[super init]可能會返回一個取代的目的,並且不會吧當B嘗試設置屬性(A沒有)時會導致錯誤?

如果這確實會導致錯誤,我們如何才能以正確的方式實現上述模式?

更新:將一個更真實的特定問題

這裏的所謂C C++類(其使用將在後面解釋)

class C 
{ 
    /// Get the user data pointer 
    void* GetUserData() const; 

    /// Set the user data. Use this to store your application specific data. 
    void SetUserData(void* data); 
} 

說的A的目的是充當一個包裝的C;在AC之間始終保持一對一關係至關重要

於是我想出了以下inteface和實施

@interface A : NSObject 
-(id)initWithC:(C *)c; 
@end 

@implementation A { 
    C *_c; 
} 
-(id)initWithC:(C *)c { 
    id cu = (__bridge id) c->GetUserData(); 
    if (cu) { 
     // Bingo, we've got the object already! 
     if ([cu isKindOfClass:self.class]) { 
      return (self = cu); 
     } else { 
      // expensive operation to unbind cu from c 
      // but how...? 
     } 
    } 
    if (self=[super init]) { 
     _c = c; 
     c->SetUserData((__bridge void *)self); 
     // expensive operation to bind c to self 
     // ... 
    } 
    return self; 
} 
@end 

這適用於時間之中。現在,我想繼承A,所以我拿出B

@interface B : A 
@property (nonatomic, strong) NSArray *usefulArray; 
@end 

的問題表面現在爲A沒有對如何正確地解除綁定實例的知識。所以我要修改上面的代碼到

@interface A : NSObject { 
    C *_c; 
} 
-(id)initWithC:(C *)c; 
-(void) bind; 
-(void) unbind; 
@end 

@implementation A 
-(id)initWithC:(C *)c { 
    id cu = (__bridge id) c->GetUserData(); 
    if (cu) { 
     // Bingo, we've got the object already! 
     if ([cu isKindOfClass:self.class]) { 
      return (self = cu); 
     } else { 
      NSAssert([cu isKindOfClass:[A class]], @"inconsistent wrapper relationship"); 
      [(A *)cu unbind]; 
     } 
    } 
    if (self=[super init]) { 
     _c = c; 
     c->SetUserData((__bridge void *)self); 
     [self bind]; 
    } 
    return self; 
} 

-(void) bind { 
    //.. do something about _c 
} 

-(void) unbind { 
    // .. do something about _c 
    _c = nil; 
} 
@end 

現在B只覆蓋bindunbind,使其工作。

但是當我想到它時,所有B想要做的就是有一個額外的陣列usefulArray,它真的保證這麼多工作...?並且編寫unbind的想法僅適用於您的子類,以與C++對象的1對1關係取代您,這看起來很奇怪(並且效率也很低)。

回答

0

似乎有一個誤區: 一個init方法必須總是返回接收類的一個實例。 如果-[A init]通過[super init]-[B init]中調用,self是類B的 (已分配但尚未初始化)實例。因此-[A init] 必須返回類B(或子類)的一個實例。

因此,如果您決定「回收」對象,則必須確保回收正確類的對象。

我不能告訴你是否在你的情況下「代替自己在init中」,那麼 可能取決於對象和canBeReused條件。它主要通過 「類集羣」,如NSNumber,NSArray

+0

的確,我在我的代碼中做的是在嘗試使用緩存對象時放置'isKindOfClass:'檢查。不過,我覺得這很不好,並不是很優雅。這就是爲什麼我試圖找到一個完美的解決方案(如果真的存在的話) – lynnard

+0

@ yulan6248:您可以在'B'中覆蓋'wonderfulCache'(只需將該方法複製到B.m),那麼每個子類都會自動擁有自己的緩存。或者,使用「self.class」是關鍵字並且緩存是值的字典。 –

+0

我的真正問題是對ikaver在他的回答中的迴應。我需要在C++對象和它的包裝器之間建立一對一的關係;有一次我意識到我需要繼承這個包裝類。現在我唯一保持一致性的機會是當子類包裝嘗試使用相同的C++對象初始化時,首先撤銷舊包裝對象與其對應的C++對象之間的關係... – lynnard

1

您的代碼是正確的,不應該產生任何錯誤。

他們所說的「可能產生替代對象」並不是說它可能會返回一個你期待的類的對象,而是他們的超級初始化方法可能會創建同一類的不同實例。

因此,[super init]的返回可能與self不同,因此您需要執行self = [super init]而不是[super init]。但是,只要沒有編碼錯誤,您可以安全地假定該對象將按照您的預期進行初始化。

這也是爲什麼你把self = [super init]放在if語句中的原因;如果由於某種原因初始化程序返回nil,您不想繼續設置事情,而只是想返回自我。

+2

如果'[B init]'從緩存而不是'B'對象返回一個'A'對象,則代碼*將會導致錯誤。 –

+0

我只用兩條簡單的線條對其進行了測試:A * a = [A new]; B * b = [B new]'並且如預期的那樣拋出一個錯誤'無法識別的選擇器在 - [A setUsefulArray:]' – lynnard

相關問題