Objective-C coding standards: 26 aspects to solve iOS development problems

Introduction

The reason we developed the Objective-C coding standard is that we can keep the code elegant and consistent in our books, tutorials and starter kits. Even if we have many different authors to complete different books.

The coding standard here may be different from other Objective-C coding standards you have seen, because it is mainly for printing and web legibility.

About the author

The creation of this coding standard was completed by many team members from raywenderlich.com under the leadership of Nicholas Waynik. The team members are: Soheil Moayedi Azarpour, Ricardo Rendon Cepeda, Tony Dahbura, Colin Eberhardt, Matt Galloway, Greg Heo, Matthijs Hollemans, Christopher LaPollo, Saul Mora, Andy Pereira, Mic Pringle, Pie Todo Rea, Marisare Roc And Ray Wenderlich.

We are also very grateful to the authors of New York Times and Robots & Pencils' Objective-C coding standards. These two coding standards provide a good starting point for the creation of this guide.

background

There are some official Apple documents about coding style. If something is not mentioned, you can find more details in the following documents:

Language

US English should be used.

should:

UIColor *myColor = [UIColor whiteColor];  

Should not:

UIColor *myColour = [UIColor whiteColor];  

Code organization

Use #pragma mark-to classify methods in function grouping and protocol/delegate implementation, following the general structure:

#pragma mark - Lifecycle  
- (instancetype)init {}  
- (void)dealloc {}  
- (void)viewDidLoad {}  
- (void)viewWillAppear:(BOOL)animated {}  
- (void)didReceiveMemoryWarning {}  
#pragma mark - Custom Accessors  
- (void)setCustomProperty:(id)value {}  
- (id)customProperty {}  
#pragma mark - IBActions  
- (IBAction)submitData:(id)sender {}  
#pragma mark - Public  
- (void)publicMethod {}  
#pragma mark - Private  
- (void)privateMethod {}  
#pragma mark - Protocol conformance  
#pragma mark - UITextFieldDelegate  
#pragma mark - UITableViewDataSource  
#pragma mark - UITableViewDelegate  
#pragma mark - NSCopying  
- (id)copyWithZone:(NSZone *)zone {}  
#pragma mark - NSObject  
- (NSString *)description {}  

Space

  • Use 4 spaces for indentation, make sure to set it in Xcode preferences. (Raywenderlich.com uses 2 spaces)
  • Method braces and other braces (if/else/switch/while, etc.) are always opened in the same line but closed in a new line.

should:

if (user.isHappy) {  
    //Do something  
} else {  
    //Do something else  
}  

Should not:

if (user.isHappy)  
{  
  //Do something  
}  
else {  
  //Do something else  
}  
  • There should be one and only one line between the methods, which is conducive to visually clearer and easier to organize. The gaps in the method should separate functions, but usually they are all pulled out to become a new method.
  • Use auto-synthesis first. But if necessary, @synthesize and @dynamic should each declare a new line in the implementation.
  • You should avoid calling methods in a colon-aligned manner. Because sometimes the method signature may have more than 3 colons and the colon alignment will make the code more readable. Please don't do this, although the colon alignment method includes code blocks, because Xcode's alignment makes it illegible.

should:

// blocks are easily readable  
[UIView animateWithDuration:1.0 animations:^{  
  // something  
} completion:^(BOOL finished) {  
  // something  
}];  

Should not:

// colon-aligning makes the block indentation hard to read  
[UIView animateWithDuration:1.0  
                 animations:^{  
                     // something  
                 }  
                 completion:^(BOOL finished) {  
                     // something  
                 }];  

Comment

When comments are needed, they should be used to explain why this particular piece of code does it. Any comments used must be kept up to date or deleted.

Block comments are generally avoided because the code is as self-explanatory as possible, and comments are only needed when there are intermittent or few lines of code. Exception: This does not apply to comments in the generated documentation

Pay attention to iOS development iOSDevTip , reply "Information" to download 2000G programming materials.

name

Apple's naming rules are as consistent as possible, especially the memory management rules (NARC) related to these.

Long, descriptive methods and variable naming are good.

should:

UIButton *settingsButton;  

Should not:

UIButton *setBut;  

The three-character prefix should often be used in class and constant naming, but should be ignored in Core Data entity names. For official raywenderlich.com books, starter kits or tutorials, the prefix'RWT' should be used.

Constants should use camel case naming rules, all words with initial capital letters and prefixes related to the class name.

should:

static NSTimeInterval const RWTTutorialViewControllerNavigationFadeAnimationDuration = 0.3;  

Should not:

static NSTimeInterval const fadetime = 1.7;  

The attribute also uses camel case, but the first letter of the first word is lowercase. Use auto-synthesis for properties instead of manually writing @synthesize statements unless you have a good reason.

should:

@property (strong, nonatomic) NSString *descriptiveVariableName;  

Should not:

id varnm;  

Underscore

When using attributes, instance variables should be accessed and changed using self. This means that all attributes will have different visual effects because they have self. in front of them.

But there is a special case: In the initialization method, instance variables (for example, _variableName) should be used directly to avoid potential side effects of getters/setters.

Local variables should not contain underscores.

method

In the method signature, there should be a space after the method type (-/+ sign). There should also be a space between each section of the method (in accordance with Apple's style). A descriptive keyword should be included before the parameter to describe the parameter.

The usage of the word "and" should be retained. It should not be used for multiple parameters to illustrate, as in the following example of initWithWidth:height:

should:

- (void)setExampleText:(NSString *)text image:(UIImage *)image;  
- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;  
- (id)viewWithTag:(NSInteger)tag;  
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;  

Should not:

-(void)setT:(NSString *)text i:(UIImage *)image;  
- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;  
- (id)taggedView:(NSInteger)tag;  
- (instancetype)initWithWidth:(CGFloat)width andHeight:(CGFloat)height;  
- (instancetype)initWith:(int)width and:(int)height;  // Never do this.  

variable

Try to name variables in a descriptive way. Single-character variable naming should be avoided as much as possible, except in the for() loop.

The asterisk indicates that the variable is a pointer. For example:
NSString *text 

Neither NSString* text 

Nor is it NSString * text, except for constants in some special cases.

Private variables should replace the use of instance variables as much as possible. Although using instance variables is an effective way, it is more inclined to use attributes to maintain code consistency.

Direct access to instance variables through the use of the'back' attribute (_variable, with an underscore in front of the variable name) should be avoided as much as possible, except in the initialization method (init, initWithCoder:, etc...), dealloc method and custom setters and getters. For more information on how to use the Accessor method directly in the initialization method and dealloc, check here.

should:

@interface RWTTutorial : NSObject  
@property (strong, nonatomic) NSString *tutorialName;  
@end  

Should not:

@interface RWTTutorial : NSObject {  
  NSString *tutorialName;  
}  

Attribute characteristics

All attribute characteristics should be listed explicitly to help novices read the code. The order of attribute characteristics should be storage, atomicity, which is consistent with the automatic code generation when Interface Builder connects to UI elements.

should:

@property (weak, nonatomic) IBOutlet UIView *containerView;  
@property (strong, nonatomic) NSString *tutorialName;  

Should not:

@property (nonatomic, weak) IBOutlet UIView *containerView;  
@property (nonatomic) NSString *tutorialName;  

NSString should use copy rather than strong attribute characteristics.

why? Even if you declare an NSString property, someone may pass in an instance of NSMutableString and then modify it without you paying attention.

should:

@property (copy, nonatomic) NSString *tutorialName;  

Should not:

@property (strong, nonatomic) NSString *tutorialName;  

Dot notation syntax

Point syntax is a convenient way to encapsulate access method calls. When you use dot syntax, the properties are still accessed or modified by using getter or setter methods. To learn more, read:  https://developer.apple.com/library/ios/documentation/cocoa/conceptual/ProgrammingWithObjectiveC/EncapsulatingData/EncapsulatingData.html .

Dot syntax should always be used to access and modify properties, because it makes the code more concise. The [] symbol is more biased to be used in other examples.

should:

objc  
NSInteger arrayCount = [self.array count];  
view.backgroundColor = [UIColor orangeColor];  
[UIApplication sharedApplication].delegate;  

Should not:

NSInteger arrayCount = self.array.count;  
[view setBackgroundColor:[UIColor orangeColor]];  
UIApplication.sharedApplication.delegate;  

Literal value

The literal values ​​of NSString, NSDictionary, NSArray, and NSNumber should be used when creating immutable instances of these classes. Please pay special attention to the nil value cannot be passed into NSArray and NSDictionary literal value, because this will cause crash.

should:

NSArray *names = @[@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul"];  
NSDictionary *productManagers = @{@"iPhone": @"Kate", @"iPad": @"Kamal", @"Mobile Web": @"Bill"};  
NSNumber *shouldUseLiterals = @YES;  
NSNumber *buildingStreetNumber = @10018;  

Should not:

NSArray *names = [NSArray arrayWithObjects:@"Brian", @"Matt", @"Chris", @"Alex", @"Steve", @"Paul", nil];  
NSDictionary *productManagers = [NSDictionary dictionaryWithObjectsAndKeys: @"Kate", @"iPhone", @"Kamal", @"iPad", @"Bill", @"Mobile Web", nil];  
NSNumber *shouldUseLiterals = [NSNumber numberWithBool:YES];  
NSNumber *buildingStreetNumber = [NSNumber numberWithInteger:10018];  

constant

Constants are easy to be used repeatedly and can be quickly modified without searching and replacing. Constants should be declared using static instead of #define, unless you explicitly use macros.

should:

static NSString * const RWTAboutViewControllerCompanyName = @"RayWenderlich.com";  
static CGFloat const RWTImageThumbnailHeight = 50.0;  

Should not:

#define CompanyName @"RayWenderlich.com"  
#define thumbnailHeight 2  

Enumerated type

When using enum, it is recommended to use the new fixed basic type specification because it has stronger type checking and code completion. Now the SDK has a macro NS_ENUM() to help and encourage you to use fixed basic types.

E.g:

typedef NS_ENUM(NSInteger, RWTLeftMenuTopItemType) {  
  RWTLeftMenuTopItemMain,  
  RWTLeftMenuTopItemShows,  
  RWTLeftMenuTopItemSchedule  
};  

You can also assign values ​​explicitly (show the old k-style constant definition):

typedef NS_ENUM(NSInteger, RWTGlobalConstants) {  
  RWTPinSizeMin = 1,  
  RWTPinSizeMax = 5,  
  RWTPinCountMin = 100,  
  RWTPinCountMax = 500,  
};  

The old k-style constant definitions should be avoided unless you write Core Foundation C code.

Should not:

enum GlobalConstants {  
  kMaxPinSize = 5,  
  kMaxPinCount = 500,  
};  

Case statement

Braces are not required in case statements unless required by the compiler. When a case statement contains multiple lines of code, braces should be added.

switch (condition) {  
  case 1:  
    // ...  
    break;  
  case 2: {  
    // ...  
    // Multi-line example using braces  
    break;  
  }  
  case 3:  
    // ...  
    break;  
  default:   
    // ...  
    break;  
}  

Many times, when the same code is used in multiple cases, a fall-through should be used. A fall-through is to remove the'break' statement at the end of the case, so as to allow the execution flow to jump to the next case value. In order to make the code more clear, a fall-through needs to be commented.

switch (condition) {  
  case 1:  
    // ** fall-through! **  
  case 2:  
    // code executed for values 1 and 2  
    break;  
  default:   
    // ...  
    break;  
}  

When using enumerated types in switch,'default' is not needed. E.g:

RWTLeftMenuTopItemType menuType = RWTLeftMenuTopItemMain;  
switch (menuType) {  
  case RWTLeftMenuTopItemMain:  
    // ...  
    break;  
  case RWTLeftMenuTopItemShows:  
    // ...  
    break;  
  case RWTLeftMenuTopItemSchedule:  
    // ...  
    break;  
}  

Private property

Private attributes should be declared in the class extension (anonymous classification) in the implementation file of the class, and the named class (such as RWTPrivate or private) should never be used unless it extends other classes. Anonymous classification should be exposed to the test by using the naming convention of the +Private.h file.

E.g:

@interface RWTDetailViewController ()  
@property (strong, nonatomic) GADBannerView *googleAdView;  
@property (strong, nonatomic) ADBannerView *iAdView;  
@property (strong, nonatomic) UIWebView *adXWebView;  
@end  

Boolean value

Objective-C uses YES and NO. Because true and false should only be used in CoreFoundation, C or C++ code. Since nil resolves to NO, there is no need to compare conditional statements. Don't compare something directly with YES, because YES is defined as 1 and a BOOL can be set to 8 bits.

This is to maintain consistency in different files and to be more visually concise.

should:

if (someObject) {}  
if (![anotherObject boolValue]) {}  

Should not:

if (someObject == nil) {}  
if ([anotherObject boolValue] == NO) {}  
if (isAwesome == YES) {} // Never do this.  
if (isAwesome == true) {} // Never do this.  

If the name of the BOOL property is an adjective, the property can ignore the "is" prefix, but specify the idiomatic name of the get accessor. E.g:

@property (assign, getter=isEditable) BOOL editable;  

The text and examples refer to Cocoa Naming Guidelines from here.

Conditional statements

The body of the conditional statement should be surrounded by braces in order to prevent errors, even if the body of the conditional statement can be written without braces (for example, only one line of code). These errors include adding a second line of code and expecting it to become an if statement; also, even more dangerous defect may occur when one line of code in the if statement is commented, and then the next line of code becomes part of the if statement unknowingly. In addition, this style is consistent with other conditional statements, so it is easier to read.

should:

if (!error) {  
  return success;  
}  

Should not:

if (!error)  
  return success;  

or

if (!error) return success;  

Ternary operator

When you need to improve the clarity and conciseness of the code, the ternary operator ?: will be used. It is often needed for single condition evaluation. When multiple conditions are evaluated, the code will be easier to read if you use if statements or restructure instance variables. Generally speaking, it is best to use ternary operators when assigning values ​​based on conditions.

Non-boolean variables are compared with something, and brackets () will improve readability. If the variable being compared is of type boolean, then parentheses are not needed.

should:

NSInteger value = 5;  
result = (value != 0) ? x : y;  
BOOL isHorizontal = YES;  
result = isHorizontal ? x : y;  

Should not:

result = a > b ? x = c > d ? c : d : y;  

Init method

The Init method should follow Apple's naming rules for generating code templates, and the return type should use instancetype instead of id.

- (instancetype)init {  
  self = [super init];  
  if (self) {  
    // ...  
  }  
  return self;  
}  

Class constructor

When the class constructor is used, it should return instancetype instead of id. This ensures that the compiler correctly infers the result type.

@interface Airplane  
+ (instancetype)airplaneWithType:(RWTAirplaneType)type;  
@end  

For more instancetype information, please check NSHipster.com.

CGRect function

When accessing x, y, width, or height in CGRect, you should use the CGGeometry function instead of directly accessing it through the structure. To quote Apple's CGGeometry:

  • All functions in this reference document accept the CGRect structure as input and implicitly normalize these rectangles when calculating their results. Therefore, your application should avoid directly accessing and modifying the data stored in the CGRect data structure. Instead, use these functions to manipulate rectangles and obtain their characteristics.

should:

CGRect frame = self.view.frame;  
CGFloat x = CGRectGetMinX(frame);  
CGFloat y = CGRectGetMinY(frame);  
CGFloat width = CGRectGetWidth(frame);  
CGFloat height = CGRectGetHeight(frame);  
CGRect frame = CGRectMake(0.0, 0.0, width, height);  

Should not:

CGRect frame = self.view.frame;  
CGFloat x = frame.origin.x;  
CGFloat y = frame.origin.y;  
CGFloat width = frame.size.width;  
CGFloat height = frame.size.height;  
CGRect frame = (CGRect){ .origin = CGPointZero, .size = frame.size };

Golden path

When coding with conditional statements, the code on the left hand side should be the "golden" or "happy" path. That is, do not nest if statements, multiple return statements are also OK.

should:

- (void)someMethod {  
  if (![someOther boolValue]) {  
    return;  
  }  
  //Do something important  
}  

Should not:

- (void)someMethod {  
  if ([someOther boolValue]) {  
    //Do something important  
  }  
}  

Error handling

When a method returns an error parameter by reference, judge the return value instead of the error variable.

should:

NSError *error;  
if (![self trySomethingWithError:&error]) {  
  // Handle Error  
}  

Should not:

NSError *error;  
[self trySomethingWithError:&error];  
if (error) {  
  // Handle Error  
}  

In the case of success, some Apple APIs record garbage values ​​(garbage values) to wrong parameters (if non-NULL), then judging the wrong value will cause false negative values ​​and crash.

Singleton mode

Singleton objects should use thread-safe mode to create shared instances.

+ (instancetype)sharedInstance {  
  static id sharedInstance = nil;  
  static dispatch_once_t onceToken;  
  dispatch_once(&onceToken, ^{  
    sharedInstance = [[self alloc] init];  
  });  
  return sharedInstance;  
}  

This will prevent possible and sometimes prolific crashes.

Newline

Line breaks are an important topic because its style guide is mainly for print and online readability.

E.g:

self.productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productIdentifiers];  

A long line of code should be divided into two lines of code, and the next line should be separated by two spaces.

self.productsRequest = [[SKProductsRequest alloc]   
  initWithProductIdentifiers:productIdentifiers];  

Xcode project

The physical file should be synchronized with the Xcode project file to avoid file expansion. The creation of any Xcode grouping should be reflected in files in the file system. The code is not only grouped according to type, but also can be grouped according to function, so that the code is more clear.

If possible, turn on "Treat Warnings as Errors" in the target's Build Settings, and enable the following additional warnings. If you need to ignore special warnings, use Clang's pragma feature.

Guess you like

Origin blog.csdn.net/qq_27740983/article/details/50800759