4

我从 Appledoc 中读到了这个关于著名(或臭名昭著?)的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]可能会返回一个替换的 A 对象,并且当 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是充当 ; 的包装器CA并且C 始终保持一对一的关系至关重要。

所以我想出了以下接口和实现

@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++ 对象的一对一关系中替换您的想法似乎很奇怪(而且效率也很低)。

4

2 回答 2

1

您的代码是正确的,不应产生任何错误。

他们所说的“可能会产生一个替代对象”的意思并不是说它可能会返回您期望的类类型的对象,而是他们的超级 init 方法可能会创建同一类的不同实例。

因此,[super init] 的返回值可能与selfis 不同,这就是为什么你需要 doself = [super init]而不仅仅是[super init]. 但是,您可以放心地假设只要您没有编码错误,对象就会按照您的预期进行初始化。

这也是您放入self = [super init]if 语句的原因;如果由于某种原因初始化程序返回nil,你不想继续设置,你只想返回 self。

于 2013-08-22T08:13:30.377 回答
0

似乎存在一个误解:init方法必须始终返回接收类类型的实例。如果-[A init]通过[super init]in调用-[B init]self是 class 的(已分配但尚未初始化)实例B。因此-[A init] 必须B返回类(或子类)的实例。

因此,如果您决定“回收”对象,则必须确保回收了正确类的对象。

在您的情况下,我无法判断“在 init 中替换 self”是否是不好的做法,这可能取决于对象和canBeReused条件。主要是通过“类簇”来完成的,例如NSNumberNSArray

于 2013-08-22T08:12:03.317 回答