L__
L__

Reputation: 946

Is substituting self in init method a bad practice?

I've read this from Appledoc regarding the famous (or infamous?) init method

In some cases, an init method might return a substitute object. You must therefore always use the object returned by init, and not the one returned by alloc or allocWithZone:, in subsequent code.

So say I have these two class

@interface A : NSObject
@end

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

with the following implementation

@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

When B calls init, the [super init] might return a substituted A object, and won't it cause an error when B tries to set the property (which A does not have)?

If this does cause an error, how can we implement the above pattern in the correct way?

Update: attaching a more real-world specific problem

Here's a C++ class called C (its use would be explained later)

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);
}

Say the aim of A is to act as a wrapper of C; and it's vital that a one-to-one relationship should be maintained between A and C at all times.

So I come up with the following inteface and implementation

@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

This works for the time being. Now I'd like to subclass A so I come up with B

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

A problem surfaces now as A does not have the knowledge of how to properly unbind an instance. So I have to modify the above code into

@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

Now B only has to override bind and unbind to make it work.

But when I think about it, all B wants to do is to have an additional array usefulArray, does it really warrant this much of work...? And the idea that writing unbind is only for your subclass to replace you in the 1-to-1 relationship with the C++ object just seems weird (and inefficient as well).

Upvotes: 4

Views: 286

Answers (2)

Jsdodgers
Jsdodgers

Reputation: 5312

Your code is correct and should not produce any errors.

What they mean by "might produce a substitute object" is not that it might return an object of a type of class you are expecting, but rather that their super init method might create a different instance of the same class.

Thus, the return for [super init] might be a different object than self is, which is why you need to do self = [super init] rather than just [super init]. You can, however, safely assume that the object will be initialized as you expect as long as you have no coding errors.

This is also why you put self = [super init] in an if statement; if for some reason the initializer returns nil, you dont want to continue setting things and you just want to return self.

Upvotes: 1

Martin R
Martin R

Reputation: 539725

There seems to be one misunderstanding: An init method must always return an instance of the receiving class's type. If -[A init] is called via [super init] in -[B init], self is an (allocated but not yet initialized) instance of class B. Therefore -[A init] must return an instance of class B (or of a subclass).

So if you decide to "recycle" objects, you must ensure that an object of the correct class is recycled.

I cannot tell if "substituting self in init" is bad practice in your case, that would probably depend on the objects and the canBeReused condition. It is mainly done by "class clusters" such as NSNumber, NSArray etc.

Upvotes: 0

Related Questions