[譯] Objective-C 中 9 種避免使用 Xcode 預(yù)處理器宏的方法

除了極少數(shù)例外权谁,使用 Xcode 預(yù)處理器宏是一種代碼氣味贞谓。C++ 程序員們已經(jīng)深有體會(huì):"不要使用預(yù)處理器來(lái)做語(yǔ)言本身提供的事情"铲汪。不幸的是斤富,還有很多的 Objective-C 程序員尚未領(lǐng)悟到這一點(diǎn)膏潮。

本文是Objective-C 中的代碼氣味系列文章中的一篇。

這是一個(gè)可以在終端運(yùn)行的便捷命令满力。它可以檢查并顯示當(dāng)前目錄下的源文件焕参,預(yù)處理器宏的使用情況轻纪,你應(yīng)該仔細(xì)檢查。

find . \( \( -name "*.[chm]" -o -name "*.mm" \) -o -name "*.cpp" \) -print0 | xargs -0 egrep -n '^\w*\#' | egrep -v '(import|pragma|else|endif)'

該命令包含一些例外情況叠纷。例如刻帚,#import 指令至關(guān)重要。......但我想對(duì)幾乎所有其他內(nèi)容提出質(zhì)疑涩嚣!這有什么關(guān)系呢崇众?因?yàn)槊看问褂妙A(yù)處理器時(shí),你看到的并不是你編譯的內(nèi)容航厚。對(duì)于作為常量使用的 #define 宏校摩,我們需要避免一些陷阱——其實(shí)我們完全可以避免這些陷阱。

以下是一些常見(jiàn)的 Xcode 預(yù)處理器宏阶淘,以及如何替換它們:

1、#include

讓我們從傳統(tǒng) C 中的一個(gè)簡(jiǎn)單例子開(kāi)始:
Smell

#include "foo.h"

除非您提供的是平臺(tái)無(wú)關(guān)的 C 或 C++ 代碼互妓,否則沒(méi)有理由使用 #include 以及與之一起的 include guards溪窒。使用 #import 可以省去那些 include guards的 #ifndef

2冯勉、Macros - 宏

Smell

#define WIDTH(view) view.frame.size.width

使用 Objective-C 并不意味著不能使用普通的 C 語(yǔ)言函數(shù)澈蚌!除非您的自定義宏依賴于 Xcode 預(yù)處理器宏(如__LINE__),否則請(qǐng)將其重寫為一個(gè)獨(dú)立函數(shù)灼狰。(即便依賴于 Xcode 預(yù)處理宏宛瞄,也要讓您的宏調(diào)用另一個(gè)函數(shù),并盡可能多地轉(zhuǎn)移到該函數(shù)中)交胚。

C 語(yǔ)言和 C++ 的有一些相似的地方份汗。其中之一就是內(nèi)聯(lián)函數(shù)的能力:

static inline CGFloat width(UIView *view) { return view.frame.size.width; }

3、常量:數(shù)字常量

現(xiàn)在蝴簇,我們開(kāi)始使用一組圍繞常量的 Xcode 預(yù)處理器宏杯活。使用常量而不是重復(fù)字面值是值得稱贊的。而使用 #define 創(chuàng)建常量則不值得稱贊熬词。
Smell

#define kTimeoutInterval 90.0

如果一個(gè)常量只在單個(gè)文件中使用旁钧,則應(yīng)將其設(shè)置為靜態(tài)常量。我們賦予常量一個(gè)明確的類型互拾,增加了它的語(yǔ)義歪今。如果你愿意,數(shù)字字面的表達(dá)也可以更簡(jiǎn)單颜矿,因?yàn)轱@式類型明確了可接受的值域寄猩。下面就是我們得到的結(jié)果:

static const NSTimeInterval kTimeoutInterval = 90;

如果一個(gè)常量是跨文件共享的,那么就像處理其他文件一樣:在頭文件中創(chuàng)建一個(gè)聲明或衡,在一個(gè)實(shí)現(xiàn)文件中創(chuàng)建一個(gè)定義焦影。(當(dāng)然车遂,你要遵循蘋果公司的編碼指南,在名稱上使用前綴斯辰,對(duì)嗎舶担?)因此,.h 文件中將包含如下聲明:

extern const NSTimeInterval JMRTimeoutInterval;

.m文件中有定義:

const NSTimeInterval JMRTimeoutInterval = 90;

4彬呻、常量: 升序整數(shù)常量

Smell

#define firstNameRow 0
#define lastNameRow 1
#define address1Row 2
#define cityRow 3
// etc.

升序整數(shù)常量在編碼表格視圖時(shí)非常方便衣陶,可以確定哪些信息屬于哪個(gè)單元格。......這就是枚舉類型的作用闸氮。

enum {
    firstNameRow,
    lastNameRow,
    address1Row,
    cityRow,
    // etc.
};

枚舉類型可以方便地重新排列順序或添加新值剪况。一般來(lái)說(shuō),人們使用 #define 是因?yàn)闃?gòu)造一個(gè)危險(xiǎn)的宏比構(gòu)造一個(gè)安全的常量更容易蒲跨。但在這里译断,語(yǔ)言所提供的不僅更安全,而且更簡(jiǎn)單或悲。

枚舉類型不必命名孙咪。但如果將這些值作為參數(shù)傳遞,就需要定義一個(gè)類型名巡语,以增加編譯器檢查和語(yǔ)義翎蹈。與其在所有需要使用 Address 枚舉類型的地方都寫 enum Address,不如創(chuàng)建一個(gè)這樣的類型定義:

typedef enum {
    firstNameRow,
    lastNameRow,
    address1Row,
    cityRow,
    // etc.
} AddressRow;

5男公、常量:字符串常量

Smell

#define JMRResponseSuccess @"Success"

與數(shù)字常量一樣荤堪,使用語(yǔ)言來(lái)定義常量。只不過(guò)枢赔,這次我們定義的是一個(gè)常量字符串澄阳,它實(shí)際上是一個(gè)對(duì)象,在 Objective-C 中表示為指針踏拜。因此寇荧,我們要定義一個(gè)常量指針。

常量字符串通常在多個(gè)文件中共享执隧,因此這里介紹如何在 .h 文件中聲明常量:

extern NSString *const JMRResponseSuccess;

因此揩抡,.m 文件中的定義是

NSString *const JMRResponseSuccess = @"Success";

6、條件編譯:注釋代碼

各種形式的條件編譯(#if镀琉、#ifdef 等)是一種選擇性啟用或禁用代碼塊的方法峦嗤。它用于不同的目的,但始終是一種\color{Red}{鈍器}屋摔。
Smell

#if 0
…
#endif

在以前的 C 語(yǔ)言中烁设,唯一的注釋形式是 /* ... */。要注釋一段代碼,可以在前面加上 /*装黑,在后面加上 */副瀑。后來(lái)有人發(fā)現(xiàn),如果代碼中已經(jīng)包含了注釋恋谭,這種方法就不起作用了糠睡。怎么辦呢?當(dāng)時(shí)的答案是使用預(yù)處理器:用 #if 0 封裝代碼就可以了疚颊。

但那是很久以前的事了狈孔,那時(shí)還沒(méi)有現(xiàn)代集成開(kāi)發(fā)環(huán)境和彩色編碼方式。顏色編碼可以幫助我們更直觀地解析代碼......但在這種情況下并不適用材义。盡管在這種情況下有一個(gè) 0均抽,但一般來(lái)說(shuō),集成開(kāi)發(fā)環(huán)境無(wú)法知道是否要顯示條件編譯刪除了源文件中的某段代碼其掂。因此油挥,沒(méi)有任何可視化指示器顯示代碼被注釋掉了!它看起來(lái)就像其他代碼一樣款熬。

C 和 Xcode 快速發(fā)展到今天喘漏。C 語(yǔ)言不斷發(fā)展,并采用了 C++ 的 // 注釋風(fēng)格华烟。Xcode 充分利用了這一點(diǎn),并在菜單中提供了 "注釋選擇 "命令持灰。只需按?/ 即可注釋出代碼的一部分:Xcode 會(huì)在每一行的開(kāi)頭添加 // 并用顏色標(biāo)記為注釋盔夜。再次按下 ?/,過(guò)程就會(huì)逆轉(zhuǎn)堤魁,代碼就會(huì)恢復(fù)原狀喂链。

因此,Xcode 可以輕松啟用和禁用代碼妥泉。但還有一個(gè)問(wèn)題椭微,我們將在下一節(jié)中討論:如果注釋掉的代碼是臨時(shí)性的,并且您計(jì)劃很快將其清理干凈盲链,那么注釋掉代碼是沒(méi)有問(wèn)題的蝇率。但通常情況下,這些代碼會(huì)被丟在那里任其腐爛......

7刽沾、條件編譯:在實(shí)驗(yàn)之間切換

Smell

#if EXPERIMENT
…
#else
…
#endif

有時(shí)本慕,您需要進(jìn)行實(shí)驗(yàn)性編碼〔嗬欤或者你想快速在兩種方法之間來(lái)回切換锅尘。這很好。

但在某些時(shí)候布蔗,我們會(huì)做出決定藤违。實(shí)驗(yàn)方法得到驗(yàn)證浪腐,你就可以準(zhǔn)備發(fā)貨了。自行清理之后顿乒!除非有重要的歷史原因需要將被拒絕的代碼作為注釋保留议街,否則請(qǐng)將其刪除。如果您選擇保留淆游,請(qǐng)刪除 Xcode 預(yù)處理器宏傍睹。將它變成真正的注釋,并附上解釋犹菱,而不僅僅是代碼拾稳。

8、 條件編譯: 在暫存和生產(chǎn) URL 之間切換

Smell

#if STAGING
static NSString *const fooURLString = @"https://dev.foo.com/services/fooservice";
#else
static NSString *const fooURLString = @"https://foo.com/services/fooservice;
#endif

當(dāng)你開(kāi)發(fā)基于服務(wù)的應(yīng)用程序時(shí)腊脱,你希望能夠指定是與真正的生產(chǎn)服務(wù)對(duì)話访得,還是與暫存服務(wù)對(duì)話。

對(duì)于只有少量 URL 的簡(jiǎn)單應(yīng)用程序陕凹,我會(huì)為 URL 創(chuàng)建一個(gè)類悍抑,然后通過(guò)方法訪問(wèn)它們:

- (NSString *)fooURLString
{
    DebugSettings *debugSettings = [self debugSettings];
    if ([debugSettings usingStaging])
        return @"https://dev.foo.com/services/fooservice";
    else
        return @"https://dev.foo.com/services/fooservice";
}

對(duì)于與許多服務(wù)對(duì)話的復(fù)雜應(yīng)用程序,可以考慮將 URL 放入 plist 中杜耙。有關(guān) plist 的示例搜骡,請(qǐng)參閱《我如何在暫存和生產(chǎn) URL 之間切換(How I Switch between Staging and Production URLs)》。

9佑女、條件編譯:支持多個(gè)項(xiàng)目或平臺(tái)

Smell

#if PROJECT_A
…
#else
…
#endif

在多個(gè)項(xiàng)目(或多個(gè)平臺(tái))中共享代碼時(shí)记靡,很容易在共享源文件中偷偷加入特定于項(xiàng)目的擴(kuò)展。這樣做看似方便团驱,但會(huì)污染源代碼摸吠,并掩蓋統(tǒng)一代碼的機(jī)會(huì)。

我們使用的是面向?qū)ο蟮恼Z(yǔ)言嚎花,所以讓我們使用 OO 模式寸痢,好嗎?基本策略是將包含項(xiàng)目特定代碼的方法改寫為模板方法(Template Methods)紊选,由項(xiàng)目特定的子類提供項(xiàng)目特定的操作啼止。

步驟

  • 為每個(gè)項(xiàng)目變量創(chuàng)建一個(gè)子類。
  • 在每個(gè)項(xiàng)目中兵罢,為該項(xiàng)目添加子類族壳。
  • 編譯每個(gè)項(xiàng)目。
  • 創(chuàng)建一個(gè)工廠方法趣些,使用 #if 創(chuàng)建正確的子類仿荆。(我們引入預(yù)處理器的一種用法,這樣就可以消除其他用法)。
  • 找到每個(gè)實(shí)例化原始類的地方拢操。讓它調(diào)用工廠方法锦亦。
  • 編譯和測(cè)試每個(gè)項(xiàng)目。
  • 對(duì)于每個(gè)有條件編譯的部分:
    • 執(zhí)行提取方法令境,確定所需的簽名杠园。
    • 將主體的每個(gè)平臺(tái)特定部分向下移動(dòng)到平臺(tái)特定子類,直到基類的方法為空舔庶。
    • 編譯和測(cè)試每個(gè)項(xiàng)目抛蚁。
  • 查找每個(gè)子類內(nèi)部以及子類之間的重復(fù)代碼

如果你的代碼中存在多個(gè)特定于平臺(tái)的子類層次結(jié)構(gòu)惕橙,你可能會(huì)發(fā)現(xiàn)使用橋接模式的機(jī)會(huì)瞧甩。

避免使用 Xcode 預(yù)處理器宏!

請(qǐng)?jiān)俅卧诮K端中執(zhí)行此命令弥鹦,以查找代碼中可能違規(guī)的 Xcode 預(yù)處理器宏肚逸。您找到了多少?能否減少它們彬坏?剩余的宏是否合理朦促?

請(qǐng)記住不要使用 Xcode 預(yù)處理器宏來(lái)做語(yǔ)言本身提供的事情!

譯自 Jon Reid 的 9 Ways You Can Avoid ObjC Xcode Preprocessor Macros
侵刪

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市栓始,隨后出現(xiàn)的幾起案子务冕,更是在濱河造成了極大的恐慌,老刑警劉巖幻赚,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件禀忆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坯屿,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門巍扛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)领跛,“玉大人,你說(shuō)我怎么就攤上這事撤奸》驼眩” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵胧瓜,是天一觀的道長(zhǎng)矢棚。 經(jīng)常有香客問(wèn)我,道長(zhǎng)府喳,這世上最難降的妖魔是什么蒲肋? 我笑而不...
    開(kāi)封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上兜粘,老公的妹妹穿的比我還像新娘申窘。我一直安慰自己,他們只是感情好孔轴,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布剃法。 她就那樣靜靜地躺著,像睡著了一般路鹰。 火紅的嫁衣襯著肌膚如雪贷洲。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天晋柱,我揣著相機(jī)與錄音优构,去河邊找鬼。 笑死趣斤,一個(gè)胖子當(dāng)著我的面吹牛俩块,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浓领,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼玉凯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了联贩?” 一聲冷哼從身側(cè)響起漫仆,我...
    開(kāi)封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泪幌,沒(méi)想到半個(gè)月后盲厌,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祸泪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年吗浩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片没隘。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡懂扼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出右蒲,到底是詐尸還是另有隱情阀湿,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布瑰妄,位于F島的核電站陷嘴,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏间坐。R本人自食惡果不足惜灾挨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一邑退、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧涨醋,春花似錦瓜饥、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至溯警,卻和暖如春趣苏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背梯轻。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工食磕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喳挑。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓彬伦,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親伊诵。 傳聞我的和親對(duì)象是個(gè)殘疾皇子单绑,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容