Objective-C style guide

Introduction

這個style guide規范描述了我們iOS開發團隊喜歡的Objectiv-C編程習慣。

代碼規范的意義,在于提高團隊各個成員寫的代碼的一致性和可讀性。一致性能減少工程師編寫代碼風格的困惑和猶豫;良好的可讀性更是能幫助我們瀏覽其他人代碼、以便合作和code review。

它并不是去定義the right way or the wrong way,而是達成一個需要遵守的共識,it's about agreeing on doing things the same way。

這個規范可能比其他一般的style guide規范涵蓋了更多的方面和細節。

歡迎聯系我,給出你關于這個規范的想法。

Credits

它絕大部分內容,來自Wonderful Objective-C style guide,并加入了自己的一些偏好。

Background

此規范也參考了一些蘋果官方文檔。如果在此規范里找不到你關心的內容,可以嘗試在下面的文檔里尋找:

Tools

VVDocumenter-Xcode

VVDocumenter-Xcode是一個幫我們寫注釋(用于生成文檔)的Xcode插件??梢允褂?a target="_blank" rel="nofollow">Alcatraz Xcode package manager安裝后重啟Xcode。

clang-format

  1. Download clang-format 3.5.2
  2. Copy clang-format into folder /usr/local/bin/
  3. Copy pre-commit into folder .gits/hooks/
  4. chmod +x pre-commit
  5. Change .clang-format if you want to change the style.

Cocoapods

當需要一些“輪子”時,可以先在現有的代碼里面找找(Podfile或者Libraray)。沒有的話,再用pod search或者github找找,看有沒有代碼質量高的、大家廣泛使用的(watch、star、fork數量多)、比較活躍、比較友好的第三方代碼,看看實現原理以及使用方法,是否適合我們的需求。

或可以通過繼承,滿足我們的需求。如果第三方的代碼有bug,或者需要改進,可以自己fork后修改使用(順便可以給作者發一個pull request)

在你有更好的實現、有時間的前提下,可以自己造更好的“輪子”。

Podfile.lock 有變動表示使用的第三庫版本有變化,如果采用新的版本,需要檢查新版本的第三方庫沒有問題。

盡量使用最新版本的第三方庫。

但發包前對此文件的修改要多尤其小心。最好發完包后,自己看看新版本有無問題,如果有必要再讓測試看看。

Cocoapods命令解釋

  • pod update: Updating local specs repositories, 更新工程中的第三方庫
  • pod update --no-repo-update:使用local specs repositories中最新的版本
  • pod install: Updating local specs repositories, 安裝podfile.lock中規定版本
  • pod install --no-repo-update:當podfile.lock中規定的版本,不在local specs repositories中,install會失敗

Cocoapods使用規范

  • pod update命令,由小組某個成員,開啟定時任務:
    30 12 * * 1 cd /Users/Can/Workspace/project_name;pod update;
    //用來檢查第三方庫是否有更新,并升級庫。這個是為了定期檢查第三方庫是否有新版本。
    //一個小組成員做這件事就可以
    //發現Podfile.lock變了,表示第三方代碼版本升級了,應該了解一下升級的內容,另外上線前不要輕易升級影響功能第三方代碼
  • pod install命令,由小組其他成員,開啟定時任務:
    30 12 * * * cd /Users/Can/Workspace/project_name;pod install;
    //主要用來Updating local specs repositories,防止install失敗
  • pull完代碼推薦使用pod install,pod install --no-repo-update,后者速度更快可能會報錯
  • git checkout 老代碼,推薦使用pod install --no-repo-update,因為是老代碼,一般不會報錯
  • Podfile.lock任何情況下不能刪除
  • pod update --no-repo-update,只用在pod install不成功,但需要馬上編譯成功的情況

新版本的cocoapods的一些命令執行方式,有些變化,上面的內容有部分過期

Table of Contents

<h2 id="language">Language</h2>

使用美國英語,而非英國等國家英語。

Preferred:

UIColor *myColor = [UIColor whiteColor];

Not Preferred:

UIColor *myColour = [UIColor whiteColor];

<h2 id="project-organization">Project Organization</h2>

Xcode工程,采用類似下面的工程文件結構。

Your_Project
  |-- AppDelegate.h
  |-- AppDelegate.m
  |-- Images.xcassets
  |-- MainMenu.xib
  |-- Supporting Files
  |-- Models
  |-- Views
  |-- Controllers
  |-- Stores
  |-- Helpers
  |-- ...

并且保持對應的物理文件系統路徑,方便在finder里面查找,也能保持finder內文件整齊。因此需要加入新的group時,需要先在文件系統里新建一個folder,然后add這個folder。

<h2 id="code-organization">Code Organization</h2>

使用#pragma mark -對.m文件里面的Methods進行歸類。

#pragma mark - Class Methods

+ (ClassObject)classWithDefaults;
+ (ClassObject)convertToJSON;
+ (ClassObject)convertToXML;

#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 {}

<h2 id="language">line-wrapping-(code-width)</h2>

合適的規范一般并不需要一行能容納無限長度的代碼,80列的限制反而能提醒你,可能你的代碼可讀性不太好。

Preferences->Text Editing->Page Guide at column:

As seen here:


Xcode Page Guide Pref

<h2 id="spacing">Spacing</h2>

  • 縮進使用4個空格(Xcode默認)。
  • 操作符左右留空格。

Preferred:

if (self.changeEable && self.shouldChange)
sum = count1 + count2

Not Preferred:

if (self.changeEable&&self.shouldChange)
sum=count1+count2
  • 方法體的花括號需要在新的一行開啟,在新的一行關閉。而其它花括號(if/else/switch/while etc.),加入一個空格后在行尾開啟,在新一行關閉(Xcode默認)。

Preferred:

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

Not Preferred:

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

在使用else或者else if,它們前后的大括號應該是在一行,否則不太好看:

Not Preferred:

if (user.isHappy) 
{
  // Do something
} 
else 
{
  // Do something else
}
  • methods之間只留一個空行。
  • 盡量使用auto-synthesis。如果需要使用@synthesize,每個property需要新開一行, @dynamic也是需要新開一行。
  • 當methods里面需要傳入block時,不要使用冒號對齊對方式:

Preferred:

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

Not Preferred:

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

<h2 id="comments">Comments</h2>

如果需要注釋,它只用來解釋為什么這段代碼要這么寫,并且是正確的,代碼變化時也需要馬上更新,不能有誤導。

盡可能避免大量使用注釋,好的代碼應該盡可能是self-documenting。

Exception: 上面兩條不適用于生成文檔用的注釋.

<h2 id="documentation">Documentation</h2>

在頭文件中的以下內容,需要寫注釋用來生成文檔(可以使用appledoc)。

VVDocumenter-Xcode也可以幫我們。

  • Classes
  • Prototype
  • Constants
  • Properties
  • Class Methods
  • Instance Methods

<a id="class-documentation">Class Documentation</a>

-- **EXAMPLE PENDING** --

<a id="constant-documentation">Constant Documentation</a>

/**
 *  @memberof PDFReaderConfig
 *  Default value for bookmarksEnabled: TRUE
 */
extern const BOOL kPDFReaderDefaultBookmarksEnabled;

<a id="property-documentation">Property Documentation</a>

/**
 *  Enable/disable bookmark support.
 *
 *  @see kPDFReaderDefaultBookmarksEnabled
 */
@property (nonatomic, readwrite, unsafe_unretained, getter=isBookmarksEnabled)
    BOOL bookmarksEnabled;

<a id="class-method-documentation">Class Method Documentation</a>

/**
 * Returns the shared PDFReaderConfig instance, creating it if necessary.
 *
 * @return The shared PDFReaderConfig instance.
 */
+ (instancetype)sharedConfig;

<a id="instance-method-documentation">Instance Method Documentation</a>

/**
 *  Initializes and returns a newly allocated PDFReaderViewController object.
 *
 *  @param object Reference to an initialized PDFReaderDocument
 *
 *  @return Initialized class instance or nil on failure
 *
 *  @throws "<MissingArguments>" When object is nil
 *  @throws "<WrongType>" When object is not a reference to a PDFReaderDocument
 *    object
 *
 *  @remark Designated initializer.
 */
- (instancetype)initWithReaderDocument:(PDFReaderDocument *)object;

/**
 *  Update bookmark state for each page in the PDFReaderDocument's bookmarks 
 *    list.
 */
- (void)updateToolbarBookmarkIcon;

<h2 id="naming">Naming</h2>

<h3 id="naming-conventions-for-methods-and-variables">Naming Conventions for Methods and Variables</h3>

詳盡的、描述性的方法和變量名,對代碼的self-documenting是很有幫助的。命名的清晰性和簡潔性都很重要,然而,在Objective-C的世界里,不能為簡潔性犧牲清晰性。

Preferred:

UIButton *settingsButton;
NSString *pageTitle = @"My Title";
int pageCounter = 0;

Not Preferred:

UIButton *setBut;
NSString *string = @"My Title";
int c = 0;

<h3 id="naming-conventions-for-constants-and-macros">Naming Conventions for Constants and Macros</h3>

下面的命名方式第一眼望去可能覺得好復雜,習慣習慣了就好了,別偷懶。。。

tPRE_Space_Name

Where:

Element Definition Value Example
t type = constant k* kMIX_MyClass_DefaultTitle
type = macro m* mBUZ_MyClass_doubleIt
PRE three-letter prefix XXX kXXX_MyClass_MenuBarHeight
Space unique namespace within tPRE WindowSize kXXX_MyClass_WindowSize
Name unique name within tPRE_Space BorderColor kZRE_MyClass_BorderColor
isIphone mZBB_MyClass_isIphone

類型(t)元素要么是"k" (for constant),要么是"m" (for macro)。

PRE元素一般是公司名稱前三個字母的大寫。

Space元素可以是Library名、類名、app名稱或模塊名稱,采用駝峰命名法并首字母大寫。

Name元素是具體名稱,constant采用駝峰命名法并首字母寫,macro采用駝峰命名法并首字母寫。

Preferred:

static const NSInteger kMIX_PDFReader_DefaultPagingViews = 3;

Not Preferred:

static NSTimeInterval const fadetime = 1.7;

Preferred

#define mMIX_MacroLib_rgb(r,g,b) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:1.0f]

Not Preferred

#define RGBColor(r,g,b) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:1.0f]

類的成員變量采用駝峰命名法并首字母寫。

Preferred:

@property (nonatomic, readwrite, strong) NSString *descriptiveVariableName;

// if/when needed
@synthesize descriptiveVariableName = _descriptiveVariableName;

Not Preferred:

id varnm;
...
@property (strong) id varnm;
...
@synthesize varnm;

<h3 id="naming-conventions-for-enumerated-types">Naming Conventions for Enumerated Types</h3>

Enumerated Type names 用e開頭,其他和Constants一樣。

Preferred:

// using the NS_ENUM macro (preferred)
typedef NS_ENUM(NSInteger, eMIX_UtilityLib_PlayerStateType) {
  PlayerStateOff,
  PlayerStatePlaying,
  PlayerStatePaused
};

Not Preferred:

enum {
    PlayerStateOff,
    PlayerStatePlaying,
    PlayerStatePaused
};

typedef NSInteger PlayerState;

<h3 id="underscores">Underscores</h3>

讀或寫所有properties,instance variables,都應該用self.,除了以下三種列外情況:

  • Setup and Tear down: init and dealloc
  • Overriding accessors (getters/setters)
  • Archiving activities: e.g. NSCoding Protocol's encodeWithCoder and initWithCoder

當一個對象還沒有初始化好,它處于不穩定的狀態,調用getters/setters的副作用,可能會導致異常情況發生,所以應該直接使用_variableName。see: Don’t Message self in Objective-C init (or dealloc)。

重載getter/setter來添加自定義的邏輯,必須使用_variableName,否則會無限循環。

歸檔或使用其它持久化方法時,使用_variableName可以避免getters/setters的副作用對屬性的改變。比如你使用getter讀一個屬性時,里面的邏輯可能修改了其他屬性的值。

還有,local variables不能使用下劃線。

<h2 id="methods">Methods</h2>

方法名的-/+符號后面要留一個空格,冒號前面的描述詞不能省略,方法里面不要使用and。

Preferred:

- (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;

Not Preferred:

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

<h2 id="variables">Variables</h2>

變量名應該盡可能的描述自身的存儲信息的內容。for()小括弧中的臨時變量、或用于計數的變量,可以簡單一些。

星號*應該緊靠變量名,而不是類型后面,比如:NSString *text 而不是 NSString* textNSString * text。

如果存在qualifier,格式應該為ClassName * qualifier variableName;

為了一致性,類的成員變量應該使用property。

Preferred:

@interface MyAppTutorial : NSObject

@property (strong, nonatomic) NSString *tutorialName;

@end

Not Preferred:

@interface MyAppTutorial : NSObject {
  NSString *tutorialName;
}

<h2 id="property-attributes">Property Attributes</h2>

property的所有屬性,應該按照atomicity、accessibility(readonly, readwrite)、storage的順序明確列出來。

Preferred:

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

Not Preferred:

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

一個非容器類,如果它具有mutable的相關類,比如NSString與NSMutableString、NSURLRequest與NSMutableURLRequest,在定義類成員變量時,應該使用copy而不是strong。why?當你定義NSSting為strong時,它可能被賦值為NSMutableString類型對象,這個對象可能會在你不注意的情況下改變值,而你既然把它類型聲明為NSSting,這種情況顯然不是你需要的。

Preferred:

@property (nonatomic, readwrite, copy) NSString *tutorialName;

Not Preferred:

@property (nonatomic, readwrite, strong) NSString *tutorialName;

<h2 id="dot-notation-syntax">Dot-Notation Syntax</h2>

前面講Underscores時,說了下點語法的使用。

還有一個限制是,點語法只應該用在訪問對象成員變量,而不是去調用對象的方法:

Preferred:

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

Not Preferred:

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

<h2 id="literals">Literals</h2>

盡可能使用語法糖去創建immutable對象。

Preferred:

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

Not Preferred:

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];

<h2 id="constants">Constants</h2>

因為類型安全的緣故,常量不應該用#define去定義,應該聲明為全局變量。

Preferred:

// Format: type const constantName = value;
NSString * const kMIX_MyClassShortDateFormat = @"MM/dd/yyyy";

// Macro: #define constantName value
\#define mMIX_MacroLib_isIPad (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)

Not Preferred:

\#define CompanyName @"MyCompany"
\#define thumbnailHeight 2

為了使全局變量能夠在其他文件類使用,可以在.h中加入:

extern NSString * const kMIX_MyClass_ShortDateFormat;

如果這個全局變量只用在當前類里面,需要在.m中去定義,并且前面加上static關鍵字限定作用域。

static NSString * const kMIX_MyClass_ShortDateFormat = @"MM/dd/yyyy";

一個static變量如果定義在函數體里面,它的值能夠一直保存,下次調用這個函數時,它的值還是上次調用這個函數后的值。

<h2 id="enumerated-types">Enumerated Types</h2>

使用NS_ENUM()去定義枚舉,它能夠讓編譯器檢查枚舉的數據類型。在swift里使用Objecive-C的枚舉,也是需要使用NS_ENUM()才可以。

Preferred

// using the NS_ENUM macro (preferred)
typedef NS_ENUM(NSInteger, eMIX_UtilityLib_PlayerStateType) {
  PlayerStateOff,
  PlayerStatePlaying,
  PlayerStatePaused
};

// with explicit value assignments
typedef NS_ENUM(NSInteger, eMIX_UtilityLib_PlayerStateType) {
  PlayerStateOff = 0,
  PlayerStatePlaying = 1,
  PlayerStatePaused = 2
};

Not Preferred:

// Apple Modern Objective-C style, provides type checking, enum type declared inline...
typedef enum _eMIX_UtilityLib_PlayerStateType : NSUInteger {
    PlayerStateOff,
    PlayerStatePlaying,
    PlayerStatePaused
} eMIX_UtilityLib_PlayerStateType;

// Apple Modern Objective-C style, provides type checking, enum type declared seperately...
enum _eMIX_UtilityLib_PlayerStateType : NSInteger {
  PlayerStateOff,
  PlayerStatePlaying,
  PlayerStatePaused
};
typedef enum _eMIX_UtilityLib_PlayerStateType : NSInteger eMIX_UtilityLib_PlayerStateType;

// Apple legacy style enum (32bit/64bit portability, no formal relationship between type and enum)...
enum {
    PlayerStateOff,
    PlayerStatePlaying,
    PlayerStatePaused
};
typedef NSInteger PlayerState;

// Apple legacy style enum (no 32bit/64bit portability)...
typedef enum {
    PlayerStateOff,
    PlayerStatePlaying,
    PlayerStatePaused
} PlayerState;
// NOTE: Implies...
// typedef int PlayerState;

// Generic C-style enum (no 32bit/64bit portability)...
typedef enum {
    PlayerStateOff,
    PlayerStatePlaying,
    PlayerStatePaused
} PlayerState;

<h2 id="switch-statements-and-case-label-blocks">Switch Statements and Case Label Blocks</h2>

前面說過,switich:后面的大括號不應該新開一行。

case:后面一般是不用帶大括號的,但如果你需要在里面聲明臨時變量,編譯器要求必須要帶大括號。為了一致性和可讀性,當case后面的語句多于一行時,帶上大括號。

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

當多個case:需要執行的代碼一樣時,需要去掉break,并注釋fall-through!

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

當使用枚舉時,default是多余的,不用寫。每個枚舉值都需要覆蓋到,否則會有警告。

kMIX_LeftMenuTopItemType menuType = kMIX_LeftMenuTopItemMain;

switch (menuType) {
  case kMIX_LeftMenuTopItemMain:
    // ...
    break;
  case kMIX_LeftMenuTopItemShows:
    // ...
    break;
  case kMIX_LeftMenuTopItemSchedule:
    // ...
    break;
}

<h2 id="private-properties">Private Properties</h2>

類私有的變量應該聲明在class extensions (anonymous categories)中。它可以放在.m文件里,或者新建一個 <headerfile>+Private.h文件,給子類或測試用。

For Example:

@interface MyAppDetailViewController ()

@property (nonatomic, readwrite, strong) GADBannerView *googleAdView;
@property (nonatomic, readwrite, strong) ADBannerView *iAdView;
@property (nonatomic, readwrite, strong) UIWebView *adXWebView;

@end

<h2 id="image-naming">Image Naming</h2>

圖片放入Images.xassets,命名方式是首字母大寫的駝峰命名法,并將不同的部分往后靠,在命名中盡量加入模塊名、類名進行區分。比如ShortRentCarRentButtonNormal,ShortRentCarRentButtonTouched。

Examples:

  • RefreshBarButtonItem / RefreshBarButtonItem@2x and RefreshBarButtonItemSelected / RefreshBarButtonItemSelected@2x
  • ArticleNavigationBarWhite / ArticleNavigationBarWhite@2x and ArticleNavigationBarBlackSelected / ArticleNavigationBarBlackSelected@2x.

<h2 id="booleans">Booleans</h2>

Objective-C使用YESNO,而不是truefalse(在CoreFoundation,C或C++代碼里可以使用)。

nil可以表示NO,所以在if中,對象的值與NO比較是沒必要的。

if中不要任何值與YESNO比較,YES是#define YES (BOOL)1,而BOOL是8bit。

Preferred:

if (someObject) {
  // code...
}

if (![anotherObject boolValue]) {
  // code...
}

Not Preferred:

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

如果一個類的成員變量類型是BOOL,并且它可以是個形容詞XXX,那么is的前綴可以省略,但需要加getter=isXXX

@property (nonatomic, readwrite, unsafe_unretained, getter=isEditable) BOOL editable;

參見Cocoa Naming Guidelines.

<h2 id="conditionals">Conditionals</h2>

為了避免錯誤、方便以后增加代碼、一致性和可讀性,if后面的大括號應該一直有,大括號在if所在行開啟。避免的錯誤包括誤將if大括號代碼塊里面的內容弄到if外面去了,還有even more dangerous defect

Preferred:

if (!error) {
  return success;
}

Not Preferred:

if (!error)
  return success;

or

if (!error) return success;

<h3 id="conditional-expressions">Conditional Expressions</h3>

if小括號里面的表達式,應該是一個變量,而不是一個函數調用,在if小括號里包含太多邏輯不太容易引起人注意。但類似[anObject boolValue]這種只會讀值,不會有其他邏輯的,是一個例外,但anObject的變量名也需要足夠清楚,這種情況單獨再聲明一個bool型變量不會增加可讀性。

Preferred:

BOOL continuousPlayEnabled = [[MediaAppPrefs sharedInstance] continuousPlay];
MediaAppTrack *nextMediaTrack = [MediaAppPlayer nextTrack];
if (continuousPlayEnabled && nextMediaTrack) {
  // play the next song
}

Not Preferred:

if ([[MediaAppPrefs sharedInstance] continuousPlay] && [MediaAppPlayer nextTrack]) {
  // play the next song
}

<h3 id="ternary-operator">Ternary Operator</h3>

三元操作符號不可濫用,導致可讀性降低。

Preferred:

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

BOOL isHorizontal = YES;
result = (isHorizontal) ? x : y;

Not Preferred:

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

result = isHorizontal ? x : y;

<h2 id="return-statements">Return Statements</h2>

不要return一個函數調用。

不要在函數中間調用return,可以在函數前面檢查傳參合法性時使用return。對于一個比較長的函數,中間有個return,讀代碼的人很難找到。

Preferred:

- (BOOL) playNext
{
  BOOL continuousPlayEnabled = [[MediaAppPrefs sharedInstance] continuousPlay];
  MediaAppTrack *nextMediaTrack = [MediaAppPlayer nextTrack];
  
  return (continuousPlayEnabled && nextMediaTrack);
}  

Not Preferred:

- (BOOL) playNext
{
  return ([[MediaAppPrefs sharedInstance] continuousPlay] && [MediaAppPlayer nextTrack]);
}  

<h2 id="init-methods">Init Methods</h2>

初始化方法返回類型應該為instancetype,而不是id。

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

  return self;
}

參見NSHipster.com。

另外,應該定義更安全、合理的初始化方法。參見Cocoa Fundamentals Guide中Object Creation一章節。

<h2 id="class-constructor-methods">Class Constructor Methods</h2>

類的創建對象便捷方法返回類型應該為instancetype,而不是id

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

參見NSHipster.com.

<h2 id="cgrect-functions">CGRect Functions</h2>

獲取CGRectx、y、widthheight,應該使用CGGeometry functions 而不是直接訪問結構體成員。 From Apple's CGGeometry reference:

All functions described in this reference that take CGRect data structures as inputs implicitly standardize those rectangles before calculating their results. For this reason, your applications should avoid directly reading and writing the data stored in the CGRect data structure. Instead, use the functions described here to manipulate rectangles and to retrieve their characteristics.

Preferred:

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);

Not Preferred:

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 };

<h2 id="error-handling">Error handling</h2>

參見Apple's developer documentation, 可以向方法傳入NSError變量的指針獲取異常情況,調用這類方法時,需要對NSError變量進行檢查,如果錯誤需要作出合適的處理:

Preferred:

NSError *error;
BOOL success;
success = [self trySomethingWithError:&error];

if (!success) {
  // Handle Error
  // e.g.
  NSLog(@"Something bad just happened: %@", error);
}

Not Preferred:

NSError *error;

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

<h2 id="singletons">Singletons</h2>

單例的正確創建方法:

+ (instancetype)sharedInstance {
  static id sharedInstance = nil;

  // thread-safe init
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    sharedInstance = [[self alloc] init];
  });

  return sharedInstance;
}

This will prevent possible and sometimes prolific crashes.

另外,還應該在dealloc中添加abort()方法:

- (void)dealloc {

    // implement -dealloc & remove abort() when refactoring for
    // non-singleton use.
    abort();
}

因為不應該給單例對象發送dealloc消息。參見StackOverflow thread.

<h2 id="line-breaks">Line Breaks</h2>

避免一行太長,應該在合適的地方斷行。

Preferred:

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

Not Preferred:

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

<h2 id="warnings">Warnings</h2>

在target's Build Settings,中應該開啟"Treat Warnings as Errors",并開啟盡可能多的警告。參見additional warnings。如果有些警告你無法避免,可以使用Clang's pragma feature。

<h2 id="log">Log</h2>

使用DDLog。并使用合適的等級打印不同級別的信息。

<h2 id="other-objective-c-style-guides">Other Objective-C Style Guides</h2>

上面的規范可能你不太喜歡,或者沒有涉及到你需要的某些方面,可以參考下面的內容:

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容

  • 紐約時報 移動團隊 Objective-C 規范指南 這份規范指南概括了紐約時報 iOS 團隊的代碼約定。 介紹 ...
    _淺墨_閱讀 1,084評論 0 1
  • 從其他地方整理了一些編碼規范的資料,分享給大家。YoY 1.語言(Language) 需要使用US English...
    大臉貓121閱讀 444評論 0 2
  • Swift style guide. Introduction API Design Guidelines是蘋果專...
    dev_xdyang閱讀 864評論 0 2
  • 引言 一直以來都是在談如何開發, 如開發的小技巧小經驗 今天為什么突然說起編程規范來了呢? 因為在我看來, 編程規...
    諾之林閱讀 561評論 1 5
  • 伊利齊顯著無所謂的樣子,“抓住四個就夠了,我保證問出實情來!” “不是這個問題!趕緊把那四個人放進我的黑罩中!快!...
    活捉一只胖墩閱讀 225評論 0 2