Objective-C: Why not call the designated initializer? - objective-c

I've inherited this code:
- (id)initWithLocation:(CLLocation *)inLocation {
if (self = [super init])
{
_location = [inLocation copy];
}
return self;
}
- (id)initWithLocation:(CLLocation *)inLocation offsetValue:(NSNumber *)offset {
if (self = [super init])
{
_location = [inLocation copy];
_offset = [offset copy];
}
return self;
}
and am wondering if there's a good reason why the first method does not call the designated initializer (e.g. like this Is it okay to call an init method in self, in an init method?)?
i.e. why not do this:
- (id)initWithLocation:(CLLocation *)inLocation {
if (self = [super init])
{
[self initWithLocation:inLocation offsetValue:nil];
}
return self;
}
- (id)initWithLocation:(CLLocation *)inLocation offsetValue:(NSNumber *)offset {
if (self = [super init])
{
_location = [inLocation copy];
_offset = [offset copy];
}
return self;
}

The - (id)initWithLocation:(CLLocation *)inLocation offsetValue:(NSNumber *)offset method should be a designated initializer and the - (id)initWithLocation:(CLLocation *)inLocation should call it like this:
- (id)initWithLocation:(CLLocation *)inLocation {
return [self initWithLocation:inLocation offsetValue:nil];
}
It's also considered a good practice to mark a designated initializer in the class interface using NS_DESIGNATED_INITIALIZER:
- (id)initWithLocation:(CLLocation *)inLocation offsetValue:(NSNumber *)offset NS_DESIGNATED_INITIALIZER;

the much more appropriate way would be like this:
- (id)initWithLocation:(CLLocation *)inLocation {
return [self initWithLocation:inLocation offsetValue:nil];
}
- (id)initWithLocation:(CLLocation *)inLocation offsetValue:(NSNumber *)offset {
if (self = [super init]) {
_location = [inLocation copy];
_offset = [offset copy];
}
return self;
}

All you actually need to do is...
- (id)initWithLocation:(CLLocation *)inLocation {
return [self initWithLocation:inLocation offsetValue:nil];
}
- (id)initWithLocation:(CLLocation *)inLocation offsetValue:(NSNumber *)offset {
if (self = [super init])
{
_location = [inLocation copy];
_offset = [offset copy];
}
return self;
}
And you're right. There is no reason not to in this case.

Related

Is it safe to cascade constructors in Objective C?

I want to do the following:
// I want to do this! :D
- (instancetype) init
{
return [self initWithVal1:[NSDecimalNumber zero] val2:MyEnumDefault];
}
- (instancetype) initWithVal1:(NSDecimalNumber*)val1 val2:(MyEnum)val2
{
return [self initWithVal1:val1 val2:val2 val3:12];
}
- (instancetype) initWithVal1:(NSDecimalNumber*)val1 val2:(MyEnum)val2 val3:(NSInteger)val3
{
if (self = [super init])
{
_prop1 = val1;
_prop2 = val2;
_prop3 = val3;
}
return self;
}
But I know that when I call [self initWithVal1:[NSDecimalNumber zero], self has not yet been initialized. However, I've seen examples around the itnernet that show this in examples. Is this safe, or do I have to initialize them all each time like below?
// I don't want to do this... :(
- (instancetype) init
{
if (self = [super init])
{
_prop1 = [NSDecimalNumber zero];
_prop2 = MyEnumDefault;
_prop3 = 12;
}
return self;
}
- (instancetype) initWithVal1:(NSDecimalNumber*)val1 val2:(MyEnum)val2
{
if (self = [super init])
{
_prop1 = val1;
_prop2 = val2;
_prop3 = 12;
}
return self;
}
- (instancetype) initWithVal1:(NSDecimalNumber*)val1 val2:(MyEnum)val2 val3:(NSInteger)val3
{
if (self = [super init])
{
_prop1 = val1;
_prop2 = val2;
_prop3 = val3;
}
return self;
}
I don't like that approach, since it duplicates code, like _prop3 = 12, which leads to potential discrepancies if the code must be changed later.
It's safe because self has already been allocated (with alloc) by the time its init is called. So, methods can be called on it, but its objects are just all not-yet-allocated.

How to fix "instance variable while 'self'"

How to fix this analysis issue:
CODE
if(![super initWithFrame:CGRectZero]){
return nil;
}
firstOfPrev = -1;
marks = markArray;
monthDate = date;
startOnSunday = sunday;
You're getting the error because you're never assigning the result of the super initializer call to self. I believe this is what you want:
- (instancetype)initWithFrame:(CGRect)frame
{
if (!(self = [super initWithFrame:frame])) {
return nil;
}
// do stuff
return self;
}
However, this may be more clear if you write out your initializers like this:
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// do stuff
}
return self;
}

Bail out of an object's init with arc?

How do I bail out of creating an object with ARC?
I'm looking for the ARC equivalent of this (from memory):
- (id)init
{
if (( self = [super init] )) {
if (!condition) {
[self release];
self = nil;
return self;
}
}
return self;
}
Just get rid of the call to release and you'll be fine. Since you nil self, there will be no more references to the old self so it will be deallocated.
- (id)init;
{
if ((self = [super init])) {
if (!condition) {
return nil;
}
}
return self;
}

TTModel load method not being called

Issue
Description
The following code results in load method of TTModel not being called. I've stepped it through the debugger, as well as stepping through the TTCatalog application. The only difference between the two that I can see, is that when the catalog sets it's DataSource in the createModel method of the controller, TTModel's load method gets called, whereas mine does not.
About the Code
I've commented the specific areas of code, to tell what they should be doing and what problem is happening, but I included all of it for completion's sake.
You should look at specifically
PositionsController's createModel method
PositionsList's load method
Those are the areas where the issue is, and would be the best place to start.
Code
PositionsController : TTTableViewController
- (id)initWIthNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self)
{
self.title = #"Positions";
self.variableHeightRows = NO;
self.navigationBarTintColor = [UIColor colorWithHexString:#"1F455E"];
}
return self;
}
//This method here should result in a call to the PositionsList load method
- (void)createModel
{
PositionsDataSource *ds = [[PositionsDataSource alloc] init];
self.dataSource = ds;
[ds release];
}
- (void)loadView
{
[super loadView];
self.view = [[[UIView alloc] initWithFrame:TTApplicationFrame()] autorelease];
self.tableView = [[[UITableView alloc] initWithFrame:TTApplicationFrame() style:UITableViewStylePlain] autorelease];
self.tableView.backgroundColor = [UIColor colorWithHexString:#"E2E7ED"];
self.tableView.separatorColor = [UIColor whiteColor];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
//self.tableView.delegate = self;
[self.view addSubview:self.tableView];
}
//Override UITableViewDelegate creation method, so we can add drag to refresh
- (id<TTTableViewDelegate>) createDelegate {
TTTableViewDragRefreshDelegate *delegate = [[TTTableViewDragRefreshDelegate alloc] initWithController:self];
return [delegate autorelease];
}
PositionsDataSource : TTListDataSource
#implementation PositionsDataSource
#synthesize positionsList = _positionsList;
-(id)init
{
if (self = [super init])
{
_positionsList = [[PositionsList alloc] init];
self.model = _positionsList;
}
return self;
}
-(void)dealloc
{
TT_RELEASE_SAFELY(_positionsList);
[super dealloc];
}
-(void)tableViewDidLoadModel:(UITableView*)tableView
{
self.items = [NSMutableArray array];
}
-(id<TTModel>)model
{
return _positionsList;
}
#end
PositionsList : NSObject <TTModel>
#implementation PositionsList
//============================================================
//NSObject
- (id)init
{
if (self = [super init])
{
_delegates = nil;
loaded = NO;
client = [[Client alloc] init];
}
return self;
}
- (void)dealloc
{
TT_RELEASE_SAFELY(_delegates);
[client dealloc];
[super dealloc];
}
//==============================================================
//TTModel
- (NSMutableArray*)delegates
{
if (!_delegates)
{
_delegates = TTCreateNonRetainingArray();
}
return _delegates;
}
-(BOOL)isLoadingMore
{
return NO;
}
-(BOOL)isOutdated
{
return NO;
}
-(BOOL)isLoaded
{
return loaded;
}
-(BOOL)isEmpty
{
//return !_positions.count;
return NO;
}
-(BOOL)isLoading
{
return YES;
}
-(void)cancel
{
}
//This method is never called, why is that?
-(void)load:(TTURLRequestCachePolicy)cachePolicy more:(BOOL)more
{
//This method is not getting called
//When the PositionsController calls self.datasource, load should be called,
//however it isn't.
[_delegates perform:#selector(modelDidStartLoad:) withObject:self];
[client writeToServer:self dataToSend:#""];
}
-(void)invalidate:(BOOL)erase
{
}
#end
Short answer: return NO instead of YES for isLoading in your PositionList.
For a longer explanation:
If you dig through the Three20 source, you'll find that setting the dataSource on a view controller sets the model, refreshing the model and potentially calling load. Here is the code called when the TTModelViewController refreshes:
- (void)refresh {
_flags.isViewInvalid = YES;
_flags.isModelDidRefreshInvalid = YES;
BOOL loading = self.model.isLoading;
BOOL loaded = self.model.isLoaded;
if (!loading && !loaded && [self shouldLoad]) {
[self.model load:TTURLRequestCachePolicyDefault more:NO];
} else if (!loading && loaded && [self shouldReload]) {
[self.model load:TTURLRequestCachePolicyNetwork more:NO];
} else if (!loading && [self shouldLoadMore]) {
[self.model load:TTURLRequestCachePolicyDefault more:YES];
} else {
_flags.isModelDidLoadInvalid = YES;
if (_isViewAppearing) {
[self updateView];
}
}
}
Your PositionList object is returning YES for isLoading and NO for isLoaded. This means that Three20 thinks your model is loading when it isn't. You may also need to implement shouldLoad if it doesn't return YES by default.

Adding a custom initWith?

If I create a custom initWith for an object do I essentially include the code I would add should I want to override init?
-(id) init {
self = [super init];
if (self) {
NSLog(#"_init: %#", self);
}
return(self);
}
e.g.
-(id) initWithX:(int) inPosX andY:(int) inPosY {
self = [super init];
if(self) {
NSLog(#"_init: %#", self);
posX = inPosX;
posY = inPosY;
}
return(self);
}
gary
You can create one designated initializer that accepts all parameters that you want to make available in initialization.
Then you call from your other -(id)init your designated initializer with proper parameters.
Only the designated initializer will initialize super class [super init].
Example:
- (id)init
{
return [self initWithX:defaultX andY:defaultY];
}
- (id)initWithPosition:(NSPoint)position
{
return [self initWithX:position.x andY:position.y];
}
- (id)initWithX:(int)inPosX andY:(int)inPosY
{
self = [super init];
if(self) {
NSLog(#"_init: %#", self);
posX = inPosX;
posY = inPosY;
}
return self;
}
The designated initializer is -(id)initWithX:andY: and you call it from other initializers.
In case you want to extend this class you call your designated initializer from subclass.
I'd suggest creating one main initializer that handles most of the work. You can then create any number of other initializers that all call this main one. The advantage of this is if you want to change the initialization process, you'll only have to change one spot. It might look like this:
-(id) initWithX:(float)x {
if (self = [super init]) {
/* do most of initialization */
self.xVal = x;
}
return self;
}
-(id) init {
return [self initWithX:0.0f];
}
In this example initWithX: is our main initializer. The other initializer (init) simply calls initWithX: with a default value (in this case 0).
Yes, that's exactly how I do it. One slight change will cut out a line of code:
if (self = [super init]) {
As opposed to:
self = [super init];
if(self) {
For modern Objective-C ...
UDFile.h
#import <Foundation/Foundation.h>
#interface UDFile : NSObject
#property (nonatomic, strong) NSString *name;
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
#end
UDFile.m
#import "UDFile.h"
#implementation UDFile
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
_name = [name copy];
}
return self;
}
- (instancetype)init {
return [self initWithPathname:#""];
}
Sometimes, you want to reuse some initialisation code and modify the behaviour only slightly for specific initialisers. In this case, I do the following:
- (id) init
{
self = [super init];
if (!self) return nil;
// These values are always initialised this way
ivar1 = 10;
ivar2 = #"HellO";
ivar3 = [[NSMutableArray alloc] initWithCapacity:10];
ivar4 = 22;
return self;
}
- (id) initWithIvar4:(int) aValue
{
// call -init on self, which will call -init on super for us, and set
// up ivar1, ivar2, ivar3, and ivar4.
self = [self init];
if (!self) return nil;
// Change ivar4 from the default 22 to whatever aValue is.
ivar4 = aValue;
return self;
}