The Code Commandments: Best Practices for OCCoding
2019獨角獸企業重金招聘Python工程師標準>>>
Preface
I don’t normally post highly technical stuff to my blog, but here’s an exception that I hope will benefit the Mac and iOS (iPhone & iPad) developer community. So if you’re not part of that, feel free to skip it.
Introduction
This article is a cumulative list of the most commonly-violated best practices for Objective-C coders that I’ve witnessed in my many years of experience with the language. I call them “commandments” because there are many good reasons to observe them and few, if any valid reasons not to. However, when I show these practices to other developers, they frequently raise one big objection…
The Big Objection: Won’t That Hurt Performance?
This is almost never a legitimate objection. If you can show me any case where code written in conformance to any of the following policies has become?the?performance bottleneck, you have my blessing to optimize that code with an exception to the policy, as long as?how?you’re breaking the policy and?why?are?clearly documented in the code.
WHY?
These policies target code safety, clean memory management, understandability, and maintainability. Most of them are?not?performance enhancers. Nonetheless, writing code with performance in mind is never the way to go first. Always write clean, correct code and then optimize only when and where profiling indicates you need it. In a typical UI-driven application, the single biggest bottleneck is almost always the user, followed by network access, and then disk access. Nonetheless,?premature optimization?is rampant among programmers who are concerned that if they don’t use every optimization trick they’ve been taught, their application will be dog slow. This simply isn’t true.
Important General Advice
Even if you understand how your code operates when you write it, that doesn’t mean others will understand it so completely before they attempt to modify it, and it doesn’t mean that you will understand it yourself when you revisit it months later.?ALWAYS?strive to make your code self-documenting by using verbose symbol names and add detailed comments in sections of code where meaning is not obvious despite clear and descriptive symbols. Any time you need to work around a bug or misfeature in an API you are using, isolate your workaround to a single method, explain your workaround and put a keyword like?KLUDGE?in your comments so you can easily find and fix these trouble spots later.
Things This Article Doesn’t Cover (Yet)
Key Value Coding (KVC) and Key Value Observing (KVO). You should use them.
Added April 26, 2012
THOU SHALT…
ALWAYS?use?Automatic Reference Counting?(ARC). This article has been revised to remove references to pre-ARC code style. All new code should be written using ARC, and all legacy code should be updated to use ARC (or kept in a very tight box with a pretty API covering it up.)
THOU SHALT…
ALWAYS?use the @private directive before any instance variable declarations, andNEVER?access instance variables directly from outside the class.
WHY?
Preserves information hiding (encapsulation)
Limits to methods in the class implementation cases where instance variables are accessed directly.
The default privacy for instance variables is @protected , which means subclasses can access these instance variables freely. But there has to be a very good reason to allow subclasses to do this— everything that a superclass exposes to the outside world becomes part of its contract, and the ability to change the internal representation of a class’s data without changing its API or contract is an important benefit of object-oriented encapsulation. By keeping your instance variables private, you make it clear that they are implementation details and not part of your class’s API.
BAD
@interface ?Foo?:?NSObject?{
? ? ? ??int?a;
? ? ? ??NSObject*?b;
? ? ? ??// …
}
// method & property declarations…
@end
BETTER
@interface ?Foo?:?NSObject?{
? ? ? ??@private
? ? ? ??int?a;
? ? ? ??NSObject*?b;
? ? ? ??// …
}
// method & property declarations…
@end
THOU SHALT…
ALWAYS?create a @property for every data member and use “self.name” to access it throughout your class implementation.?NEVER?access your own instance variables directly.
WHY?
Properties enforce access restrictions (such as readonly)
Properties enforce memory management policy (strong, weak)
Properties provide the opportunity to transparently implement custom setters and getters.
Properties with custom setters or getters can be used to enforce a thread-safety strategy.
Having a single way to access instance variables increases code readability.
BAD
@interface ?Foo?:?NSObject?{
? ? ? ??@private
? ? ? ??NSObject*?myObj;
}
@end
?
@implementation?Foo
-?(void)bar?{
? ? ? ? myObj?=?nil;
}
@end
BETTER
@interface ?Foo?:?NSObject?{
? ? ? ??@private
? ? ? ??NSObject*?myObj;
}
?
@property(strong, nonatomic)?NSObject*?myObj;
?
@end
?
@implementation?Foo
-?(void)bar?{
? ? ? ? self.myObj?=?nil;
}
@end
THOU SHALT…
ALWAYS?use the “nonatomic” attribute on your properties, unless you are writing a thread-safe class and actually need access to be atomic, and then?put a comment in the code that this was your intent.
WHY?
Classes with properties that aren’t declared “nonatomic” may give the impression that they were designed with thread-safety in mind, when in fact they weren’t. Only drop the “nonatomic” qualifier from properties when you have actually designed the class for thread-safety.
BAD
@interface ?Foo?:?NSObject
?
@property(strong)?NSObject*?myObj;
?
@end
BETTER
@interface ?Foo?:?NSObject
?
@property(strong, nonatomic)?NSObject*?myObj;
?
@end
BETTER
// This class and all it’s properties are thread-safe.
@interface ?Foo?:?NSObject
?
@property(strong)?NSObject*?myObj;
?
@end
THOU SHALT…
NEVER?allow your instance variable names to be confused with property names, or with data member names.?ALWAYS?end your instance variable names with an underscore.UNLESS?you are subclassing a 3rd-party class which already has a data member of the same name, in which case pick another non-similar name or add another underscore andput a comment in as to why you did this.
Use “@synthesize name = name_;” in your implementation, instead of just “@synthesize name;”
WHY?
Even within the class implementation you have to have a very good reason to access a data member directly, instead preferring property accessors.
Apple uses “_name” for their private instance variables, and by using “name_” for yours, you avoid naming conflicts.
Apple has begun to use the “name_” convention in code examples and templates that demonstrate developer-level code.
BAD
@interface ?Foo?:?NSObject
?
@property(strong, nonatomic)?NSObject*?myObj;
?
@end
?
// …
?
@implementation?Foo
?
@synthesize?myObj;
?
@end
BETTER
@interface ?Foo?:?NSObject
?
@property(strong, nonatomic)?NSObject*?myObj;
?
@end
?
// …
?
@implementation?Foo
?
@synthesize?myObj?=?myObj_;
?
@end
THOU SHALT…
NEVER?redundantly add the data member to the class @interface yourself. Allow the @synthesize directive to implicitly add the data member.
Following this practice will yield many classes that explicitly declare no instance variables in the @interface section. When a class has no instance variables, omit the @private declaration, and even omit the opening and closing braces of the data member section.
WHY?
Reduces redundancy.
Simplifies class headers.
Avoids the need for forward class declarations in public headers merely so you can declare members of classes that are only actually used in the implementation.
BAD
@interface ?Foo?:?NSObject?{
? ? ? ??@private
? ? ? ??NSObject*?myObj_;
}
?
@property(strong, nonatomic)?NSObject*?myObj;
?
@end
?
// …
?
@implementation?Foo
?
@synthesize?myObj?=?myObj_;
?
@end
BETTER
@interface ?Foo?:?NSObject
?
@property(strong, nonatomic)?NSObject*?myObj;
?
@end
?
// …
?
@implementation?Foo
?
@synthesize?myObj?=?myObj_;
?
@end
You may still need to declare the underlying name of the variable if you need to access it directly, as when writing custom getters and setters:
WON’T WORK
@interface ?Foo?:?NSObject
?
@property(strong, nonatomic)?NSObject*?myObj;
?
@end
?
// …
?
@implementation?Foo
?
@synthesize?myObj;
?
-?(NSObject*)myObj
{
? ? ? ??return?self.myObj;?// recursive call to this getter!
}
?
-?(void)setMyObj:(NSObject*)myObj
{
? ? ? ? self.myObj?=?myObj;?// recursive call to this setter!
}
?
@end
WILL WORK
@interface ?Foo?:?NSObject
?
@property(strong, nonatomic)?NSObject*?myObj;
?
@end
?
// …
?
@implementation?Foo
?
@synthesize?myObj?=?myObj_;
?
-?(NSObject*)myObj
{
? ? ? ??return?myObj_;?// No problem.
}
?
-?(void)setMyObj:(NSObject*)myObj
{
? ? ? ??// no problem
? ? ? ? myObj_?=?myObj;?// do the assignment (ARC handles any necessary retaining and releasing)
}
?
@end
THOU SHALT…
NEVER?access a data member through an underscore-suffixed symbol UNLESS you are writing a setter or getter.
BAD
@interface ?Foo?:?NSObject
?
@property(strong, nonatomic)?Bar*?myObj;
?
@end
?
// …
?
@implementation?Foo
?
@synthesize?myObj?=?myObj_;
?
-?(void)someMethod
{
? ? ? ? myObj_?=?[[Bar alloc]?init];
}
?
@end
BETTER
@interface ?Foo?:?NSObject
?
@property(strong, nonatomic)?Bar*?myObj;
?
@end
?
// …
?
@implementation?Foo
?
@synthesize?myObj?=?myObj_;
?
-?(void)someMethod
{
? ? ? ? self.myObj?=?[[Bar alloc]?init];
}
?
@end
THOU SHALT…
NEVER?declare internal (private) methods or properties in the class header files.?ALWAYSput all internal method and property declarations into a “class extension” in the implementation file.
BAD
//
// Foo.h
//
?
@interface ?Foo?:?NSObject
?
@property(nonatomic)?int?myPublicProperty;
@property(strong, nonatomic)?Bar*?myPrivateProperty;?// This can be accessed by anyone who includes the header
?
-?(int)myPublicMethod;
-?(int)myPrivateMethod;?// So can this.
?
@end
BETTER
//
// Foo.h
//
?
@interface ?Foo?:?NSObject
?
// Only the public API can be accessed by including the header
?
@property(nonatomic)?int?myPublicProperty;
?
-?(int)myPublicMethod;
?
@end
//
// Foo.m
//
?
@interface ?Foo?()?// This is a "class extension" and everything declared in it is private, because it’s in the implementation file
?
@property(strong, nonatomic)?Bar*?myPrivateProperty;
?
-?(int)myPrivateMethod;
?
@end
?
@implementation?Foo
// …
@end
THOU SHALT…
NEVER?use more than one return-statement in a method, and only then as the last statement of a method that has a non-void value-type.
In methods that have a non-void value-type, declare a variable for the value as the very first statement, and give it a sensible default value. Assign to it in code paths as necessary. Return it in the last statement.?NEVER?return it prematurely using a return-statement.
WHY?
Premature return-statements increase the possibility that some sort of necessary resource deallocation will fail to execute.
BAD
@implementation?Foo
?
-?(Bar*)barWithInt:(int)n
{
? ? ? ??// Allocate some resource here…
?
? ? ? ??if(n?==?0)?{
? ? ? ? ? ? ? ??// …and you have to deallocate the resource here…
? ? ? ? ? ? ? ??return?[[Bar alloc]?init];
? ? ? ??}?else?if(n?==?1)?{
? ? ? ? ? ? ? ??// …and here…
? ? ? ? ? ? ? ??return?self.myBar;
? ? ? ??}
?
? ? ? ??// …and here.
? ? ? ??return?nil;
}
?
@end
BETTER
@implementation?Foo
?
-?(Bar*)barWithInt:(int)n
{
? ? ? ? Bar*?result?=?nil;
?
? ? ? ??// Allocate some resource here…
?
? ? ? ??if(n?==?0)?{
? ? ? ? ? ? ? ? result?=?[[Bar alloc]?init];
? ? ? ??}?else?if(n?==?1)?{
? ? ? ? ? ? ? ? result?=?self.myBar;
? ? ? ??}
?
? ? ? ??// …and deallocate the resource here, you’re done!
?
? ? ? ??return?result;
}
?
@end
THOU SHALT…
Understand what autorelease pools are for, when they are created and destroyed for you, and when to create and destroy your own.
Automatically by NSRunLoop on each pass
Automatically by NSOperation
By you, at the start and end of threads
By you, whenever you must create and release a large number of objects before you’ll cede control back to the run loop.
Under ARC, you create autorelease pools with the @autoreleasepool { … } construct.
THOU SHALT…
ALWAYS?prefer class-level convenience constructors over init-constructors. All of the foundation framework container classes provide these.
BAD
NSMutableDictionary*?dict?=?[[NSMutableDictionary?alloc]?init];
BETTER
NSMutableDictionary*?dict?=?[NSMutableDictionary?dictionary];
THOU SHALT…
ALWAYS?offer class-level convenience constructors in the API of the classes you write.
WHY?
Clients of your class can reap the benefits of being able to keep the previous commandment.
BAD
@interface ?Foo?:?NSObject
?
-?(id)initWithBar:(int)bar baz:(int)baz;
?
@end
BETTER
@interface ?Foo?:?NSObject
?
-?(id)initWithBar:(int)bar baz:(int)baz;
?
+?(Foo*)fooWithBar:(int)bar baz:(int)baz;
?
@end
THOU SHALT…
ALWAYS?understand when object ownership transfers happen. ARC handles a lot of this for you, but you should still be generally knowledgable of what is going on behind the scenes.
Common ways of taking posession of an object:
New objects are owned by you when you call +alloc on a class.
New objects are owned by you when you call -copy or -mutableCopy on an instance.
You become an owner of an existing object when you assign it to a property with the (retain) or (strong) attribute.
Common ways of releasing ownership an object:
Assign another object (or nil) to a property with the (strong) attribute.
Let an owning local variable go out of scope.
Another object holding a reference to the object is destroyed.
THOU SHALT…
ALWAYS?understand the memory management policies of your properties and instance variables, particularly when writing custom getters and setters. Make sure your instance variables have the correct storage policies declared on them in order to let ARC do it’s job. The easiest way to do this is simply to use @synthesize and then override the generated setter and/or getter to access the underlying instance variable, which will have the same storage policy as that declared by the property.
@interface ?bar
?
@property?(strong, nonatomic)?id?foo;
?
@end
?
?
@implementation?bar
?
@synthesize?foo?=?foo_;
?
-?(id)foo
{
? ? ? ??return?foo_;
}
?
-?(void)setFoo:(id)foo;
{
? ? ? ? foo_?=?foo; ?// Retained/released automatically by ARC because of (strong) attribute on @property above
? ? ? ??[self syncToNewFoo]; ?// The reason for our custom setter
}
?
@end
THOU SHALT…
NEVER?have a -dealloc method if your class unless it must release special resources (close files, release memory acquired with malloc(), invalidate timers, etc.) ARC should be allowed to take care of everything else.
THOU SHALT…
ALWAYS?write a custom getter for a property where you have a custom setter, and vice versa.
WHY?
Getters and setters need to have symmetrical behavior about memory management, thread safety, and any other side effects they create. You should not rely on a synthesized getter or setter having the proper symmetrical behavior to any custom getter or setter you write. Therefore, if you’re going to provide either the getter or setter yourself, you should provide both.
THOU SHALT…
ALWAYS?write a custom setter for a property, and assign that property to nil in your -dealloc method, if there’s something you must?ALWAYS?do when you release an object (such as invalidating a timer).
BAD
@implementation?Foo
?
@synthesize?myTimer;
?
-?(void)dealloc
{
? ? ? ? self.myTimer?=?nil;?// Timer not invalidated, we could get called back if the timer fires after we’re dealloced!
}
?
@end
BETTER
@implementation?Foo
?
@synthesize?myTimer?=?myTimer_;
?
-?(NSTimer*)myTimer
{
? ? ? ??return?myTimer_;
}
?
-?(void)setMyTimer:(NSTimer*)myTimer
{
? ? ? ??[myTimer_ invalidate];
? ? ? ? myTimer_?=?myTimer;
}
?
-?(void)dealloc
{
? ? ? ? self.myTimer?=?nil;?// Timer guaranteed not to fire after we’re gone! Still necessary under ARC.
}
?
@end
WHY?
ARC takes care of releasing your (strong) or (retain) instance variables at -dealloc time, but it does it directly, rather than by calling your custom setters. So if your custom setters have other side effects (like invaliding timers) you must still make sure to invoke them yourself.
THOU SHALT…
When writing constructors,?ALWAYS?minimize or eliminate the amount of code that executes before [super init] is called.
WHY?
In general, the call to the superclass’ constructor may fail, causing it to return nil. If that happens, then any initialization you do before the call to the super constructor is worthless or worse, has to be undone.
BAD
@implementation?Foo
?
-?(id)initWithBar:(Bar*)bar
{
? ? ? ??[bar someMethod];
? ? ? ??// other pre-initialization here
? ? ? ?
? ? ? ??if(self?=?[super init])?{
? ? ? ? ? ? ? ??// other initialization here
? ? ? ??}?else?{
? ? ? ??// oops! failed to initialize super class
? ? ? ??// undo anything we did above
}
?
? ? ? ??return?self;
}
?
@end
BETTER
@implementation?Foo
?
-?(id)init
{
? ? ? ??if(self?=?[super init])?{
? ? ? ? ? ? ? ??// minimal initialization here
? ? ? ??}
?
? ? ? ??return?self;
}
?
// Other methods that put a Foo into a usable state
?
@end
THOU SHALT…
When writing a UIViewController and not using a nib file,?ALWAYS?create and setup your view hierarchy in -loadView, and never in -init. Your implementation of -loadView is the ONLY place you should ever assign to the view attribute.
THOU SHALT…
NEVER?call -loadView yourself! The view attribute of a UIViewController loads lazily when it’s accessed. It can also be automatically purged in a low-memory situation, so?NEVERassume that a UIViewController’s view is going to live as long as the controller itself.
THOU SHALT…
ALWAYS?set up the views you need once, then show, hide, or move them as necessary.NEVER?repeatedly destroy and recreate your view hierarchy every time something changes.
THOU SHALT…
NEVER?call -drawRect on a UIView yourself. Call -setNeedsDisplay.
THOU SHALT…
ALWAYS?avoid long compound operations in your code. Local variables are your friend.
BAD
NSMutableDictionary*?listDict?=?[[NSMutableDictionary?alloc]initWithDictionary:[[NSUserDefaults?standardUserDefaults]objectForKey:@"foo"]];
BETTER
NSUserDefaults*?defaults?=?[NSUserDefaultsstandardUserDefaults];
NSDictionary*?dataModelDict?=?[defaults objectForKey:@"foo"];
NSMutableDictionary*?listDict?=?[NSMutableDictionarydictionaryWithDictionary:dataModelDict];
WHY?
Self-documenting
Easier to understand
Easier to step through in debugger
THOU SHALT…
NEVER?use method names like -drawUI when you aren’t really drawing anything.ALWAYS?prefer names like -setupUI or -resetUI, especially if there’s the possibility the method can be called more than once.
THOU SHALT…
NEVER?repeat yourself. Have a single, authoritative location for each piece of information or functionality, and deploy it throughout the application, even when it means touching others’ code.?NEVER?program via copy-and-paste.
Updated October 15, 2010
THOU SHALT…
NEVER?call -stringWithString UNLESS you:
Are converting an NSString to an NSMutableString.
Are converting an NSMutableString to an NSString.
Need to guarantee that an NSString* you’ve been handed is really immutable
Really need a copy of an NSMutableString because you plan to modify them separately.
WHY?
NSStrings are immutable. You should never have to copy an NSString unless you want a mutable copy, or guarantee that an NSString pointer you want to save is not already an NSMutableString (and thus might have its value changed by some other code later.) In fact, attempts to copy NSStrings simply bump up the string’s retain count and return the original string.
[/code]
Added April 26, 2012
THOU SHALT...
ALWAYS?use the (copy) storage class for properties that receive objects (like NSStrings or NSArrays) that have mutable subclasses.
WHY?
The (copy) storage class of properties (as well as the -copy method) always make IMMUTABLE copies of these objects. So you can rely on the immutability of a copy, while you cannot rely on the immutability of the original. This is also a promise to the caller that you will not mutate objects that are passed to you this way that happen to be only incidentally mutable.
THOU SHALT...
When overriding a method in a superclass,?ALWAYS?call the superclass' implementation,even if?you know that super's implementation does nothing,?unless?you have a very good reason to not call super's implementation, in which case you?must?document your reason in the comments.
WHY?
Super's implementation might not?ALWAYS?do nothing in the future.
Other classes may eventually interpose themselves between your class and the superclass, with non-empty implementations.
Makes your code more self-documenting in that it is not possible to call a super implementation unless the method is an override.
BAD
@implementation?Foo
?
-?(void)awakeFromNib
{
? ? ? ??// do my setup
}
?
@end
BETTER
@implementation?Foo
?
-?(void)awakeFromNib
{
? ? ? ??[super awakeFromNib];
?
? ? ? ??// do my setup
}
?
@end
Added October 15, 2010
THOU SHALT...
In conditional statements,?NEVER?treat pointers or numerical values as booleans.
WHY?
Booleans have two values: true and false (by convention in Objective-C we use YES and NO.) Pointers, on the other hand, have a value that is an object, or nil. Numerical values have values that are some number, or zero. While zero and nil evaluate to boolean false in a conditional context, this is a form of implicit type coercion that can make the meaning of your code more difficult to understand. So explicitly test for nil when dealing with pointers, 0 when dealing with integers, 0.0 when dealing with floating point values, etc.
BAD
-?(void)fooWithBar:(Bar*)bar baz:(BOOL)baz quux:(float)quux
{
? ? ? ??if(bar?&&?baz?&&?quux)?{
? ? ? ? ? ? ? ??// do something interesting
? ? ? ??}
}
BETTER
-?(void)fooWithBar:(Bar*)bar baz:(BOOL)baz quux:(float)quux
{
? ? ? ??if(bar?!=?nil?&&?baz?&&?quux?!=?0.0)?{
? ? ? ? ? ? ? ??// do something interesting
? ? ? ??}
}
Added October 15, 2010
THOU SHALT...
NEVER?use conditional statements to check for nil when you don't have to. In particular, understand that in Objective-C, the act of calling any method on a nil object reference is a no-op that returns zero (or NO, or nil), and write your code accordingly.
BAD
-?(void)setObj:(NSObject*)obj
{
? ? ? ??if(obj_?!=?nil)?{
? ? ? ? ? ? ? ??[obj_ doCleanup];
? ? ? ??}
? ? ? ??if(obj?!=?nil)?{
? ? ? ? ? ? ? ??[obj doSetup];
? ? ? ??}
? ? ? ? obj_?=?obj;
}
BETTER
-?(void)setObj:(NSObject*)obj
{
? ? ? ??[obj_ doCleanup];?// Does nothing if obj_ == nil
? ? ? ??[obj doSetup];?// Does nothing if obj == nil
? ? ? ? obj_?=?obj;
}
轉載于:https://my.oschina.net/w11h22j33/blog/205795
總結
以上是生活随笔為你收集整理的The Code Commandments: Best Practices for OCCoding的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jQuery Mobile页面返回无需重
- 下一篇: (动态,静态)(解释,编译)(强类型,弱