PDA

View Full Version : Why no alloc init?




chrono1081
Dec 26, 2011, 12:47 PM
Hi guys,

Did something change in Objective-C I am un-aware of?

I was helping my friend work through the Big Nerd Ranch Objective-C book and look at this code:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[])
{

@autoreleasepool {

NSDate *now = [NSDate date]; //Why not NSDate *now = [[NSDate alloc] init]; ? Does this mean ARC is turned on?

NSLog(@"The new date lives at %@", now);

}
return 0;
}



I go in an out of using Objective-C but I could have sworn you used to have to write something like this:

NSDate *now = [[NSDate alloc] init];
[NSDate date];

I know the syntax for autoreleasepool is different now too (not sure why) but did something change here? Should I go out and update my Objective-C books?



marc0015
Dec 26, 2011, 01:15 PM
+ (id)date

Creates and returns a date initialised to the current date and time.

Edit: the title should have read "NSDate - class methods"

chrono1081
Dec 26, 2011, 01:30 PM
+ (id)date

Creates and returns a date initialised to the current date and time.

Edit: the title should have read "NSDate - class methods"

Ok I think I see what you mean.

If its a class method I can call it like:

NSDate *myDate = [NSDate date];

but if I want to just create an instance of the class itself I can call:

NSDate *myDate = [[NSDate alloc] init];

Is that correct? (Sorry I am way more familiar with C++ than Objective-C).

GorillaPaws
Dec 26, 2011, 02:35 PM
Ok I think I see what you mean.

If its a class method I can call it like:

NSDate *myDate = [NSDate date];

but if I want to just create an instance of the class itself I can call:

NSDate *myDate = [[NSDate alloc] init];

Is that correct? (Sorry I am way more familiar with C++ than Objective-C).

You are correct. It's a common pattern to have factory methods that will return new autoreleased instances of a class initialized with some commonly desired behavior. In this case the +date class method of NSDate will create an autoreleased NSDate instance initialized to the current date/time.

There's nothing wrong with:

NSDate *myDate = [[NSDate alloc] init];

except it's slightly more verbose. Also the former makes it clearer that you intend the current date. I had to look up the docs to see that the default behavior of init is also to return the current date. The former approach is slightly more declarative.

EDIT: After re-reading your question, it seems you still might be confused about instances. Both approaches are creating instances of NSDate. The +alloc method is a class method that allocates memory for an instance of NSDate, and the -init method is an instance method that initializes the instance returned by +alloc to the current date. Using the class method +date will do the same thing, it will also return an instance of NSDate the only difference is that the instance it returns is not owned by you. As per the memory guidelines, with the first example, you would have to later release the object created with the init method if you weren't using ARC.

Using NSNumbers might make the distinction more obvious:

NSNumber *firstNumber = [[NSNumber alloc] initWithDouble: 1.0];
NSNumber *secondNumber = [NSNumber numberWithDouble: 1.0];

if( [firstNumber isEqualToNumber: secondNumber] )
{
NSLog( @"This should print" );
}

[firstNumber release]; // only if this code isn't ARC


Both are doing the same thing, but secondNumber is not owned by you while firstNumber has a retain count of 1. In all other respects, they are identical.

chrono1081
Dec 26, 2011, 04:44 PM
Thanks so much! It makes sense now. I'm still trying to get used to this ARC thing.

The last time I used Objective-C significantly I had to worry about retain counts ;)

I also have NO clue how I missed this in the documentation:

"init
Returns an NSDate object initialized to the current date and time.

- (id)init"

doh! :o

foidulus
Dec 26, 2011, 05:21 PM
I would also add that not all factory methods actually create (i.e. call the static alloc method) a new object. For instance to get an instance of the current thread, you don't actually use

NSThread *bob=[[NSThread alloc] init] and then set the thread to the current thread, instead you use:

NSThread *bob=[NSThread currentThread]

You have an NSThread instance, but nothing was added to the heap

Sydde
Dec 26, 2011, 07:32 PM
I would also add that not all factory methods actually create (i.e. call the static alloc method) a new object.

Other examples would include class methods from NSUserDefaults, NSFileManager, NSWorkspace, NSFontPanel, etc, etc, which return a static object. I seem to recall that there are a handful of predefined NSNumber constants which do not respond to -release. And with this code
NSString *aString = [NSString stringWithString:@"wordswordswords"];
I would not rely on the returned string being a different object from the string constant and may not respond to -release.

admanimal
Dec 26, 2011, 07:45 PM
Other examples would include class methods from NSUserDefaults, NSFileManager, NSWorkspace, NSFontPanel, etc, etc, which return a static object. I seem to recall that there are a handful of predefined NSNumber constants which do not respond to -release. And with this code
NSString *aString = [NSString stringWithString:@"wordswordswords"];
I would not rely on the returned string being a different object from the string constant and may not respond to -release.

They will still respond to release...but it just might not do anything.

Sydde
Dec 26, 2011, 08:16 PM
They will still respond to release...but it just might not do anything.

Sorry, poor wording on my part.

chrono1081
Dec 26, 2011, 08:25 PM
Thank you so much guys for the extra tips!

Unfortunately my mind is shot from working on a Maya plugin all day so I will have to try and make sense of everything tomorrow after sleep :o

jiminaus
Dec 26, 2011, 11:03 PM
I feel there's some terminology confusion happening here.

NSDate's date is called a convenience constructor. A convenience constructor is a factory method, but not all factory methods are convenience constructors. Convenience constructors have names that begin with the name of class minus any prefix (or the name of a parent class in the case of inherited convenience constructors.

Examples include string in NSString, stringWithFormat: in NSString, and string in NSMutableString. The last example is an example of an inherited convenience constructor.

By convention, convenience constructors are notionally equivalent to an auto-released alloced/inited object. For example, [NSDate date] is notionally equivalent to [[[NSData alloc] init] autorelease].

Convenience constructors typically return unique objects. But actually the convention is more that the client code should be able to treat the returned object as being unique. A convenience constructor can return a shared object if has no effect on the calling code. For example, [NSString string] could always returned the same empty string object. But it can only do that because NSString is immutable. [NSMutableString string] however would need to return a unique object each time it's called.

In regards to NSString's stringWithString:, it could simply return the passed in object if and only if the passed in object is actually an NSString object. If the passed in object was actually an NSMutableString, then it would need to return an immutable copy of that passed in object. In fact, I would rely on this behaviour of NSString's stringWithString:. This way you can code defensively while only incurring the runtime and memory overhead when necessary.

While [NSThread currentThread] could be called a factory method, it cannot be called a convenience constructor. So it is free to return a non-unique object.



They will still respond to release...but it just might not do anything.

It would be wrong to call release on objects returned by [NSFileManager defaultManager] for example. It would be a violation of the memory-management rules. The behaviour of calling release on these objects cannot be relied upon. I would rely on it causing the program to crash, eventually.

GorillaPaws
Dec 26, 2011, 11:37 PM
A convenience constructor is a factory method, but not all factory methods are convenience constructors.

I thought they were synonyms. Thanks for sorting me out, and sorry for any confusion I may have caused.

admanimal
Dec 27, 2011, 09:02 PM
It would be wrong to call release on objects returned by [NSFileManager defaultManager] for example. It would be a violation of the memory-management rules. The behaviour of calling release on these objects cannot be relied upon. I would rely on it causing the program to crash, eventually.

I wasn't saying anything about whether it is correct to call release on any particular object, only that the objects in question will respond to release, i.e. that they have a release method defined for them.

jiminaus
Dec 28, 2011, 12:49 AM
I wasn't saying anything about whether it is correct to call release on any particular object, only that the objects in question will respond to release, i.e. that they have a release method defined for them.

Odd thing to say then. All objective-c objects will respond to release because it's declared in the NSObject protocol that all objective-c objects must conform to.

Sydde
Dec 28, 2011, 10:00 PM
It would be wrong to call release on objects returned by [NSFileManager defaultManager] for example. It would be a violation of the memory-management rules. The behaviour of calling release on these objects cannot be relied upon. I would rely on it causing the program to crash, eventually.

Actually, IIRC, calling release on a singleton object would not result in an eventual crash. In Apple's guidelines for creating singletons, I think they recommend overriding -release in order to protect the singleton, but allow any others to be released (in case, for example, code were to attempt to create the object with alloc/init). It is still wrong to release a singleton, but probably not potentially disastrous.

How they make this work correctly with ARC is something I have yet to investigate presumably the compiler will make the correct inference for the static/self assignment in the -init method. EDIT: Silly me, obviously overriding +allocWithZone: prevents memory leaks from arising in the first place.