I've read in many places that you should always initialize Objective-C objects like so:
- (id) init {
if (self = [super init]) {
....
}
return self;
}
Because the super's init method may return a separate object from the current self.
Now I'm trying to do something like this, and I'm not sure if I have it right, vis-a-vis how retaining and releasing should work:
- (id) init:(int)idx {
id obj = [Cache findSelf:idx];
if (obj) {
[self release];
self = [obj retain];
} else {
self = [self doLoad];
}
return self;
}
I'm mostly curious if this is the correct way to do the retaining and releasing of self and obj. Is there a better way?
You're correct about the self = [super init] part, since some Cocoa classes actually do return a different object than the one that was allocated. However, this is the exception rather than the rule, and doing so in your own code should be exceedingly rare or not done at all. Although it may be tempting to intercept -init calls, you'd be going against the grain of established convention and what Objective-C programmers expect the code to do.
This type of -init method is generally a bad approach, since -init methods should be as straightforward as possible, and should really be concerned with initializing the object. I'd probably write a convenience method like this:
+ (id) instanceForIndex:(NSUInteger)index {
id obj = [Cache findSelf:index];
if (obj == nil) {
obj = [[self alloc] init];
// Add to cache
}
return [[object retain] autorelease];
}
Then call this method instead of -init. This will make the -init logic much cleaner.
Also, I'm not sure what your Cache class does, but it could be worth rethinking that implementation, and using a hidden static variable to store instances (for example, an NSMutableDictionary, where the key is an NSNumber created from the index). This SO question may be of use.
I agree with Quinn that you should use a convenience class method. Still, I think that your init method is mostly correct, except in your else clause you need to call the parent initializer, i.e. self = [super init].
Related
I'm in a bit of a pickle. I know that calling [self methodName] from within a block will lead to a retain cycle.
However in this class due to multithreading I cannot allow execution of the method that the block is accessing from anywhere else other than the block, as it would potentially lead to serious problems.
Current code:
if (_getRunning==NO){
__weak SyncArrayMT *_weak_self = self;
_get_t = ^void (void){
_weak_self->_getRunning = YES;
NSArray *objects = [_weak_self get:getQuery
usingClassCtor:ctor
withAuthBlock:authBlock];
if (_weak_self.getBlockCb)
_weak_self.getBlockCb(objects);
_weak_self->_getRunning = NO;
};
}
Does exactly that, it calls [self getmethod]. While its ok for the dispatched block to run this method, I do not want anything outside this class calling this method.
So, would it be ok to override this inherited method as such:
- (NSArray *) get:(NSString *)getQuery usingClassCtor:(initBlock)initCb withAuthBlock:(authenticate)authBlock
{
NSLog(#"Direct call to get is not allowed - use the threaded method");
return nil;
}
And then change the block to this:
_get_t = ^void (void){
_weak_self->_getRunning = YES;
NSArray *objects = [super get:getQuery
usingClassCtor:ctor
withAuthBlock:authBlock];
if (_weak_self.getBlockCb)
_weak_self.getBlockCb(objects);
_weak_self->_getRunning = NO;
};
I have tried it and it works without doing a call to the [self getMethod], but will super be retained, properly released, etc? Yes I am using ARC. Would calling super within a block lead to any problem ? Is there a hack to get a __weak to super instead ?
Alternatively, how can I disallow direct calls to [self getMethod] (which is inherited) and only use it internally ?
I know that Objective-C doesn't exactly implement this, but I know there are tricks, such as declaring and implementing a method in the implementation file only.
EDIT#1:
I have tried with SEL & IMP and function pointers. Problem is that IMP and function pointers require as a parameter an instance, and this renders the hole point mute:
NSString * (*getFuncPtr)(id,SEL,id,id) = (NSString * (*)(id,SEL,id,id))[super methodForSelector:#selector(sendObjectsPassingTest:withAuthBlock:)];
NSString *reply = getFuncPtr(_weak_self,#selector(sendObjectsPassingTest:withAuthBlock:),predicate,authBlock);
This simply calls the inherited method. Trying to use it with super simply gives an error. At this point I will go ahead and simply use super within the block, and try and profile to see if it leads to any retain cycle.
EDIT#2:
Based on newacct's answer, this is what I ended up doing:
typedef NSArray * (* getFuncPtr)(id,SEL,id,id,id);
...
...
__weak SyncArrayMT *_weak_self = self;
_getMethod = (NSArray * (*)(id,SEL,id,id,id))[[[self class] superclass] instanceMethodForSelector:#selector(get:usingClassCtor:withAuthBlock:)];
_get_t = ^void (void){
NSArray *objects = _weak_self->_getMethod(_weak_self,#selector(get:usingClassCtor:withAuthBlock:),getQuery,ctor,authBlock);
}
I am hoping this should avoid any retain cycles, although I haven't actually profiled it yet.
I know that calling [self methodName] from within a block will lead to
a retain cycle.
That is not true in general. The block will retain self, yes. But there will only be a "retain cycle" if self somehow retains the block. In this case, it does.
but will super be retained
Yes, self will be retained (super is a call on self with a different method lookup pathway).
I have tried with SEL & IMP and function pointers. Problem is that IMP
and function pointers require as a parameter an instance, and this
renders the hole point mute:
NSString * (*getFuncPtr)(id,SEL,id,id) = (NSString * (*)(id,SEL,id,id))[super methodForSelector:#selector(sendObjectsPassingTest:withAuthBlock:)];
NSString *reply = getFuncPtr(_weak_self,#selector(sendObjectsPassingTest:withAuthBlock:),predicate,authBlock);
This simply calls the inherited method. Trying to use it with super simply gives an error. At this point I will go ahead and simply use super within the block, and try and profile to see if it leads to any retain cycle.
There are many wrong points here. First, as said above, super is a call on self (there is no such thing as a super object), so it would be sufficient to get the IMP for the method in the superclass, and call it on self.
BUT, [super methodForSelector:... does not get the method in the superclass. It actually gets the method in this class. The super in [super methodForSelector:... affects which methodForSelector: method is called. However, no class ever overrides methodForSelector:, so there is actually no difference between [super methodForSelector:... and [self methodForSelector:.... As said above, super calls the method on self, so it still finds the method based on the class of the current object.
You can get the right IMP by using the class method +instanceMethodForSelector::
NSString *(*getFuncPtr)(id,SEL,id,id) = (NSString * (*)(id,SEL,id,id))[[[self class] superclass] instanceMethodForSelector:#selector(sendObjectsPassingTest:withAuthBlock:)];
However, using the above will not work correctly if the current object is an instance of a subclass, because then [self class] will be the subclass. So to make sure it does what we want, we need to hard-code the name of our current class, or the superclass:
NSString *(*getFuncPtr)(id,SEL,id,id) = (NSString * (*)(id,SEL,id,id))[[SyncArrayMT superclass] instanceMethodForSelector:#selector(sendObjectsPassingTest:withAuthBlock:)];
NSString *reply = getFuncPtr(_weak_self,#selector(sendObjectsPassingTest:withAuthBlock:),predicate,authBlock);
It is also possible to do it using objc_msgSendSuper directly, but that function is not really that easy to use either. So I think you should stick with the IMP approach above.
I have general question about designated initializer. I have a some class and from there i want to call a initializer, but before i started to fill my #properties with passing data i want to make data default. For example:
-(id)initWithDefault:(NSDictionary*)defaultTemplate
{
self = [super init];
_fontColor = [defaultTemplate objectForKey:#"color"];
_fontSize = [[defaultTemplate objectForKey:#"size"] intValue];
return self;
}
-(id)initWithTemplate:(NSDictionary*)template
{
self = [self initWithDefault:myDefaultTemplate];
//now i doing something with my template
return self;
}
Is this is a way to prevent null #properties? It this a correct use of designated initializer? Of course you can assume that myDefaultTemplates is not null, and has not null object in keys.
This seems fine with me. I would use the safe way (presented below), but otherwise your code is fine.
-(id)initWithTemplate:(NSDictionary*)template
{
if(self = [self initWithDefault:myDefaultTemplate]) {
//now i doing something with my template
}
return self;
}
Your implementation is perfectly fine, given the fact that _fontColor and _fontSize variables are your local variables of properties.
adig's suggestion is just an enhancement on what you already have implemented. This check takes care of the situation, when your object does not get allocated due to any reason.
I was reading through the source of core-plot and ran across this code in CPTColor:
-(id)initWithCGColor:(CGColorRef)newCGColor
{
if ( (self = [super init]) ) {
CGColorRetain(newCGColor);
cgColor = newCGColor;
}
return self;
}
-(id)initWithComponentRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha
{
CGFloat colorComponents[4];
colorComponents[0] = red;
colorComponents[1] = green;
colorComponents[2] = blue;
colorComponents[3] = alpha;
CGColorRef color = CGColorCreate([CPTColorSpace genericRGBSpace].cgColorSpace, colorComponents);
[self initWithCGColor:color];
CGColorRelease(color);
return self;
}
If [self initWithCGColor:color]; results in initWithCGColor's superclass assigning a different value to self, will that new value be returned from initWithComponentRed:green:blue:alpha:?
In other words, is self in the two init methods the same variable two distinct variables?
This setup is potentially fragile, and Joe's suggestion, assigning self in initWithComponentRed:... before returning that value will fix it.
Every method gets an argument, self, that points to the instance it was called on. The argument acts just like any other variable; there's nothing special about it. That's why it's possible to assign self, and why it's necessary to have an explicit return in initializers.
When you send [super init], you're using the superclass's version of the init method, but the current instance will still be passed as self. Since initializers in Objective-C are allowed to destroy the object they receive and create and return a new one,* there is a potential for self = [super init] in initWithColor: to not be the same object as that pointed to by self back in initWithComponentRed:....
If that happens, since you don't reassign self to point to that new, correctly-initialized object, you'll be returning an uninitialized object from initWithComponentRed:....
This is only a practical concern if your superclass could possibly do the substitution I described, but there's also no reason, AFAIK, to not do that reassignment.
*Collections do this, for example, because they don't know how big they need to be at alloc time; I believe NSNumber also does this because it has cached values for instances representing small integers.
No if self were to change (ex. if you were to implementing a color cache) initWithComponentRed:green:blue:alpha: would return the wrong object unless the code was changed to:
...
self = [self initWithCGColor:color];
CGColorRelease(color);
return self;
}
self always refers to the same object. It is not a "variable", but a language key word. If one initializer is called after the other, the object is simply altered.
NB: Otherwise, your pour class would have a "split personality" ;-).
This is currently what I have for my init,
- (id)init
{
self = [super init];
if (self) {
self.url = [[NSURL alloc] init];
self.blurb = [[NSString alloc] init];
self.author = [[NSString alloc] init];
}
return self;
}
It does nothing, but I have another method called initWithObject: that will use its argument to fill up the instance variables url, blurb, and author. I don't know what I should be doing with this init. Should I throw an exception? What other options do I have?
If you want to override your standard -init method you could either return nil (if you do not want -init to be used) or do:
- (instancetype)init
{
return [self initWithObject:nil];
}
If you want to stop the use of -init completely you can tag it as an unavailable attribute or use NSAssert:
// .h
// ...
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
// ...
// .m
- (instancetype)init
{
NSAssert(NO, #"This method should not be used");
return nil;
}
You can use either UNAVAILABLE_ATTRIBUTE or NSAssert(), but if you use UNAVAILABLE_ATTRIBUTE you need some kind of implementation of -init, even if it just returns nil.
You don't have to have a plain init—you can simply have your initWithObject: method. If you're doing any basic setup that will remain the same 80% of the time, or if you have some common code in all your initializers, you can override init, but you are not required to.
Also consider changing your initWithObject: name to be more specific, to something like initWithPost: (I'm assuming this is some kind of blog-entry fetcher based on your ivars) so it's more apparent what object is desired.
I think you misinterpreted what you read. I don't think you would throw an exception. You could; however, leak memory. If your initWithObject: method looks like this:
- (id)initWithObject:(id)obj {
if ((self = [self init])) {
self.url=[obj url];
self.blurb=[obj blurb];
self.author=[obj author];
}
return self;
}
And you would be perfectly fine. You could get an exception if your object was instantiated with -init and you used a variable which was assigned, assuming it was real. So in your subsequent methods be sure to check that the objects exist before using them.
If you made your object with -init rather than -initWithObject this could throw an exception:
- (void)dealloc {
[url release];
[blurb release];
[author release];
[super dealloc];
}
The rule that Apple has established for Cocoa programming is that every class must have one initializer method which is the "Designated Initializer". Every other initializer for the class must call that D.I.* The D.I. itself must call the superclass's D.I. Generally, the initializer with the greatest number of arguments (the one that most completely specifies the state of the new object) is the D.I.
In your case, with the bare init, and initWithObject:, the second would likely be the D.I. You would therefore override init to call initWithObject: with some default argument:
- (id) init {
return [self initWithObject:[Object objectWithURL:[NSURL URLWithString:#"http://www.apple.com"]
blurb:#""
author:#""]];
}
This will result in a sort of dummy object, which is correctly initialized with useless data. (Outside of ARC, be sure to watch the memory management of the default argument(s) -- you want to use an autoreleased/unowned object(s).)
*Sometimes an exception is made for initWithCoder:.
If you have any method that you don't want called and that you don't want your subclass to support, throwing an exception in a Debug build is perfectly reasonable.
I'm from the C++ world so the notion of assigning this makes me shudder:
this = new Object; // Gah!
But in Objective-C there is a similar keyword, self, for which this is perfectly acceptable:
self = [super init]; // wait, what?
A lot of sample Objective-C code uses the above line in init routines. My questions:
1) Why does assignment to self make sense (answers like "because the language allows it" don't count)
2) What happens if I don't assign self in my init routine? Am I putting my instance in some kind of jeopardy?
3) When the following if statement fails, what does it mean and what should I do to recover from it:
- (id) init
{
self = [super init];
if (self)
{
self.my_foo = 42;
}
return self;
}
This is a topic that is frequently challenged by newcomers:
Wil Shipley: self = [stupid init];
Matt Gallagher: What does it mean when you assign [super init] to self?
Apple documentation: Implementing Initializers
Cocoa-Dev: self = [super init] debate
Basically, it stems from the idea that a superclass may have over-ridden the designated initializer to return a different object than the one returned from +alloc. If you didn't assign the return value of super's initializer into self, then you could potentially be dealing with a partially initialized object (because the object that super initialized isn't the same object that you're initializing).
On the whole, it's pretty rare for super to return something different, but it does happen in a couple of cases.
In Objective-C, initializers have the option of returning nil on failure or returning a completely different object than the one the initializer was called on (NSArray always does this, for example). If you don't capture the return value of init, the method might be executing in the context of a deallocated object.
Some people disagree about whether you should do the whole assign-to-self rigamarole if you don't expect to get something else back from the superclass initializer, but it's generally considered to be good defensive coding.
And yes, it looks weird.
It is true that init may return nil, if the initialization fails. But this is not the primary reason why you should assign to self when you implement your own initializers.
It has been mentioned before, but it is needed to stress even harder: the instance returned from an initializer may not be the same instance as the one you sent in, in fact it may not even be of the same class!
Some classes use this as a standard, for example all initializer to NSString and NSArray will always return a new instance of a different class. Initializers to UIColor will frequently return a different instance of a specialized class.
And you yourself can happely implement something like this if you want:
-(id)initWithName:(NSString*)name;
{
if ([name isEqualToString:#"Elvis"]) {
[self release];
self = [[TheKing alloc] init];
} else if (self = [super init]){
self.name = name;
}
return self;
}
This allows you to break out the implementation of some special case into a separate class, without requiring the clients of your API to care or even know about it.
All the other points here are valid, but it's important for you to understand as well that self is an implicit parameter to every Objective-C method (objc_msgSend() passes it) and can be written to, just like any other method parameter. (Writing to explicit parameters is generally frowned upon, unless they are out parameters.)
Typically, this is only done in the -init method, for the reasons others have stated. It only has any effect because self is returned from the method and used in the assignment id obj = [[NSObject alloc] init]; It also affects the implicit resolution of ivars, because, for example, if myVar is an ivar of my class, then accessing it in a method causes it to be implicitly resolved to self->myVar.
I'm still new to Objective C, but this post helped me in understanding this.
To sum it up, most init calls return the same object that self is already initialized to. If there is an error, then init will return nil. Also, some objects such as singletons or unique objects (like NSNumber 0) will return a different object than the one initialized (the singleton or a global 0 object). In these situations you need to have self reference that object. I'm by no means an expert in what is going on behind the scenes here, but it makes sense on the surface, to me.
If [super init] returns nil that means that you have been deallocated and your self parameter is now an invalid pointer. By blindly following the self = [super init] convention you will save you from potentially nasty bugs.
Consider the following non-typical initializer:
- (id)initWithParam:(id)param {
if (!param) {
// Bad param. Abort
self = [super init]; // What if [super init] returns nil?
[self release];
return nil;
}
else
{
// initialize with param.
...
}
}
Now what happens if my superclass decides to abort and return nil? I have been de-allocated and my self parameter is now invalid and [self release] will crash. By re-assigning self, I avoid that crash.