2013-08-22 37 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

相关问题