【问题标题】:Is substituting self in init method a bad practice?在 init 方法中替换 self 是一种不好的做法吗?
【发布时间】:2013-08-24 19:34:08
【问题描述】:

我从 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的目的是充当C的包装器;在AC 之间保持一对一的关系非常重要始终

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

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

【问题讨论】:

    标签: objective-c subclass init


    【解决方案1】:

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

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

    我无法判断在您的情况下“在 init 中替换 self”是否是不好的做法,那会 可能取决于对象和canBeReused 条件。它主要由 “类簇”如NSNumberNSArray

    【讨论】:

    • 确实,我在代码中所做的是在尝试使用缓存对象时进行isKindOfClass: 检查。但是,我觉得这很hacky而且不是很优雅。这就是为什么我试图找到一个完美的解决方案(如果真的有的话)
    • @yulan6248:你可以在B中覆盖wonderfulCache(只需将方法复制到B.m),然后每个子类自动拥有自己的缓存。或者,使用字典,其中self.class 是键,缓存是值。
    • 我的真正问题在他的回答中概述为对 ikaver 的回应。我需要在 C++ 对象与其包装器之间建立一对一的关系;有一次我意识到我需要子类化这个包装类。现在我保持一致性的唯一机会是当子类包装器尝试使用相同的 C++ 对象初始化时,首先撤销旧包装器对象与其对应的 C++ 对象之间的关系...
    • @yulan6248:我建议您用更多信息更新您的问题,以便其他读者在不浏览所有 cmets 的情况下了解您的“真正问题”。或许添加一些关于 C++ 类、Objective-C 包装类和子类的细节。
    【解决方案2】:

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

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

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

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

    【讨论】:

    • 如果[B init] 从缓存中返回A 对象而不是B 对象,代码 导致错误。
    • 我刚刚用两条简单的线测试了它A *a = [A new]; B *b= [B new] 并且它按预期抛出了一个错误unrecognized selector in -[A setUsefulArray:]
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-03-30
    • 1970-01-01
    • 1970-01-01
    • 2019-04-19
    • 2015-05-31
    • 2019-01-24
    • 1970-01-01
    相关资源
    最近更新 更多