0

Imagine my class has following ivars/properties:

@property (nonatomic, copy)   NSString *itemName;
@property (nonatomic, copy)   NSString *serialNumber;
@property (nonatomic) int     valueInDollars;
@property  NSDate *dateCreated; 

1) One way to initialize ivars of this class is like this:

// Designated initializer of this class 
-(id) initWithItemName: (NSString*) name
      valueInDollars:(int)value
      serialNumber:(NSString *)sNumber
{

    // Call the superclass's designated initializer
    self = [super init];

    if(self)
    {
        // Init properties
        self.itemName =  name;          
        self.serialNumber = sNumber;      
        self.valueInDollars = value;       
        dateCreated = [[NSDate alloc] init];  
    }

    // Return the address of the newly initialized object
    return self;
}

2) Another way I am thinking go initialize this class is for example is to write:

-(id) init
{
 self = [super init];

    if(self)
    {
     // basically do nothing
    }

return self;

}

And then leave it up to the user who will be using the class to do initialization as he needs it, e.g.,

MyClass *object = [[MyClass alloc] init];

object.dateCreated = [[NSDate alloc] init];
[object.dateCreated someMethod];
object.itemName = "Hello";
object.someProperty = [[SomeClass alloc] init];

The thing with above I think is that some properties (as above) must be called a alloc/init before they can be used isn't it? And if user forgets to do so, then at most the app won't work as expected right? (It won't crash as we can send message to nil). What I wrote here seems to be the only problem with this way of initialization. What is your opinion?

ps. it is permitted as here too: http://developer.apple.com/library/ios/#documentation/general/conceptual/CocoaEncyclopedia/Initialization/Initialization.html

pps. Assuming ARC is used.

thanks for many replies, but basically I was interested in what are the possible problems with solution 2?

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
user2054339
  • 393
  • 1
  • 6
  • 20
  • 1
    Depends on your design. If your object's properties must have default values other than nil, set them in init. – Kreiri Aug 07 '13 at 07:57
  • 2
    In my experience, it is incredibly rare that a class has no invariants which it should maintain at all times -- and that includes the construction time. Using option 2 is fine, if the values of your class' properties are independent from each other. It is not so fine, if the values must form a consistent state. So, it really depends on the use case. Yes, yes: that's _primarily opinion based_ ... – Dirk Aug 07 '13 at 08:56
  • Regarding option 2, if you are doing nothing in your implementation of init, you don't need to declare a method for it; the superclasses implementation will be called. – Abizern Aug 07 '13 at 11:05
  • Since it's not mentioned on any answer, you shouldn't use self inside a constructor, because it may have side effects on an object which have not been initialized yet. Use direct access instead (_itemName). – Jano Aug 07 '13 at 11:24
  • I nominated for reopening because you should create objects with valid state to avoid users forgetting to set some variable before using them. Even when you may find rare exceptions, this is objectively a difference between good and bad style. – Jano Aug 07 '13 at 11:33

6 Answers6

1

I think you'll find the answer in the document that you linked to:

Overriding init is fine for subclasses that require no additional data to initialize their objects. But often initialization depends on external data to set an object to a reasonable initial state.

So if your class is not in a reasonable state if the variables are not initialised to a proper value, you should use

- (id)initWithItemName:(NSString*)name valueInDollars:(int)value serialNumber:(NSString *)sNumber

Then in init you could either call your designated initialiser with default values, or if there are no reasonable default values disallow the use of init as described here on SO

Community
  • 1
  • 1
Sebastian
  • 7,670
  • 5
  • 38
  • 50
1

I'd advise you to create a factory method that calls to the init method in order to combine allocation and initialization in the same step and also hiding the details of the initialization.

@interface CCAttachment()
@property (readwrite, strong, nonatomic) NSString *urlString;
@property (readwrite, strong, nonatomic) NSString *baseURLString;
@property (readwrite, strong, nonatomic) NSData *data;
@property (readwrite, strong, nonatomic) id object;
@property (readwrite, strong, nonatomic) AFHTTPClient *client;
@end

@implementation CCAttachment
//Init method
- (id)initWithURLString:(NSString *)aURLString baseURLString:(NSString *)aBaseURLString
{
    self = [super init];
    if (self)
    {
        self.urlString = aURLString;
        self.baseURLString = aBaseURLString;
    }
    return self;
}
//Factory method
+ (instancetype)attachmentWithURLString:(NSString *)aURLString baseURLString:(NSString *)aBaseURLString
{
    return [[self alloc] initWithURLString:aURLString baseURLString:aBaseURLString];
}

@end

They will provide a more uniform interface for creating instances. For example, if you later want to convert the above object to an nsmanagedobject, you would keep the same factory method and only change its implementation

+ (instancetype)attachmentWithURLString:(NSString *)aURLString baseURLString:(NSString *)aBaseURLString
{
    CCAttachment *result = [NSEntityDescription insertNewObjectForEntityForName:NSStringFromClass(self.class) inManagedObjectContext:MOC];
    result.urlString = aURLString;
    result.baseURLString = aBaseURLString;
    return result;
}

http://developer.apple.com/library/ios/#documentation/general/conceptual/CocoaEncyclopedia/ClassFactoryMethods/ClassFactoryMethods.html

serrrgi
  • 644
  • 3
  • 7
0

As @Kreiri said - Depedns... But I think that if you want to prevent some stupid errors/mistakes you should give a user a tool called designated initializer. The less possibilities to crash the better your code is!

cojoj
  • 6,405
  • 4
  • 30
  • 52
0

You should try to initialize ivars by yourself to prevent any type of crash. 1st approach bounds the user to input all parameters, and is safe. 2nd approach can cause crash a you mentioned yourself, but you can be on safe side if you initialize variables yourself in 2nd approach.

-(id) init
{
 self = [super init];

    if(self)
    {
     itemName = @"";
    serialNumber = @"";
    valueindollars = -1;
    dateCreated = nil;//Better than garbage
    }

return self;

}

As a good programming practice, you should initialize objects to nil to avoid access to any garbage value.

NightFury
  • 13,436
  • 6
  • 71
  • 120
  • 1
    Instance variables of Objective-C objects are automatically initialized to nil/0/NO/..., there is no "garbage" (as in local stack variables). – Martin R Aug 07 '13 at 08:36
0

You should make yourself familiar with the basics of Object Oriented Design.

Generally, an instance of a class SHALL fulfill a crisp defined invariant after it has been created. That means, your instance (that includes all ivars) SHALL be in a particular, logical correct state after it has been initialized.

It's also required that after responding to any message the instance MUST still fulfill its invariants. This state may be different than before responding to the message, but it MUST still in a reasonable state.

Form your second design, a user can set and read any ivar at any time through the properties. Now suppose, your instance also responds to other messages which you have defined as methods in your class.

And now answer that question: is it guaranteed that at any time an instance of your class is in a state which fulfills the invariant conditions and thus can always respond to any messages in a clear and defined manner?

You will realize, that in the second approach, this is only the case when your class just serves as a container for the four ivars, and has effectively no other responsibilities. So, one should ask what's then the purpose of the class at all? ;)

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
0

As a general principle, it is bad style to create an object with an invalid state, because the user may forget that he has to set a valid state for some variable before using it.

Sometimes there are too many possible arguments, which would make the classical pattern (sometimes called “telescoping constructor”) cumbersome. Think of pizza ingredients for example initPizzaWithPepperoni:tomate:mozzarella:...etc. For this cases, you can use a builder pattern.

This example from “Effective Java” illustrates three different ways to initialize an object

  • builder: useful when there are too many possible arguments or their combinations are complex.
  • bean
  • “telescoping constructor”

main.m

int main(int argc, char *argv[]) {
    @autoreleasepool {
        NutritionFacts *facts = [NutritionFacts new];

        // builder
        facts = [[[[facts builder] calories:100] sodium:35] build];

        // bean constructor pattern
        facts.calories=100;
        facts.sodium=35;

        // telescoping constructor pattern
        // [[NutritionFacts alloc] initWithCalories:100];
        // [[NutritionFacts alloc] initWithCalories:100 sodium:35];
    }
}

NutritionFacts+Builder.m

@interface NutritionFacts(Builder)
-(NutritionFactsBuilder*)builder;
@end

@implementation NutritionFacts(Builder)
-(NutritionFactsBuilder*)builder {
    return [[NutritionFactsBuilder alloc]initWithNutritionFacts:self];
}
@end

NutritionFacts.m

@interface NutritionFacts : NSObject
@property (nonatomic, assign) NSUInteger calories, carbohydrate,
cholesterol, fat, fiber, protein, saturatedFat, sodium;
@end

@implementation NutritionFacts
@end

NutritionFactsBuilder.m

@interface NutritionFactsBuilder : NSObject
-(id)initWithNutritionFacts:(NutritionFacts*)facts;
@end

@implementation NutritionFactsBuilder {
    NutritionFacts *_facts;
}
-(id)initWithNutritionFacts:(NutritionFacts*)facts {
    self = [super init];
    if (self){
        _facts = facts;
    }
    return self;
}
-(BOOL)isValid {
    // ... check valid ivar combos
    NSAssert(YES,@"Always valid");
}
-(NutritionFacts*)build {
    [self isValid];
    return _facts;
}
-(instancetype)calories:(NSUInteger)calories {
    _facts.calories = calories;
    return self;
}
-(instancetype)sodium:(NSUInteger)sodium {
    _facts.sodium = sodium;
    return self;
}
// ...
@end
Jano
  • 62,815
  • 21
  • 164
  • 192