Objective-C語言(一)熟悉Objective-C

本系列是根據《Effective Objective-C 2.0》一書中的系列文章,選開發中實踐的經驗之談,聚集於此,便於查閱,或者爲來訪者提供一份參考。編排按《Effective Objective-C 2.0》中條目。html

1、最佳實踐

  • 在類的頭文件中儘可能少引用其餘頭文件;
  • 多用字面量語法,少用與之等價的方法;
  • 多用類型常量,少用#define預處理指令;
  • 用枚舉表示狀態、選項、狀態碼;

2、實踐詳解

2.1 頭文件

在類的頭文件中儘可能少引用其餘頭文件程序員

@classobjective-c

"向前聲明"或「前向引用」,僅僅是聲明一個類名,並不會包含類的完整聲明。 @class還能解決循環包含的問題。在實現這個接口的實現類中,若是須要引用這個類的實體變量或者方法之類的,仍是須要#import@class中聲明的類進來。express

那麼爲何還須要@class呢?由於做聲明某個類來用,編譯器並不會將類的實例變量或方法引入,其能夠加快編譯,減小編譯時間。數組

另外,在實際中,會遇到——當解析某個文件時,編譯器會發現它引入了另外一個頭文件,而那個頭文件又回過頭來引入了第一個頭文件——循環包含,這時候,使用#import而非#include指令雖然不會致使死循環,但卻意味着兩個類裏有一個沒法被正確編譯。這時候,採用@class僅做聲明。安全

  • #import#include都能完整地包含某個文件的內容,#import能防止同一個文件被包含屢次;
  • #import <> 用來包含系統自帶的文件,#import 「」用來包含自定義的文件
  • 除非確有必要,不然不要引入頭文件。通常來講,應在某個類的頭文件中使用向前聲明來說起別的類,並在實現文件中引入那些類的頭文件。以此來儘可能下降類之間的耦合。
  • 要聲明某個類遵循一項協議,精良移至「class continuation」分類中實現。若是不行,就把該協議單獨放入一個頭文件中,而後將其引入。

2.2 字面量

多用字面量語法,少用與之等價的方法。app

字面量語法,實際上是Objective-C 2.0添加的「語法糖」,方便程序員書寫,提升可讀性以及編譯時檢查等特性。框架

其中,涉及到類NSStringNSNumberNSArrayNSDictionarypost

字面量字符串

NSString *siteTitle = @"Mobiletuts+";
//假如不用字面量語法,那麼上面可能會寫成
NSString *siteTitle = [NSString stringWithUTF8String:"Mobiletuts"];
複製代碼

字面量數值

NSNumber *boolYES = [NSNumber numberWithBool:YES];
NSNumber *boolNO  = [NSNumber numberWithBool:NO];
     
NSNumber *charX = [NSNumber numberWithChar:'X'];
     
NSNumber *fortySevenInt = [NSNumber numberWithInt:47];
NSNumber *fortySevenUnsigned = [NSNumber numberWithUnsignedInt:47U];
NSNumber *fortySevenLong = [NSNumber numberWithLong:47L];
     
NSNumber *goldenRatioFloat = [NSNumber numberWithFloat:1.61803F];
NSNumber *goldenRatioDouble = [NSNumber numberWithDouble:1.61803];
複製代碼

採用字面量語法,上面可寫爲:spa

NSNumber *boolYES = @YES;
NSNumber *boolNO  = @NO;
     
NSNumber *charX = @'X';
     
NSNumber *fortySevenInt = @47;
NSNumber *fortySevenUnsigned = @47U;
NSNumber *fortySevenLong = @47L;
     
NSNumber *goldenRatioFloat = @1.61803F;
NSNumber *goldenRatioDouble = @1.61803;
複製代碼

字面量也適用於下述表達式:

int x = 5
float y = 6.32f
NSNumber *expressionNumber = @{x * y}
複製代碼

字面量數組

NSArray *instruments = [NSArray arrayWithObjects: @"Ocarina", @"Flute", @"Harp", nil];
複製代碼

使用字面量語法來建立:

NSArray *instruments = @[ @"Ocarina", @"Flute", @"Harp" ];
複製代碼

假如,要聲明一個NSMutableArray數組,能夠採用下面: ​ ​ NSMutableArray *instrumentsMutable = [ @[ @"Ocarina", @"Flute", @"Harp" ] mutableCopy];

最後,須要注意的是,在用字面量語法建立數組時,若數組中有元素有nil,則會拋出異常,由於字面量語法實際上只是一種語法糖,其效果等同因而先建立了一個數組,而後把方括號內的多有對象都加到這個數組中。拋出的異常以下:

*** Terminating app due to uncaught exception 'NSInvalidArgumentException',reason:'*** - [__NSPlaceholderArray initWithObjects:count:]:attempt to insert nil object form objects[0]'
複製代碼

在改用字面量語法來建立數組時就會遇到這個問題,下面這段代碼分別以兩種語法建立數組:

id object1 = /*...*/
id object2 = /*...*/
id object3 = /*...*/

NSArray *arrayA = [NSArray arrayWithObjects:object1,object2,object3,nil];
NSArray *arrayB = @[object1,object2,object3];
複製代碼

假如,object1object3都指向了有效對象,而object2nil,會出現什麼狀況?

按字面量建立的arrayB會拋出異常,arrayA雖然能建立出來,可是其中只有object1一個對象。緣由在於,「arrayWithObjects」方法會一次處理各個參數,直到發現nilobject2nil,因此方法提早結束。

這個微秒的差異代表,使用字面量語法更爲安全。拋出異常令應用程序終止執行,這比建立好數組以後才發現元素個數少了要好。

字面量字典

建立一個字典:

NSArray *keys   = [NSArray arrayWithObjects:@"Character", @"Weapon", @"Hitpoints", nil];
NSArray *objects = [NSArray arrayWithObjects:@"Zelda", @"Sword", [NSNumber numberWithInt:50], nil];
NSDictionary *stats = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
複製代碼

或者,採用更簡潔的寫法:

NSDictionary *stats = [NSDictionary dictionaryWithObjectsAndKeys:
                       @"Zelda", @"Character",
                       @"Sword", @"Weapon",
                       [NSNumber numberWithInt:50], @"Hitpoints",
                       nil];
複製代碼

而採用字面量語法:

NSDictionary *stats = @{ @"Character" : @"Zelda",
                     @"Weapon" : @"Sword",
                     @"Hitpoints" : @50 };
複製代碼

一樣,在用字面量語法中,若是值是nil,與字面量建立數組表現類似,會拋出異常。

下標操做

「取下標」操做通常會用objectAtIndex方法:

NSString * flute = [instruments objectAtIndex:1];
NSString * flute = instruments[1];
複製代碼

假如,是可變數組,採用字面量寫法:

instrumentsMutable[0] = @"Ocarina of Time";
//其對應的方法爲:replaceObjectAtIndex:withObject
複製代碼

字典的讀寫也相似。

NSString *name = stats[@"Character"]; // Returns 'Zelda'
statsMutable[@"Weapon"] = @"Hammer";

//分別對應方法:
//objectForKey:
//setObject:forkey:
複製代碼

2.3 預處理

多用類型常量,少用#define預處理指令。

編寫代碼中常將常量寫爲:

#define ANIMATION_DURATION 0.3
複製代碼

預處理過程會把全部ANIMATION_DURATION一概替換成0.3,假設該指令聲明在某個頭文件中,那麼全部引入了這個頭文件的代碼,其ANIMATION_DURATION都會被替換。

更好的方式是:

static const NSTimerInterval kAnimationDuration = 0.3
複製代碼

首先,添加了類型信息,清楚地描述了常量的含義。

其次,要注意常量的名稱,經常使用的命名法是:若常量侷限於某「編譯單元(通常只實現文件,即.m)」以內,則在前面加k;若常量在類以外可見,則一般以「類名」爲前綴。

最後,要注意常量的位置。咱們總喜歡在頭文件裏聲明預處理指令,這是至關糟糕的,當常量名稱有可能互相沖突更是如此。因爲OC沒有「命名空間」這一律念,因此避免將常量聲明放在頭文件裏。即便採用static const這種方式也是如此。若不打算公開某個常量,則應該將其定義在使用該常量的實現文件裏。

那麼,爲何要用staticconst來修飾常量?

static代表的是做用域,意味着該變量盡在定義此變量的編譯單元中可見,編譯器每收到一個編譯單元,就會輸出一份「目標文件」。在Objective-C語境下,「編譯單元」一次一般指每一個類的實現文件。假如聲明此變量時,不加static,那麼編譯器會爲它建立一個「外部符號(external symbol)」。此時,若其餘編譯單元也聲明瞭同名變量,就會拋出一條錯誤消息:

duplicate symbol _kAnimationDuration in:
	EOCAnimatedView.o
	EOCOtherView.o
複製代碼

const則聲明爲不可修改。

實際上,若是一個變量既聲明爲static,又聲明爲const,那麼編譯器根本不會建立符號,而是會像#define預處理指令同樣,把全部遇到的變量頭替換爲常值,可是,這種方式具備類型信息。

那麼,假如要對外公開一個常量要怎麼辦?

對外公開常量

有時候,須要對外公開常量。常見的情景就是在類代碼中調用NSNotificationCenter以通知他人。那麼通知名通常聲明一個外界可見的常值變量。

此類變量須要放在「全局符號表(global symbol table)」中,以即可以在定義該常量的編譯單元以外使用。其定義方式:

//in the header file 
extern NSString *const EOCStringConstant;

//in the implementation file
NSSting *const EOCStringConstant = @"VALUE"
複製代碼

編譯器發現頭文件中含有extern,就知道,在全局符號表中將會有一個EOCStringConstant的符號。即編譯器無須查看其定義,就容許代碼中使用此常量。由於它知道,當連接二進制文件後,確定能找到這個常量。

此類常量必需要定義,並且只能定義一次。一般將其定義在與聲明該常量的頭文件相關的實現文件裏。由實現文件生成目標文件時,編譯器會在「數據段」爲字符串分配存儲空間。

  • 不要用預處理指令來定義常量,它不包含類型信息;
  • 在實現文件中使用static const來定義「只在編譯單元內可見的常量」。因爲此類常量不在全局符號表中,因此無須爲其名稱加前綴;
  • 在頭文件中使用extern來聲明全局變量,並在相關實現文件中定義其值。這種常量要出如今全局符號表中,因此其名稱應加以區隔,一般用與之相關的類名作前綴。

2.4 枚舉

用枚舉表示狀態、選項、狀態碼

enum {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle

};

typedef enum {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle

} UITableViewCellStyle;

typedef NS_ENUM(NSInteger, UITableViewCellStyle) {
    UITableViewCellStyleDefault,
    UITableViewCellStyleValue1,
    UITableViewCellStyleValue2,
    UITableViewCellStyleSubtitle
};

NS_OPTIONS

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
    UIViewAutoresizingNone                 = 0,
    UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
    UIViewAutoresizingFlexibleWidth        = 1 << 1,
    UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
    UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
    UIViewAutoresizingFlexibleHeight       = 1 << 4,
    UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
複製代碼

在iOS開發中,凡是須要以按位或操做來組合的枚舉都應使用NS_OPTIONS定義。

如果枚舉不須要互相組合,則應使用NS_ENUM來定義。

  • 應該用枚舉來表示狀態機的狀態、傳遞給方法的選項以及狀態碼等值,給這些值起個易懂的名字;
  • 若是把傳遞給某個方法的選項表示爲枚舉類型,而多個選項又可同時使用,那麼久將各選項值定義爲2的冪,以便經過按位或操做將其組合起來;
  • NS_ENUMNS_OPTIONS宏定義了來定義枚舉類型,並指明底層數據類型。以確保枚舉是用開發者所選的底層數據類型實現出來的,而不會採用編譯器所選的類型;
  • 在處理枚舉類型的switch語句中不要實現default分支。這樣的話,假如新枚舉以後,編譯器就會提示開發者:switch語句並未處理全部枚舉。

參考

連接

  1. Objective-C Literals Clang 3.9 documentation

  2. Objective-C Literals

書籍

《Effective Objective-C 2.0》

相關文章
相關標籤/搜索