简体   繁体   中英

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".

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 .

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.

Method naming conventions are important when converting the code from MRC to ARC, and when interoperating with MRC code from ARC Code.
Apple guide says that with "ARC code only" the naming conventions are "less important", but this is not 100% true.

For example (and this is only one example, I think that there are many other), look at this project

I swizzled release-autorelease calls to log it, and you can see the difference:

when a method begins with a special naming (for example "new"), ARC returns an object with a retain count of +1, that is balanced by release calls.
When a difference name is used, ARC returns an AUTORELEASED object. (look at the NSLog calls)

This can be a big difference in code that alloc a great number of objects in a loop. In this case, the programmer should create an autorelease pool.

So, it's not true that the naming conventions are less important when using ARC-only code.

I don't know all of the details on naming methods 100% conventionally because there are different situations that apply to each. however I think this article in the Apple docs will help you. https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/Conventions/Conventions.html

First, memory management has nothing to do with the names you choose for your methods or classes. In other words if it compiles and you're not using reserved keywords or critical method names you should be good. (Please refer to Edit note)

As for 1, preceding new is very uncommon in Objective-C, so it's better to use objectFrom: instead.

Even better would be to be more precise about the kind of object you're creating. For example:

[NSArray arrayWithArray:]
[NSString stringWithFormat:]

Or in your case as you create a copy, and supposing you're creating "Client" objects:

[Client clientWithClient:options:]

Where with is not really needed.

For 2 I would choose:

copyWithOptions:

As you're more or less customizing [NSObject copy] . I would also implement only this method and delete the now redundant 1 as less methods is clearer, easier to document, and easier to maintain!

In case of doubt just search the SDK documentation to see how similar methods were named by Apple.

Edit:

In the first paragraph I don't mean to encourage bad naming practices, just to say that they are not the reason for your memory management concerns. Yet you should try to follow the naming conventions pointed in the other answers, or as I said, "do as Apple does".

My experience in the apple world is that factory methods like your examples would usually include 'create' by convention. This is probably less important now with ARC but in the old days 'create' in a method or function signature was a pretty sure fire way to be sure you understood you were taking ownership of the resulting object (owing a release/free() on it) rather than an instance type which you would assume to autorelease [NSArray array] , [UIColor colorWith....] etc, so I still definitely like to follow this convention.

  1. ARC gave semantics to what used to be a naming convention for tracking reference counting responsibilities. So now the compiler uses the same naming patterns to determine whether a method returns a retained object, etc. See the link in @JodyHagens' answer and continue reading with http://clang.llvm.org/docs/AutomaticReferenceCounting.html#semantics-of-method-families

    Methods in the alloc , copy , mutableCopy , and new families — that is, methods in all the currently-defined families except init — implicitly return a retained object as if they were annotated with the ns_returns_retained attribute. This can be overridden by annotating the method with either of the ns_returns_autoreleased or ns_returns_not_retained attributes.

    The " new family" is the message selectors that start with "new" followed by an upper case letter. So there are memory management differences between newObjectFrom:withOptions: and objectFrom:withOptions: , but you can use an annotation to override that (don't go there), and the compiler should complain if you get it wrong.

    (All methods that implement a given message selector had better provide the same reference counting semantics.)

  2. In addition to Apple's article on Conventions that @iRebel_85 points out, Apple has an extensive set of naming guidelines .

  3. Google's style guide adds a few more naming guidelines .

These guidelines are well thought out and useful for making code easier to understand and collaborate on.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM