Is replacing the self in init method a bad practice?

I read this from Appledoc regarding the famous (or shameful?) init method

In some cases, the init method may return a placeholder object. Therefore, you should always use the object returned by init, and not the one returned by alloc or allocWithZone :, in the following code.

So to speak, I have these two classes

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

with the next 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 , [super init] can return the replaced object A, and does it cause an error when B tries to set a property (which A does not have)?

If this causes an error, how can we correctly implement the above pattern?

Update: adding a more specific specific problem

Here's a C ++ class called C (its use will 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); } 

Let's say goal A is to act like a wrapper C ; and it is vital that a one-to-one relationship is maintained between A and C at all times .

So, I come up with the following integration 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 

It works for now. Now I would like to subclass A , so I come up with B

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

Now the problem arises when A does not know how to properly untie the instance. So I need to change the above code to

 @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 needs 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 of usefulArray , does this really justify this most of the work ...? And the idea that the unbind entry is only for your subclass, which will replace you in a 1-to-1 relationship with a C ++ object, just seems weird (and inefficient).

+4
source share
2 answers

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

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

I can’t say whether “replacing self in init” is wrong practice in your case, which probably depends on the objects and the canBeReused condition. This is mainly done by "class clusters" such as NSNumber , NSArray , etc.

0
source

Your code is correct and should not contain any errors.

What they mean by “can create a placeholder object” is not that it can return an object of the class type that you expect, but rather that their super-init method can create another instance of the same class.

Thus, returning for [super init] may be a different object than self , so you need to do self = [super init] , not just [super init] . However, you can safely assume that the object will be initialized as you expect until you have coding errors.

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

+1
source

Source: https://habr.com/ru/post/1498327/


All Articles