Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

objective c "Did you forget to nest alloc and init?"

I am just starting climbing the Objective C learning curve (using Nerd Ranch iOS programming book).

Based on what I have know from other languages about "nesting" multiple executions within one line I assumed that I can alter:

NSString* descriptionString = [[NSString alloc] initWithFormat:@"%@", possesionName] 

with a two line version:

NSString* descriptionString = [NSString alloc];
[descriptionString initWithFormat:@"%@", possesionName] 

but it seems that the second attempt raises an exception

2012-01-22 18:25:09.753 RandomPossessions[4183:707] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -length only defined for abstract class.  Define -[NSPlaceholderString length]!'

Could someone help me understand what exactly I am doing wrong here? Thanks a lot in advance.

PS. If this is a way Objective C messages work and you have to make alloc and init in one line just let me know - I assumed this is just a set of functions that either can be executed two in one go or one after another.

like image 989
Piotr Avatar asked Jan 22 '12 17:01

Piotr


2 Answers

The alloc method will allocate memory for a new object. But the init method might throw away that memory and return a completely different object. Or it might return nil. This is why you must always do self = [super init] when you override an init method.

NSString is one class that does this kind of thing all the time.

I'm not exactly sure why the exception is happening, but I believe it could be ARC injecting code in between your two lines of code or something similar. Whatever it is, something is trying to act on the allocated object that has never been initialised, and this is a huge problem that can lead to all kinds of issues. Consider yourself lucky it threw an exception, sometimes it wont.

The NSString class might not actually be a real class. It may contain almost no methods and almost no variables. All it has is a bunch of factory methods to create "real" string objects of some other class, and this is done using methods like initWithFormat:. So, by long standing convention alloc/init must always be done in a single statement and there are a handful of places where, usually for performance reasons, something will rely on this convention being used.

Basically, objective-c is a language where you don't need to know exactly what is going on inside an object. You just need to know what messages can be sent to an object, and how it will respond. Anything else is undefined behaviour and even if you learn how it works, it is subject to change without notice. Sometimes the behaviour will change depending on circumstances that are completely illogical, for example you might expect the "copy" method to give you a copy of the object you send it to, and while this is the default behaviour, there are many cases where it will actually just return the same object with slightly different memory management flags. This is because the internal logic of the class knows that returning the same object is much faster and effectively identical to returning an actual copy.

My understanding is copy sent to NSString may return a new object, or it may return itself. It depends on which NSString subclass is actually being used, and there isn't even any documentation for what subclasses exist, let alone how they're implemented. All you need to know, is that copy will return a pointer to an object that is perfectly safe to treat as if it was a copy even though it might not be.

In a "proper" object oriented language like Objective-C, objects are "black boxes" which can intelligently change their internal behaviour at any time for any reason, but their external behaviour always remains the same.

With regard to avoiding nesting... The coding style for Objective-C often does require extensive nesting, or else you'll be writing 10 lines of code when only 1 is really needed. The square brace syntax is particularly suited to nesting without making your code messy.

As a rule of thumb, I turn on Xcode's "Page Guide at column" feature, and set it to 120 characters. If the line of code exceeds that width then I'll think about breaking it into multiple lines. But often it's cleaner to have a really long line than three short lines.

Be pragmatic about it. :)

like image 37
Abhi Beckert Avatar answered Oct 08 '22 06:10

Abhi Beckert


An important difference between both versions (they are not exactly equal) is that in the first version you use the result of initWithFormat for the variable descriptionString, while you use the result of alloc in the second. If you change your code to

NSString* descriptionString = [NSString alloc];
descriptionString = [descriptionString initWithFormat:@"%@", possesionName] 

all should be well again. It is specified that an object returned by alloc shall not be seen as initialized and functional until some init Method has been called and init might return something else.

like image 63
Dennis Bliefernicht Avatar answered Oct 08 '22 05:10

Dennis Bliefernicht