Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simple rules for naming methods, compatible with ARC naming conventions

I have difficulties understanding naming conventions of ARC. I have always coded with ARC, and I guess this is the reason.

1. Class methods

  • What name should I choose for the following method?
  • What are the differences, concerning memory management, between theses two names?

This name:

+ (MyObject *)newObjectFrom:(MyObject *)anObject 
                withOptions:(NSDictionary*)options
{
    MyObject * newObject = [anObject copy] ;
    [newObject modifyWith:options] ;
    return newObject ;
}

or this name ?

+ (MyObject *)objectFrom:(MyObject *)anObject
             withOptions:(NSDictionary*)options
{
    MyObject * newObject = [anObject copy] ;
    [newObject modifyWith:options] ;
    return newObject ;
}

2. Instance methods

  • What name should I choose for the following method?
  • What are the differences, concerning memory management, between theses two names?

This name:

- (MyObject *)newObjectwithOptions:(NSDictionary*)options
{
    MyObject * newObject = [self copy] ;
    [newObject modifyWith:options] ;
    return newObject ;
}

or this name?

- (MyObject *)objectwithOptions:(NSDictionary*)options
{
    MyObject * newObject = [self copy] ;
    [newObject modifyWith:options] ;
    return newObject ;
}

2. Simple rules for naming methods

Is there a basic, simple rule to follow when naming methods?

By "basic, simple", I mean

  • a rule similar to "strong when the object belongs to the class", "weak when the object is just referred to by this class, and (thus) owned by another class";

  • (and/or) a rule that does not refer to the memory management without ARC;

  • (and/or) a rule that does not use words such as "autorelease", "release".

like image 291
Colas Avatar asked Dec 15 '22 00:12

Colas


2 Answers

When Rivera said that method names are not important, he probably followed his experience: It always works. And this is correct. And you are correct that you probably do not understand the role of method names because you has always used ARC. So what is the big deal with it?

I added a synopsis to show the problem with MRR at the end of this answer. As you can see there, you do not have to care about naming rules using ARC the way you had to with MRR.

To give you a more detailed explanation, you have to understand what happens using MRR:

Prior to ARC one had to do memory management manually. Doing so he had to know, what kind of ownership a return value has. To make a long story short, one had to know, whether he has to release a returned object:

Rule 1: You do not own an object returned by a method automatically. If you want to hold it, retain it and release it, when you are done with it.

id object = [[object methodThatReturnsAnObject] retain]; // I want to hold it
…
[object release]; // Done with it

id object = [object methodThatReturnsAnObject]; // I do not want to hold it
…
// I do not release it

Doing a deep analysis you can see that there are sometimes problems. (Object deallocation as a side effect.) But this was the basic approach.

The advantage of that was that a retain-release pair could be handled locally (inside a compound statement) and it was easy to follow the ownership of an object.

Rule 2: But when an object was created the first time this could not work: The sender of the message has always to hold it. Otherwise it would be destroyed immediately (= before the sender has the chance to retain it.) So there was a additional rule:

If the name of a class method that returns an object starts with alloc, init, or new you have to handle the returned object as you did a retain on it. Or in one word: Ownership transfer:

id object = [Class allocOrInitOrNewMethod];
…
[object release];

As -retain took the ownership explicitly, alloc–, init…, new… transferred it implicitly.

All other methods behaves like rule 1.

Rule 3: Sometime you need an autorelease pool. To make the advantage of ARPs visible think of this code: (It is useless, just to demonstrate the problem

Case 1.1:

Person *person = [[Person alloc] initWithName:…]; // Ownership transfer, release person, when you are done
NSString *name = [person name]; // the name object is hold by the person object only
[person release]; // I do not need the person object any more
[name doSomething]; // Crash: The person was the only object holding the name

Another problem:

Case 2.1:

Person *person = [[Person alloc] initWithName:…]; // Ownership transfer, release person, when you are done
if (…)
{
   return; // break, continue, goto
}
…
[person release];

Because the last line is never reached, the object is never released.

You can repair that with autoreleasing methods. An object moved to the ARP lives as long as the control flow returns to the run loop. So it lives through every method, through method return and so on. To do it explicitly:

Case 1.2:

Person *person = [[[Person alloc] initWithName:…] autorelease]; // No ownership transfer, persons belongs to the ARP
NSString *name = [person name]; // the name object is hold by the person object only
[name doSomething]; // No Crash: The person object holding the name object is still alive

Case 2.2:

Person *person = [[[Person alloc] initWithName:…] autorelease]; // No ownership transfer, prsons belongs to the AR.
if (…)
{
   return; // break, continue, goto
}
…
// No release necessary.

Because one has to be too lazy to type such a long message chain, convenience allocators has been invented to do this for you:

+ (Person*)personWithName:(NSString*)name
{
   return [[[self alloc] initWithName:name] autorelease];
}

With the result:

Case 2.3:

Person *person = [personWithName:…]; // No ownership transfer, persons belongs to the AR.
if (…)
{
   return; // break, continue, goto
}
…
// No release necessary.

And you can do the same with getters:

- (NSString*)name
{
   return [[_name retain] autorelease];
}

So we at the end of the day we have simple rules:

Rule 1: If you want to keep a returned object in memory retain it – and release it, when you do not need it any more.

Rule 2: If the class method's name starts with alloc, new, or init, think of it as an implicit retain (and therefore release it, when you are done with the object.)

Rule 3: Use -autorelease to delay the deallocation of returned objects for convenience.

Synopsis:

Alloc-init creation

// MRR:
Person *person = [[Person alloc] initWithName:@"Amin"];
…
[person release]; // You create it, you release it

// ARC:
Person *person = [[Person alloc] initWithName:@"Amin"];
…

New creator

// MRR:
Person *person = [[Person newPersonWithName:@"Amin"];
…
[person release]; // You create it, you release it

// ARC:
Person *person = [[Person newPersonWithName:@"Amin"];
…

Convenience allocator

// MRR:
Person *person = [[Person personWithName:@"Amin"]; // Autoreleased
…

// ARC:
Person *person = [[Person personWithName:@"Amin"];
…

As you can see, there is no difference for the three ways of object creation using ARC. So Riviera is right, when he said that this is not important any more. But under the hood the last way is different, because it moves the object to the ARP.

It is the same with the implementation of this methods:

Implementing a new allocator

// MRR
+ (Person*)newPersonWithName:(NSString*)name
{
    return [[self alloc] initWithName:name];
}

// ARC
+ (Person*)newPersonWithName:(NSString*)name
{
    return [[self alloc] initWithName:name];
}

Implementing a convenience allocator

// MRR
+ (Person*)personWithName:(NSString*)name
{
    return [[[self alloc] initWithName:name] autorelease];
}

// ARC
+ (Person*)personWithName:(NSString*)name
{
    return [[self alloc] initWithName:name];
}

Again the implementation for both methods is identical using ARC.

-> You do not need it any of this rules any more. ARC cares four you.

Especially you do not need convenience allocators any more, because there is nothing inconvenient any more. (And even they are optimized at run time, there is still a minimal runtime penalty.) I do not implement convenience allocators any more, but new allocators.

But ARC has to be compatible with MRR. So it remembers all that rules. To make your code readable for others you should repeat this rules, too. But, of course, now the implementation of a convenience allocator and a new allocator is bitwise identical – the rest is done by ARC.

like image 165
Amin Negm-Awad Avatar answered Feb 05 '23 18:02

Amin Negm-Awad


Method names are important. The official documentation of how ARC interprets method names can be found in the clang ARC documentation in the section on method families.

like image 42
Jody Hagins Avatar answered Feb 05 '23 17:02

Jody Hagins