Objective-C 代碼規(guī)范

Objective-C,通常寫作ObjC或OC和較少用的Objective C或Obj-C,是擴(kuò)充C的面向?qū)ο缶幊陶Z言。它主要使用于Mac OS X和GNUstep這兩個(gè)使用OpenStep標(biāo)準(zhǔn)的系統(tǒng),而在NeXTSTEP和OpenStep中它更是基本語言。
GCC與Clang含Objective-C的編譯器,Objective-C可以在GCC以及Clang運(yùn)作的系統(tǒng)上編譯。
1980年代初布萊德·考克斯(Brad Cox)在其公司Stepstone發(fā)明Objective-C。他對軟件設(shè)計(jì)和編程里的真實(shí)可用度問題十分關(guān)心。Objective-C最主要的描述是他1986年出版的書 Object Oriented Programming: An Evolutionary Approach. Addison Wesley. ISBN 0-201-54834-8.

根據(jù)約束力強(qiáng)弱,規(guī)約依次分為強(qiáng)制、推薦、參考三大類:

  • 【強(qiáng)制】:必須遵守,違反本約定或?qū)?huì)引起嚴(yán)重的后果;
  • 【推薦】:盡量遵守,長期遵守有助于系統(tǒng)穩(wěn)定性和合作效率的提升;
  • 【參考】:充分理解,技術(shù)意識的引導(dǎo),是個(gè)人學(xué)習(xí)、團(tuán)隊(duì)溝通、項(xiàng)目合作的方向。

一. 語言規(guī)約

1.1 命名規(guī)約

1.1.1 【強(qiáng)制】命名約定通用準(zhǔn)則:清晰、一致性、不能自我指涉。

  • 清晰:命名應(yīng)該既清晰又剪短,但拒絕為了最求剪短而喪失清晰性,拒絕為了簡潔進(jìn)行隨意縮寫。

正例:

insertObject:atIndex:     好的命名
removeObjectAtIndex:      好的命名
removeObject:             好的命名
destinationSelection      好的命名
setBackgroundColor        好的命名

反例:

insert:at:      不清晰,插入什么?“at”表示什么?
remove:         不清晰,需要移除什么?
destSel         不清晰,縮寫含義不清
setBkgdColor:   縮寫含義不清
  • 一致性:命名含義應(yīng)該具有前后,全局的一致性,同個(gè)功能也應(yīng)該使用同個(gè)名稱。
-(NSInteger)tag 該方法同時(shí)定義在 NSView、NSCell、NSControl 這三個(gè)類里面
  • 不能自我指涉:除掩碼常量、通知常量外,名稱不應(yīng)該自我指涉(self-reference)。
    (通俗的講,自我指涉是指在變量末尾增加了自己類型的一個(gè)后綴)

正例:

NSString                                規(guī)范的寫法
NSUnderlineByWordMask                   掩碼常量,可以使用 Mask 自我指涉
NSTableViewColumnDidMoveNotification    通知常量,可以使用 Notification 自我指涉

反例:

NSStringObject      NSString 本身已經(jīng)是 Object 了,不需要再在名字里顯示指出

1.1.2 【強(qiáng)制】杜絕一切縮寫,除以下已經(jīng)長期使用形成共識的內(nèi)容可以使用縮寫。

alloc   Allocate
alt     Alternate
app     Application
calc    Calculate
dealloc Deallocate
func    Function
horiz   Horizontal
info    Information
init    Initialize
int     Integer
max     Maximum
min     Minimum
msg     Message
nib     Interface Builder archive
pboard  Pasteboard
rect    Rectangle
Rep     Repressentation
temp    Temporary
vert    Vertical

1.1.3 【強(qiáng)制】文件名、自定義類、Protocol 禁止以系統(tǒng)已有前綴開頭。

AC,AB,AS,AL,AU,AV,CX,CF,CK,CN,CA,CB,NS,CF,CG,CI,CL,CM,MIDI,CM,CS,CT,CV,EK,EA,GC,GK,HK,HM,AD,IN,GS,LA,MK,MA,MP,MT,MS,MF,MC,NE,NK,NC,AL,PK,PH,CA,QL,RP,SF,SL,SF,Sk,SC,TW,UI,UN,VS,VT,WC,WK
  • 一般的,自定義類名前以三位大寫字母表示前綴,例如CYYPerson

1.1.4【參考】文件名、自定義類、Protocol 、常量、枚舉等全局可見內(nèi)容需要添加三個(gè)大寫字符作為前綴,雙字幕前綴為 Apple 的類預(yù)留。盡管這個(gè)規(guī)范看起來有些古怪,但是這樣做可以減少 Objective-C 沒有命名空間所帶來的問題。


1.1.5 【強(qiáng)制】方法名、參數(shù)名、成員變量、局部變量都采用小寫字符開頭,名稱中的單詞首字符要大寫的小駝峰形式。另外,請不要在方法名稱中使用前綴(category 方法除外:category 方法強(qiáng)制使用模塊前綴)。

正例:

normal:
- (BOOL)fileExistsAtPath:(NSString *)path;

category:
- (NSString *)cyy_urlDecode;

1.1.7 【強(qiáng)制】不要使用“do”或者“does”作為名稱的一部分,因?yàn)檫@些輔助性的動(dòng)詞不能為名稱增加更多的含義。同時(shí),不要在動(dòng)詞之前使用副詞或者形容詞。


1.1.8 【強(qiáng)制】如果方法返回接收者的某個(gè)屬性,則以屬性名稱作為方法名。如果方法沒有間接地返回一個(gè)或多個(gè)值,不要使用“get”這樣的單詞。

正例:

- (NSSize)cellSize;

反例:

- (NSSize)calcCellSize;
- (NSSize)getCellSize;

1.1.9 【強(qiáng)制】所有參數(shù)前面都應(yīng)使用關(guān)鍵字。

正例:

- (void)sendAction:(SEL)aSelector to:(id)anObject forAllCells:(BOOL)flag;

反例:

- (void)sendAction:(SEL)aSelector :(id)anObject :(BOOL)flag;

1.1.10 【強(qiáng)制】如果當(dāng)前創(chuàng)建的方法比起它所繼承的方法更有針對性,則應(yīng)該在已有的方法名稱后面添加關(guān)鍵字,并將其作為新方法的名稱。

父類:
- (instancetype)initWithFrame:(NSRect)frameRect;
子類:
- (instancetype)initWithFrame:(NSRect)frameRect  mode:(int)aMode cellClass:(Class)factoryId numberOfRows:(int)rowsHigh numberOfColumns:(int)colsWide;

1.1.11 【推薦】請不要使用”and“來連接兩個(gè)表示接受者屬性的關(guān)鍵字。

  • 雖然下面的例子使用”and“這個(gè)詞感覺還不錯(cuò),但是隨著創(chuàng)建的方法所帶有的關(guān)鍵字越來越多,這種方式會(huì)引起問題

正例:

- (int)runModalForDirectory:(NSString *)path file:(NSString *)name types:(NSArray *)fileTypes;

反例:

- (int)runModalForDirectory:(NSString *)path andFile:(NSString*)name andTypes:(NSArray *)fileTypes;

1.1.12 【推薦】如果方法描述了兩個(gè)獨(dú)立的動(dòng)作,請使用”and“把它們連接起來

- (BOOL)openFile:(NSString *)fullPath withApplication:(NSString *)appName andDeactivate:(BOOL)flag;

1.1.13 【強(qiáng)制】您可以使用情態(tài)動(dòng)詞(在動(dòng)詞前冠以"can","should","will"等),使得方法的名稱更加明確,但是請不要使用"do"或"does"這樣的情態(tài)動(dòng)詞。

正例:

- (void)setCanHide:(BOOL)flag;
- (BOOL)canHide;
- (void)setShouldCloseDocument:(BOOL)flag;
- (BOOL)shouldCloseDocument;

反例:

- (void)setDoesAcceptGlyphInfo:(BOOL)flag;
- (BOOL)doesAcceptGlyphInfo;

1.1.14【強(qiáng)制】只有當(dāng)方法間接地返回對象或者數(shù)值,您才需要在方法名稱中使用"get"。這種格式只適用于需要返回多個(gè)數(shù)據(jù)項(xiàng)的方法。像這種接收多個(gè)參數(shù)的方法應(yīng)該能夠傳入nil,因?yàn)檎{(diào)用者未必對每個(gè)參數(shù)都感興趣。

- (void)getLineDash:(float *)pattern count:(int *)count phase:(float *)phase;

1.1.15【強(qiáng)制】下面是數(shù)條和方法參數(shù)命名相關(guān)的通用規(guī)則:

  • 和方法名稱一樣, 參數(shù)的名稱也是以小寫的字符開頭,并且后續(xù)單詞的首字符要大寫。 例如:removeObject:(id)anObject。
  • 請不要在參數(shù)名稱中使用"pointer"或者"ptr"。您應(yīng)該使用參數(shù)的類型來聲明參數(shù)是否是 一個(gè)指針。
  • 請不要使用一到兩個(gè)字符的名稱作為參數(shù)名。
  • 請不要使用只剩幾個(gè)字符的縮寫。

1.1.16【強(qiáng)制】方法名稱的開頭應(yīng)標(biāo)識出發(fā)送消息的對象所屬的類:

- (BOOL)tableView:(NSTableView *)tableView shouldSelectRow:(int)row;
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename;

1.1.17【強(qiáng)制】如果調(diào)用某個(gè)方法是為了通知委托某個(gè)事件已經(jīng)發(fā)生或者即將發(fā)生, 則請?jiān)诜椒Q 中使用“did”或者“will”這樣的助動(dòng)詞。

- (void)browserDidScroll:(NSBrowser *)sender;
- (NSUndoManager *)windowWillReturnUndoManager:(NSWindow *)window;

1.1.18【強(qiáng)制】【強(qiáng)制】如果調(diào)用某個(gè)方法是為了要求委托代表其他對象執(zhí)行某件事,當(dāng)然,您也可以在方法名 稱中使用“did”或者“will”,但我們傾向于使用“should”。

- (BOOL)windowShouldClose:(id)sender;

1.2 常量定義

1.2.1 【強(qiáng)制】請使用NS_ENUM枚舉類型來表示一群相互關(guān)聯(lián)的整數(shù)值常量。枚舉項(xiàng)以枚舉類型為前綴。

typedef NS_ENUM(NSInteger, NSMatrixMode) {
    NSMatrixModeRadio       = 0,
    NSMatrixModeHighlight   = 1,
    NSMatrixModeList        = 2,
    NSMatrixModeTrack       = 3
};

1.2.2 【強(qiáng)制】請使用NS_OPTIONS定義一組相互關(guān)聯(lián)的位移枚舉常量。位移枚舉常量是可以組合使用的。枚舉項(xiàng)以枚舉類型為前綴。

typedef NS_OPTIONS(NSUInteger, NSMatrixModeMask) {
    NSMatrixModeMaskBorderless      = 0,
    NSMatrixModeMaskTitled          = 1 << 0,
    NSMatrixModeMaskClosable        = 1 << 1,
    NSMatrixModeMaskMiniaturizable  = 1 << 2,
    NSMatrixModeMaskResizable       = 1 << 3
};

1.2.3 【強(qiáng)制】請使用 const 來創(chuàng)建浮點(diǎn)值常量。如果某個(gè)整數(shù)值常量和其他的常量不相關(guān),您也可以使用 const 來創(chuàng)建,否則,則應(yīng)使用枚舉類型。下面的聲明展示了 const 常量的格式:

const float NSLightGray;

1.2.4 【強(qiáng)制】通常情況下,請不要使用#define 預(yù)處器理命令創(chuàng)建常量。對于整數(shù)值常量,請使用枚舉類型創(chuàng)建,而對于浮點(diǎn)值常量,請使用const 修飾符創(chuàng)建。


1.2.5 【強(qiáng)制】有些符號,預(yù)處理器需要對其進(jìn)行計(jì)算,以便決定是否要對某一代碼塊進(jìn)行處理,則它們應(yīng)該使用大寫字符表示。

#ifdef DEBUG

1.2.6 【強(qiáng)制】推薦使用常量來代替字符串字面值和數(shù)字。常量應(yīng)該用 static 聲明為靜態(tài)常量,而不要用 #define,除非它明確的作為一個(gè)宏來使用。這樣能夠方便復(fù)用,而且可以快速修改而不需要查找和替換

正例:

static NSString * const CYYCacheControllerDidClearCacheNotification = @"CYYCacheControllerDidClearCacheNotification";
static const CGFloat CYYImageThumbnailHeight = 50.0f;

反例:

#define CompanyName @"Apple Inc."
#define magicNumber 42
  • 常量應(yīng)該在頭文件中以這樣的形式暴露給外部:
extern NSString *const CYYCacheControllerDidClearCacheNotification;

并在實(shí)現(xiàn)文件中為它賦值。


1.2.7 【強(qiáng)制】異常使用全局的 NSString 對象來標(biāo)識,其名稱按如下的方式進(jìn)行組合:異常名稱中的具有唯一性的那部分,其組成詞應(yīng)該拼寫在一起, 并且每個(gè)單詞的首字符要大寫。

[Prefix] + [UniquePartOfName] + Exception

例如:

NSColorListIOException
NSColorListNotEditableException 
NSDraggingException
NSFontUnavailableException
NSIllegalSelectorException

1.2.8 【強(qiáng)制】Notification消息使用全局的 NSString 對象進(jìn)行標(biāo)識,其名稱按如下的方式組合:

[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

例如:

NSApplicationDidBecomeActiveNotification  
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

1.3 類定義規(guī)約

1.3.1 【強(qiáng)制】要盡可能地使用屬性定義代替無修飾的實(shí)例變量。

正例:

@interface Item : NSObject

@property (nonatomic, copy) NSString* name;

@end

反例:

@interface Item : NSObject {
    NSString* _name
}
@end

1.3.2 【推薦】如果需要自定義property的getter或setter方法時(shí),請?jiān)诼暶鱬roperty時(shí)一起聲明掉。

@property (nonatomic, copy, getter=my_name, setter=my_setName:) NSString *name;

1.3.3 【推薦】對外暴露的屬性,盡量定義為readonly。


1.3.4 【推薦】不建議使用@dynamic修飾屬性,除非你真的知道自己在干什么。


1.3.5 【強(qiáng)制】在為某個(gè)類添加實(shí)例變量時(shí),請記住下面幾個(gè)因素:

  • 只暴露必須的對外接口或?qū)傩栽?h中,其它private保留在.m里。
  • 請確保實(shí)例變量的名稱能夠扼要地描述它所保存的屬性。

1.3.6 【強(qiáng)制】除非在沒有其他解決方案的情況下,否則不復(fù)寫任何 + (void) load方法。所有的load方法的執(zhí)行在Class的裝載階段,會(huì)延長App的啟動(dòng)時(shí)間.且如果存在穩(wěn)定性問題,也沒有可以修復(fù)的時(shí)機(jī)。


1.3.7 【強(qiáng)制】+(void)initialize必須判斷class類型或使用dispatch_once防止執(zhí)行多次.由于任何繼承類也會(huì)執(zhí)行父類的initilize,所以這里一定要做類型判斷,或使用dispatch_once來保障不會(huì)執(zhí)行多次

if (self == [NSFoo class]) {
    // the initializing code
}

1.3.8 【強(qiáng)制】不應(yīng)該顯式地調(diào)用 initialize方法。如果需要觸發(fā)初始化行為,則請調(diào)用一些無害的方法。

正例:

[NSImage self];

反例:

[NSImage initialize];

1.3.9 【強(qiáng)制】在property的getter方法里不能再顯式或者隱式的調(diào)用同一個(gè)property的getter方法,會(huì)導(dǎo)致死循環(huán)。

反例

@interface ALPerson : NSObject

@property (nonatomic, copy) NSString *name;

@end

@implementation ALPerson

- (void)setName:(NSString *)name {
    self.name = name;//死循環(huán)!
}

@end

1.3.10 【推薦】方法調(diào)用,方法調(diào)用應(yīng)盡量保持與方法聲明的格式一致。當(dāng)格式的風(fēng)格有多種選擇時(shí),新的代碼要與已有代碼保持一致。

  • 調(diào)用時(shí)所有參數(shù)應(yīng)該在同一行:
[myObject doSomethingWith:arg1 name:arg2 error:arg3];
  • 或者每行一個(gè)參數(shù),以冒號對齊:
[myObject doSomethingWith:arg1 
                     name:arg2 
                    error:arg3];
  • 方法定義與方法聲明一樣,當(dāng)關(guān)鍵字的長度不足以以冒號對齊時(shí),下一行都要以四個(gè)空格進(jìn)行縮進(jìn)。
[myObj short:arg1
    longKeyword:arg2
    evenLongerKeyword:arg3];

1.3.11 【推薦】使用nonnull、nullable、__kindof來修飾方入?yún)?shù)、返回值、屬性

@property (nonatomic, strong, nonnull) Sark *sark;
@property (nonatomic, copy, readonly, nullable) NSArray *friends;
+ (nullable NSString *)friendWithName:(nonnull NSString *)name;

與Swift混編的時(shí)候,Swift的變量會(huì)有,? 與!修飾的變量,前者為變量可以為nil,后者為變量不會(huì)為nil。
為了橋接此變量,OC中提供,nullable與nonnull關(guān)鍵字修飾,作為對應(yīng)的橋接。
例:

// OC
- (nullable NSString*)name {
}
- (NSString* __nullable)name {
}
- (NSString* _Nullable)name {
}
// MARK: -
- (int _Nonnull)age {
}

// 橋接Swift
func name() -> String? {
}
func age() -> Int {
}

1.3.12 【強(qiáng)制】禁止從designated initializer 里面調(diào)用一個(gè) secondary initializer。如果這樣,調(diào)用很可能會(huì)調(diào)用一個(gè)子類重寫的init方法并且陷入無限遞歸之中。

  • Objective-C 有指定初始化方法(designated initializer)和間接(secondary initializer)初始化方法的觀念。designated 初始化方法是提供所有的參數(shù),secondary 初始化方法是一個(gè)或多個(gè),并且提供一個(gè)或者更多的默認(rèn)參數(shù)來調(diào)用designated 初始化的初始化方法。
  • 一個(gè)類應(yīng)該有且只有一個(gè)designated初始化方法,其他的初始化方法應(yīng)該調(diào)用這個(gè)designated的初始化方法。
  • 在希望提供你自己的初始化函數(shù)的時(shí)候,應(yīng)該遵守這三個(gè)步驟來保證獲得正確的行為:

(1)定義你的designated initializer,確保調(diào)用了直接超類的 designated initializer。
(2)重載直接超類的 designated initializer。調(diào)用你的新的 designated initializer。
(3)為新的 designated initializer 寫文檔??梢杂镁幾g器的指令 attribute((objc_designated_initializer))來標(biāo)記。用編譯器指令attribute((unavailable(Invoke the new designated initializer))讓父類的 designated initializer 失效.

正例:

@interface ZOCNewsViewController : UIViewController

- (instancetype)initWithNews:(ZOCNews *)news __attribute__((objc_designated_initializer));
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil __attribute__((unavailable("Invoke the designated initializer,call initWithNews:")));
- (instancetype)init __attribute__((unavailable("Invoke the designated initializer,call initWithNews:"));

@end

@implementation ZOCNewsViewController

- (id)initWithNews:(ZOCNews *)news
{
    //調(diào)用直接父類的 designated initializer
    self = [super initWithNibName:nil bundle:nil];
    if (self) {
        _news = news;
    }
    return self;
}

// 重載直接父類的  designated initializer
// 如果你沒重載 initWithNibName:bundle: ,而且調(diào)用者決定用這個(gè)方法初始化你的類(這是完全合法的)。 initWithNews: 永遠(yuǎn)不會(huì)被調(diào)用,所以導(dǎo)致了不正確的初始化流程,你的類的特定初始化邏輯沒有被執(zhí)行。
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
    // call the new designated initializer
    return [self initWithNews:nil];
}

@end

反例:

@implementation ParentObject

//designated initializer    
- (instancetype)initWithURL:(NSString*)url title:(NSString*)title {
    if (self = [super init]) {
        _url = [url copy];
        _title = [title copy];
    }
    return self;
}
//secondary initializer
- (instancetype)initWithURL:(NSString*)url {
    return [self initWithURL:url title:nil];
}

@end

@interface ChildObject : ParentObject

@end

@implementation ChildObject
//designated initializer
- (instancetype)initWithURL:(NSString*)url title:(NSString*)title {
    //在designated intializer中調(diào)用 secondary initializer,錯(cuò)誤的
    if (self = [super initWithURL:url]) {

    }
    return self;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // 這里會(huì)死循環(huán)
    ChildObject* child = [[ChildObject alloc] initWithURL:@"url" title:@"title"];
}
@end

1.4 注釋規(guī)約

1.4.1 【強(qiáng)制】頭文件中的暴露的方法或者屬性都必須添加注釋

  • 注釋建議使用Xcode自帶工具插入默認(rèn)格式。option+command+/即可自動(dòng)插入。

1.4.2 【強(qiáng)制】自動(dòng)生成的代碼注釋中的placeholder要替換掉


1.4.3 【推薦】建議對于復(fù)雜難懂邏輯添加注釋


1.5 代碼組織規(guī)約

1.5.1 【推薦】當(dāng)一個(gè)類功能很多時(shí),建議使用Category的方式進(jìn)行功能劃分,這些Category可以放在同一個(gè)文件中。

示例:

@interface UIViewController (UIViewControllerRotation)

+ (void)attemptRotationToDeviceOrientation NS_AVAILABLE_IOS(5_0) __TVOS_PROHIBITED;
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation NS_DEPRECATED_IOS(2_0, 6_0) __TVOS_PROHIBITED;
@end

@interface UIViewController (UILayoutSupport)
@property(nonatomic,readonly,strong) id<UILayoutSupport> topLayoutGuide NS_AVAILABLE_IOS(7_0);
@property(nonatomic,readonly,strong) id<UILayoutSupport> bottomLayoutGuide NS_AVAILABLE_IOS(7_0);
@end

@interface UIViewController (UIKeyCommand)
- (void)addKeyCommand:(UIKeyCommand *)keyCommand NS_AVAILABLE_IOS(9_0);
- (void)removeKeyCommand:(UIKeyCommand *)keyCommand NS_AVAILABLE_IOS(9_0);

@end

1.5.2 【推薦】建議使用#pragma marks -來進(jìn)行方法分組,提高可讀性,具體樣例如下,建議把生命周期,事件,property方法以及protocol方法進(jìn)行區(qū)分。

示例:

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

1.5.3 【推薦】建議合理使用group或folder來組織工程結(jié)構(gòu),而不是全部放在source里,物理group與工程中g(shù)roup要對應(yīng)

1.5.4 【推薦】過期方法,不要直接刪除,先標(biāo)記為depcrated。

1.5.5 【推薦】建議類繼承關(guān)系不要超過2層,并且抽取公共邏輯到父類,盡量避免父類,子類方法調(diào)用跳躍

1.5.6 【參考】盡量減少繼承,可以考慮組合,category,protocol等方式

1.5.7 【推薦】每個(gè)文件.m的方法數(shù)目不應(yīng)該超過20個(gè),每個(gè)方法的行數(shù)不應(yīng)該超過200行。

  • 每個(gè)方法應(yīng)該只做一件事情。當(dāng)函數(shù)過長時(shí),它做的事情通常會(huì)不明確,后續(xù)會(huì)很難理解與維護(hù)。

1.5.8 【強(qiáng)制】函數(shù)內(nèi)嵌套不能太深,一個(gè)函數(shù)內(nèi)大括號里嵌套大括號不能超過三層。

  • 超過三層已經(jīng)很難理解一個(gè)函數(shù)的作用,可以將其中的一些邏輯抽離成一個(gè)單獨(dú)的函數(shù)。

1.5.9 【推薦】建議業(yè)務(wù)bundle使用統(tǒng)一的前綴來標(biāo)識

1.5.10 【推薦】頭文件中只暴露出需要給他人調(diào)用的類、方法及屬性,私有類、方法、變量放在.m中

1.5.11 【強(qiáng)制】Release包必須關(guān)閉非離線日志(NSLog、print)

1.5.12 【強(qiáng)制】必須清理工程中的所有warning

1.5.13 【推薦】長條件判斷建議使用bool變量來代替

  • 太長不容易調(diào)試,且不直觀。

正例:

BOOL isConditionSatisfied = (1 == a.x &&  3==b.y && 2 == c.x);
if (isConditionSatisfied){
 doSomething()
}

反例:

if (a.x = 1 && b.y =3 && c.x = 2){
 doSomething()
}

1.5.15 【推薦】條件判斷,推薦加大括號,即使一行,容易導(dǎo)致的錯(cuò)誤為,當(dāng) if 語句里面的一行被注釋掉,下一行就會(huì)在不經(jīng)意間成為了這個(gè) if 語句的一部分。

正例:

if (!error) {
    return success;
}

反例:

if (!error)
    return success;
//或
if (!error) return success;

1.5.16 【推薦】對三目運(yùn)算使用時(shí),要注意簡化,x=a?a:b只要寫成x=a?:b 即可;

1.5.17 【推薦】編寫switch語句的時(shí)候, 一定要實(shí)現(xiàn)default:,防止外部異常調(diào)用,內(nèi)部沒有處理的情況,

1.5.18 【強(qiáng)制】switch里每個(gè)case里需要強(qiáng)制有break;

1.5.19 【強(qiáng)制】switch里每個(gè)case里都要使用{}所有代碼括起來,就算只有一行。


二. 最佳實(shí)踐

2.1.1 【強(qiáng)制】自建線程必須命名。

2.1.2 【強(qiáng)制】多線程訪問同一個(gè)對象時(shí),必須注意臨界區(qū)的保護(hù)

2.1.3 【強(qiáng)制】單例創(chuàng)建要使用線程安全模式,并且禁止在單例的init方法中使用dispatch_sync來阻塞線程,極易出現(xiàn)死鎖

正例:

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

2.1.4 【強(qiáng)制】在多線程環(huán)境下使用懶加載方式加載變量,會(huì)有crash風(fēng)險(xiǎn),必須加鎖保護(hù)

正例:

//多線程環(huán)境下調(diào)用
- (NSCache *)contactCache
{
    if (!_contactCache) {
        @synchronized(self) { 
            if (!_contactCache) {
                _contactCache = [[NSCache alloc] init];
                _contactCache.name = @"contactCache";
            }
        }
    }
    return _contactCache;
}

2.1.5 【強(qiáng)制】performSelector:withObject:afterDelay:要在有Runloop的線程里調(diào)用,否則調(diào)用無法生效。

  • 說明:異步線程默認(rèn)是沒有runloop的,除非手動(dòng)創(chuàng)建;而主線程是系統(tǒng)會(huì)自動(dòng)創(chuàng)建Runloop的。所以在異步線程調(diào)用是請先確保該線程是有Runloop的。

2.1.6 【強(qiáng)制】禁止隨意創(chuàng)建長駐線程,除非是在整個(gè)app運(yùn)行周期內(nèi)都必須存在且有任務(wù)運(yùn)行的。


2.1.7 【推薦】NSNotificationCenter在iOS 8及更老系統(tǒng)上存在多線程bug,selector執(zhí)行到一半時(shí)可能會(huì)因?yàn)閟elf銷毀而觸發(fā)crash,解決方案是在selector里開始的地方引入下面的宏:

- (void)onMultiThreadNotificationTrigged:(NSNotification *)notify {
   __weak typeof(self) weakSelf = self;
   __strong typeof(self) strongSelf = weakSelf;
   if (! weakSelf) { 
    return; 
   }
   [strongSelf doSomething];
}

2.1.8 【推薦】在多線程應(yīng)用中,Notification在哪個(gè)線程中post,就在哪個(gè)線程中被轉(zhuǎn)發(fā),而不一定是在注冊觀察者的那個(gè)線程中。如果發(fā)送消息的不在主線程,而接受消息的回調(diào)里做了UI操作,需要讓其在主線程執(zhí)行。


2.1.9 【推薦】僅當(dāng)必須保證順序執(zhí)行時(shí)才使用dispatch_sync,否則容易出現(xiàn)死鎖,應(yīng)避免使用,可使用dispatch_async。

正例:

- (void)viewDidLoad {
  [super viewDidLoad];
  dispatch_queue_t mainQueue = dispatch_get_main_queue();
  dispatch_block_t block = ^() {
      NSLog(@"%@", [NSThread currentThread]);
  };
  dispatch_async(mainQueue, block); //使用異步操作
}

反例:

// 禁止。出現(xiàn)死鎖,報(bào)錯(cuò):EXC_BAD_INSTRUCTION。原因:在主隊(duì)列中同步的添加一個(gè)block到主隊(duì)列中
- (void)viewDidLoad {
  [super viewDidLoad];
  dispatch_queue_t mainQueue = dispatch_get_main_queue();
  dispatch_block_t block = ^() {
      NSLog(@"%@", [NSThread currentThread]);
  };
  dispatch_sync(mainQueue, block);
}

2.1.10 【參考】使用 performSelector:withObject:afterDelay:和 cancelPreviousPerformRequestsWithTarget組合的時(shí)候要小心

  • afterDelay會(huì)增加receiver的引用計(jì)數(shù),cancel則會(huì)對應(yīng)減一
  • 如果在receiver的引用計(jì)數(shù)只剩下1 (僅為delay)時(shí),調(diào)用cancel之后會(huì)立即銷毀receiver,后續(xù)再調(diào)用receiver的方法就會(huì)crash
    正例:
__weak typeof(self) weakSelf = self;
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (!weakSelf) {
//NSLog(@"self被銷毀");
    return;
}
[self doOther];

2.1.11 【強(qiáng)制】禁止在非主線程中進(jìn)行UI元素的操作

2.1.12 【強(qiáng)制】在主線程中禁止進(jìn)行同步網(wǎng)絡(luò)資源讀取,使用NSURLSession進(jìn)行異步獲取

2.1.13 【強(qiáng)制】如果需要進(jìn)行大文件或者多文件的IO操作,禁止主線程使用,必須進(jìn)行異步處理

2.1.14 【強(qiáng)制】對剪貼板的讀取必須要放在異步線程處理,最新Mac和iOS里的剪貼板共享功能會(huì)導(dǎo)致有可能需要讀取大量的內(nèi)容,導(dǎo)致讀取線程被長時(shí)間阻塞

正例:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   UIPasteboard *pasteboard = [UIPasteboard generalPasteboard]; 
   if (pasteboard.string.length > 0) {//這個(gè)方法會(huì)阻塞線程
      NSString *text = [pasteboard.string copy];
      [pasteboard setValue:@"" forPasteboardType:UIPasteboardNameGeneral];
      if (text == nil || [text isEqualToString:@""]) {
          return ;
      }
      dispatch_async(dispatch_get_main_queue(), ^{
          [self processShareCode:text];
      });
   }
});

2.2 內(nèi)存管理

2.2.1 【推薦】請慎重使用單例,避免造成產(chǎn)生不必要的常駐內(nèi)存。

2.2.2 【推薦】單例初始化方法中盡量保證單一職責(zé),尤其不要進(jìn)行其他單例的調(diào)用。極端情況下,兩個(gè)單例對象在各自的單例初始化方法中調(diào)用,會(huì)造成死鎖。

2.2.3 【強(qiáng)制】Delegate需要用weak進(jìn)行引用。

2.2.4 【強(qiáng)制】使用block時(shí),需要在block訪問外部weak修飾的self,內(nèi)部在重新strong處理。避免RetainCycle。

2.2.5 【推薦】strong引用 子實(shí)例,weak引用parent,基礎(chǔ)類型使用assign,NSString,NSArray,block使用copy

2.2.6 【強(qiáng)制】對類添加屬性時(shí)使用 copy方式還是使用retain方式規(guī)約:

  • 對實(shí)現(xiàn) NSCopying協(xié)議的對象使用copy方式。通常情況下,諸如NSString、NSURL, block,NSArray 這樣的對象應(yīng)該能被copy;
  • 像UIView的對象則應(yīng)該可以被保持。strong引用 子實(shí)例,weak引用parent.
  • 基礎(chǔ)類型使用assign。

2.2.7 【強(qiáng)制】在dealloc中要記得要remove observer, callback=null

2.2.8 【強(qiáng)制】會(huì)循環(huán)使用的Timer(指定了repeat參數(shù)為YES),必須要在合適的時(shí)機(jī)調(diào)用invalidate方法,否則會(huì)出現(xiàn)內(nèi)存泄漏,在使用類的析構(gòu)函數(shù)中調(diào)用Timer的invalidate方法為時(shí)已晚,因?yàn)閠imer會(huì)對其傳遞的目標(biāo)object增加引用計(jì)數(shù),若不調(diào)用invalidate,使用類根本得不到析構(gòu)。

  • 對于指定了repeat參數(shù)為NO的Timer,則可以不調(diào)用invalidate方法。

2.2.9 【強(qiáng)制】在init 和dealloc中不允許使用self訪問屬性(父類屬性除外),只允許通過"_變量名"直接訪問。

  • 容易出現(xiàn)重復(fù)創(chuàng)建對象,甚至crash問題。
  • 在init和dealloc階段,self是一個(gè)不完整的對象。
  • 由于accessor方法是可以被子類重寫的,在調(diào)用父類init初始化的時(shí)候,使用self訪問屬性會(huì)調(diào)到子類重寫的(如果有)getter或setter,這就出現(xiàn)了先于子類init訪問其屬性或調(diào)用子類方法的情況,如果子類getter或setter中有一些特殊的處理邏輯,在某些極端情況下就可能出現(xiàn)行為不一致的問題。 由于在init函數(shù)返回前,對象結(jié)構(gòu)和結(jié)構(gòu)是不穩(wěn)定的,在init函數(shù)內(nèi)對任何方法的調(diào)用(尤其是public方法)都應(yīng)該慎之又慎。dealloc同理。

2.2.10 【推薦】在非init和dealloc方法中訪問屬性推薦通過getter方法獲取,不推薦直接使用“_變量名”。
2.2.11 【推薦】在init中不需要直接使用的Property,建議使用lazyloading的方法創(chuàng)建。
2.2.12 【強(qiáng)制】在創(chuàng)建大量臨時(shí)的UIImage,或者 Model 之類的對象的時(shí),用@autoreleasepool使autorelease 對象在結(jié)束時(shí)間釋放,緩解內(nèi)存的壓力。比如:
正例:

NSMutableArray *dataList = [NSMutableArray new];
NSMutableArray *imageList = [NSMutableArray new];
[dataList enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
    @autoreleasepool {
        NSData *data = dataList[idx];
        UIImage *image = [[UIImage alloc] initWithData:data];
        //可能對 image 進(jìn)行一些處理,裁剪之類的
        [imageList addObject:image];
    }
}];

2.2.13 【強(qiáng)制】在使用到 UIScrollView,UITableView,UICollectionView的 Class 中,需要在dealloc方法里手動(dòng)的把對應(yīng)的 delegate, dataSouce置為 nil

  • 防止在scrollView滑動(dòng)時(shí)頁面退出,delegate釋放,出現(xiàn)crash問題
  • 蘋果在iOS9上已經(jīng)將以上類的delegate及datasource由assign改為了weak,如果只支持9.0以上,則不需要手動(dòng)置nil

2.2.14 【推薦】在dealloc中,避免將self作為參數(shù)傳遞。如果被retain住,到下個(gè)runloop周期再次釋放,則會(huì)造成多次釋放crash。

-(void)dealloc{
    [self unsafeMethod:self]; 
    //因?yàn)楫?dāng)前已經(jīng)在self所指向?qū)ο蟮匿N毀階段,如果在unsafeMethod:中將self放到了autorelease pool中,那么self會(huì)被retain住,計(jì)劃下個(gè)runloop周期再進(jìn)行銷毀;但是dealloc運(yùn)行結(jié)束后,self對象的內(nèi)存空間就直接被回收了,self變成了野指針
    //當(dāng)?shù)搅讼聜€(gè)runloop周期,self指向的對象實(shí)際上已經(jīng)被銷毀,會(huì)因?yàn)榉欠ㄔL問造成crash問題
}

2.2.15 【推薦】除非是非法參數(shù)等提前判斷提前return的可以寫在最前面。其他的return建議有效返回值盡量只剩最后一個(gè)。提前return時(shí),要注意是否有對象沒有被釋放(常見的有CF對象),是否有鎖沒有釋放等配對問題。

2.2.16 【強(qiáng)制】禁止一次性申請超過10MB的內(nèi)存。

  • 內(nèi)存過高將會(huì)導(dǎo)致app被kill,并且沒有crash堆棧。而申請大內(nèi)存將會(huì)增加內(nèi)存峰值,更容易出現(xiàn)內(nèi)存過高而crash。

2.3 集合

  • 包括,但不限于 NSMutableDictionay,NSMutableArray,NSMutableSet

2.3.1 【強(qiáng)制】插入對象需要做判空處理。

2.3.2 【強(qiáng)制】注意線程安全問題,必要時(shí)加鎖,保障線程安全

2.3.3 【強(qiáng)制】先copy,再枚舉操作,禁止對非臨時(shí)變量的可變集合進(jìn)行枚舉操作,多線程情況下有可能因?yàn)榭勺兗显谶M(jìn)行枚舉時(shí)發(fā)生改變進(jìn)而crash。

正例:

- (void)checkAllValidItems{
    [_arrayLock lock];
    NSArray *array = [oldArray copy];
    [_arrayLock unlock];
    [array enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
        //do something using obj
    }];
}

反例:

-(void)checkAllValidItems{
    [self.allItems enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
         //do something using obj
         //如果在enumerate過程中其它線程對self.allItems進(jìn)行了變更操作,這里就會(huì)引發(fā)crash
     }];
}

2.3.4 【推薦】大部分情況下都不使用可變集合作為成員變量,如果確實(shí)需要進(jìn)行集合的增刪改操作,使用臨時(shí)可變集合變量處理,之后再進(jìn)行賦值操作。

2.3.5 【強(qiáng)制】禁止返回mutable對象,禁止mutable對象作為入?yún)鬟f。

2.3.6 【推薦】如果使用NSMutableDictionary作為緩存,推薦使用NSCache代替

2.3.7 【推薦】容器類使用泛型來指定對象的類型

正例:

@property (readonly) NSArray<NSURL *> *imageURLs;
NSDictionary<NSString *, NSNumber *> *mapping = @{@"a": @1, @"b": @2};

反例:

@property (readonly) NSArray *imageURLs;
NSDictionary<NSString *, NSNumber *> *mapping = @{@"a": @1, @"b": @2};

2.4 字符串

2.4.1 【推薦】當(dāng)使用keypaths:@"xx"時(shí)候,盡量使用NSStringFromSelector(@selector(xx))方式,防止某個(gè)key被刪除后沒有編譯感知

2.4.2 【強(qiáng)制】取substring的時(shí)候要考慮emoji字符的問題,防止截到中間crash

- (NSString *)dt_substringToIndex:(NSUInteger)index {
    //... 越界判斷
    NSRange wRange = [self rangeOfComposedCharacterSequencesForRange:NSMakeRange(0, index)];
    return [self substringWithRange:wRange];
 }

2.5 鎖

2.5.1 【推薦】專鎖專用,一個(gè)lock對象只負(fù)責(zé)一個(gè)任務(wù)。這樣可以在邏輯上進(jìn)行區(qū)分,也可以避免潛在的死鎖問題

2.5.2 【推薦】不同鎖的使用場景:

  • 性能最好的屬pthread_mutex、dispatch_semaphore,另外dispatch_semaphore在等待的時(shí)候會(huì)釋放CPU資源,所以適合用在等待耗時(shí)較長的場景;
  • @synchronized是最簡單易用的遞歸鎖,不會(huì)有忘記unlock的情況,但性能也是最低的,適合用在對性能要求不高的場景;
  • 其他的還有NSLock,性能介于上面二者之間,也有對應(yīng)的條件鎖NSConditionLock和遞歸鎖NSRecursiveLock,因?yàn)槭荗bjective-C對象,適合用在偏Objective-C編程的場景,比如需要把鎖存放在NSDictionary中的場景。

2.5.3 【強(qiáng)制】在使用鎖的過程中如果要return,切記要先進(jìn)行unlock; 如果可能有exception發(fā)生,那么需要在@finally中進(jìn)行鎖的釋放

正例:

- (void) exclusiveMethod1{
    [self.lock lock];
    if (condition == true){
        //這里要記得unlcok,否則下次在進(jìn)入這個(gè)方法就會(huì)發(fā)生線程被死鎖的問題
        [self.lock unlock];
        return;
    }
    [self.lock unlock];
}
- (void) exclusiveMethod2{
    [self.lock lock];
    @try{
        //異常發(fā)生
    }@catch(NSException* ex){
    }@finally{
        //此處需要進(jìn)行鎖的回收
        [self.lock unlock];
    }
}

2.6 IO

2.6.1 【參考】盡量減少使用NSUserDefault

2.6.2 【推薦】[[NSUserDefaults standardUserDefaults] synchronize]會(huì)block當(dāng)前線程直到所有UserDefault里的內(nèi)容寫回存儲(chǔ);如果內(nèi)容過多,重復(fù)調(diào)用的話會(huì)嚴(yán)重影響性能。建議只有在合適的時(shí)候(比如退到后臺(tái))再進(jìn)行持久化操作(此方法即將deprecated,可以不再調(diào)用)

2.6.3 【推薦】一些經(jīng)常被讀取的本地文件建議做好內(nèi)存緩存,減少IO開銷

2.6.4 【推薦】文件存儲(chǔ)路徑請遵循以下規(guī)則:

  • Documents目錄:您應(yīng)該將所有的應(yīng)用程序數(shù)據(jù)文件寫入到這個(gè)目錄下。這個(gè)目錄用于存儲(chǔ)用戶數(shù)據(jù)。該路徑可通過配置實(shí)現(xiàn)iTunes共享文件??杀籭Tunes備份。
  • AppName.app 目錄:這是應(yīng)用程序的程序包目錄,包含應(yīng)用程序的本身。由于應(yīng)用程序必須經(jīng)過簽名,所以您在運(yùn)行時(shí)不能對這個(gè)目錄中的內(nèi)容進(jìn)行修改,否則可能會(huì)使應(yīng)用程序無法啟動(dòng)。
  • Library目錄:這個(gè)目錄下有兩個(gè)子目錄:
    ** Preferences 目錄:包含應(yīng)用程序的偏好設(shè)置文件。您不應(yīng)該直接創(chuàng)建偏好設(shè)置文件,而是應(yīng)該使用NSUserDefaults類來取得和設(shè)置應(yīng)用程序的偏好.
    ** Caches 目錄:用于存放應(yīng)用程序?qū)S玫闹С治募?,保存?yīng)用程序再次啟動(dòng)過程中需要的信息。 可創(chuàng)建子文件夾??梢杂脕矸胖媚M粋浞莸幌M挥脩艨吹降臄?shù)據(jù)。該路徑下的文件夾,除Caches以外,都會(huì)被iTunes備份。
  • tmp 目錄:這個(gè)目錄用于存放臨時(shí)文件,保存應(yīng)用程序再次啟動(dòng)過程中不需要的信息。該路徑下的文件不會(huì)被iTunes備份。

2.7 UI

2.7.1 【推薦】不要在除了viewDidLoad方法之外調(diào)用ViewController的self.view來進(jìn)行view操作,特別是在一些系統(tǒng)通知之類的回調(diào)中,有可能造成self.view創(chuàng)建出來之后沒有被加入到當(dāng)前層級,導(dǎo)致子view的詭異問題。

- (void)didReceiveMemoryWarning{
    [super didReceiveMemoryWarning];
    [self.view doSomething]; //如果當(dāng)VC已經(jīng)被創(chuàng)建,但是view還沒有加入到view層級中時(shí)(比如Tabbar初始化之后的非選中VC),此時(shí)接收到了內(nèi)存警告,那么self.view會(huì)被直接創(chuàng)建,沒有加入到層級,導(dǎo)致其子view可能處于異常的狀態(tài)
}

2.7.2 【推薦】如果想要獲取app的window,不要view.window來獲取,可以使用[[UIApplication sharedApplication] keyWindow]來獲取。

  • 如果view不在展示時(shí),獲取window會(huì)是nil,而不是真正的app所在的window.

2.7.3 【強(qiáng)制】UI對象只允許在主線程訪問。(避免在異步線程里釋放,這樣可以避免在dealloc時(shí)訪問view結(jié)構(gòu)導(dǎo)致問題)

2.7.4 【強(qiáng)制】禁止在ViewController的dealloc方法中訪問self.view,會(huì)導(dǎo)致已經(jīng)釋放的view被再次重建,可能會(huì)造成各種不可預(yù)知的問題

2.7.5 【強(qiáng)制】顯示帶textfield的alert之前,一定要確保鍵盤不在顯示狀態(tài),否則會(huì)crash

  • 可以直接: [[[UIApplication sharedApplication].delegate window] endEditing:YES];

2.7.6 【強(qiáng)制】禁止使用drawViewHierarchyInRect截屏

  • 原因:截屏?xí)拇髢?nèi)存和耗性能,不建議使用該技術(shù)方案.
  • 推薦使用 snapshotViewAfterScreenUpdates

2.7.7 【推薦】不建議將UIView類的對象加入到NSDictionary, NSSet,如有需要可以添加到NSMapTable 和 NSHashTable。

  • NSDictionary,NSSet會(huì)對加入的對象做strong引用,而NSMapTable、NSHashTable會(huì)對加入的對象做weak引用。

2.8 Category

2.8.1 【強(qiáng)制】category方法加自定義前綴。防止與其它人沖突。

正例:

@interface NSString(CYYEncode)
- (NSString *)cyy_urlEncode;
@end

反例:

@interface NSString(Encode)
- (NSString*)encode;
@end

2.8.2 【強(qiáng)制】禁止category方法覆蓋系統(tǒng)方法,防止出現(xiàn)方法調(diào)用的不確定性

2.8.3 【推薦】對于一些提供category的工具庫,建議根據(jù)不同類型功能拆分成不同的子bundle,方便引用方按需引用,控制App體積

2.8.4 【強(qiáng)制】Category的源文件名稱必須是“類名+擴(kuò)展名.{h,m}”

正例:

NSString+CYYEncode.h

反例:

NSStringCYYEncode.h
NSString_CYYEncode.h

2.9 異常

2.9.1 【強(qiáng)制】不要在@finally塊中使用return或者@throw等導(dǎo)致方法執(zhí)行中斷的語句,會(huì)導(dǎo)致@try內(nèi)的return失效


2.10 其它

2.10.1 【推薦】使用Method swizzle之前考慮是否有其他方法可以代替,禁止隨意swizzle其他基礎(chǔ)庫及三方庫的方法

2.10.2 【強(qiáng)制】NSNotification接口,userInfo和object的使用要規(guī)范。

  • object通常是指發(fā)出notification的對象,如果在發(fā)送notification的同時(shí)要傳遞一些信息,請使用userInfo,而不是object.

2.10.3 【強(qiáng)制】網(wǎng)絡(luò)返回?cái)?shù)據(jù)在客戶端需要轉(zhuǎn)為 NSString 類型,在作為參數(shù)返回時(shí)轉(zhuǎn)為接口需要的指定類型。避免應(yīng)用內(nèi)模塊之間傳遞數(shù)據(jù)時(shí)不必要的類型轉(zhuǎn)換。

2.10.4 【推薦】在使用固定格式的dateFormatter時(shí)候,需要設(shè)置setLocale為"en_US_POSIX",防止一些不同日歷下格式異常。

示例:

NSDate* now = [NSDate date];
NSDateFormatter* fmt = [[NSDateFormatter alloc] init];
fmt.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
NSString* string = "1996-12-19T16:39:57-08:00";
NSDate* date = fmt.dateFromString(string);

2.10.5 【推薦】在使用CTTelephonyNetworkInfo的時(shí)候,務(wù)必使用全局的單例實(shí)例,這個(gè)類本身存在bug,如果有多實(shí)例會(huì)存在會(huì)導(dǎo)致小概率的crash。

2.10.6 【強(qiáng)制】調(diào)用block時(shí)務(wù)必判斷block是否為nil

2.10.7 【推薦】調(diào)用delegate的optional方法時(shí),判斷delegate能否響應(yīng)該方法,避免crash

2.10.8 【強(qiáng)制】禁止訪問對象的結(jié)構(gòu)體變量(使用->)

2.10.9 【強(qiáng)制】需要使用磁盤緩存的業(yè)務(wù),務(wù)必提供清理緩存的能力

2.10.10 【強(qiáng)制】對于不確定對象類型的比較,可以使用isEqual:方法,其會(huì)對類型進(jìn)行判斷;對于確定對象類型的比較,比如NSString,可以使用isEqualToString:,其不對類型進(jìn)行判斷,但相比前者性能更好


三. 工程規(guī)約

3.1 版本管理規(guī)約

3.1.1 【建議】遵循語義化版本號規(guī)范,版本格式:主版本號.次版本號.修訂號,版本號遞增規(guī)則如下:

  • 主版本號:當(dāng)你做了不兼容的 API 修改
  • 次版本號:當(dāng)你做了向下兼容的功能性新增,
  • 修訂號:當(dāng)你做了向下兼容的問題修正。
  • 先行版本號及版本編譯信息可以加到“主版本號.次版本號.修訂號”的后面,作為延伸。

3.1.2 【建議】App灰度使用四位版本號

3.1.3【建議】業(yè)務(wù)方維護(hù)自己業(yè)務(wù)SDK的版本號,不要使用主App的版本號來做業(yè)務(wù)邏輯判斷,如果有需要可以使用業(yè)務(wù)SDK的版本號來判斷


3.2 分支管理

3.2.1 【建議】主分支Master

  • 代碼庫應(yīng)該有一個(gè)、且僅有一個(gè)主分支。所有提供給用戶使用的正式版本,都在這個(gè)主分支上發(fā)布。

3.2.2 【【建議】開發(fā)分支Develop

  • 日常開發(fā)分支在Develop,如果想正式對外發(fā)布,就在Master分支上,對Develop分支進(jìn)行"合并"(merge)。

3.2.3 【建議】臨時(shí)性分支,按不同的需求,開啟相應(yīng)的臨時(shí)分支,使用完以后,應(yīng)該刪除

  • 功能(feature)分支
  • 預(yù)發(fā)布(release)分支
  • 修補(bǔ)bug(fixbug)分支
  • 建議使用GitFlow進(jìn)行代碼管理。

3.2.4 【強(qiáng)制】每次版本發(fā)布之后,都應(yīng)該在代碼倉庫中對應(yīng)的節(jié)點(diǎn)添加tag,保證版本的可回溯

3.2.5 【參考】在 Git 提交時(shí)可以使用 [添加],[修改],[刪除],[修復(fù)],[更新]等前綴詞語來表明當(dāng)前的Commit 信息。

3.3 包管理

3.3.1 【強(qiáng)制】使用CocoaPods作為包管理工具

3.3.2 【參考】推薦使用 source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git'

3.3.3 【強(qiáng)制】外部 pod 倉庫引用務(wù)必使用固定版本號,僅在必要時(shí)更新指定版本號

3.3.4 【強(qiáng)制】檢查podspec的resource選項(xiàng),不要把Podfile、podspec、InfoPlist.strings、Info.plist或者源文件等導(dǎo)出到使用方的工程中

3.3.5 【強(qiáng)制】模塊引用使用自上往下方式,下層模塊禁止引用上層模塊,基礎(chǔ)模塊禁止引用其他模塊。如果在進(jìn)行模塊開發(fā)更新過程中發(fā)現(xiàn)需要違背此原則,則需要思考是否有需要新增必要的模塊。

3.3.6 【推薦】使用carthage進(jìn)行包管理工具

3.3.7 【推薦】純Swift工程時(shí),可以使用SwiftPackage


四. 示例


五. 參考


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 阿里巴巴 JAVA 開發(fā)手冊 1 / 32 Java 開發(fā)手冊 版本號 制定團(tuán)隊(duì) 更新日期 備 注 1.0.0 阿...
    糖寶_閱讀 7,891評論 0 5
  • 團(tuán)隊(duì)的Objective-C代碼規(guī)范。本文主要內(nèi)容來自raywenderlich.com Objective-C編...
    猿類素?cái)?/span>閱讀 947評論 1 3
  • 0. 前言 "代碼是寫給人看的" 例子?? 1. 布局與風(fēng)格 良好布局的目的 準(zhǔn)確表現(xiàn)代碼的邏輯結(jié)構(gòu) 始終如一地表現(xiàn)...
    kim4apple閱讀 1,073評論 0 0
  • 【按語】由于我公司正在準(zhǔn)備開發(fā)新的App,到時(shí)可能有些iOS開發(fā)者參與進(jìn)來。這時(shí)如果每個(gè)人的Objective-C...
    niu神DNS閱讀 1,092評論 2 14
  • 太陽把一切安放在地上 像云朵睡在天空里 那些站立的事物 有些像羽毛,有些像盔甲 有些像父親和媽媽 秋天慢慢把太陽帶...
    歌行者李江華閱讀 254評論 1 1

友情鏈接更多精彩內(nèi)容