iOS中的先行編譯指令

來源:互聯網
上載者:User

標籤:

iOS中的先行編譯指令的初步探究

目錄[+]

開篇

我們人類創造東西的時候有個詞叫做”仿生學“!人類創造什麼東西都會模仿自己來創造,所以上帝沒有長成樹的樣子而和人長得一樣,科幻片裡面外星人也像人一樣有眼睛有鼻子……但是人類自己創造的東西如果太像自己,自己又會嚇尿(恐怖穀效應),人類真是奇葩;奇葩的我們在20世紀創造了改變世界的東西——電腦(電腦),不用懷疑,這貨當然也是仿生學!這貨哪裡長得像人了??別不服,先聽我說完,先把你的磚頭放下。狹義的仿生學是外形上仿生嘛,其實廣義上仿生學還可以原理的仿生,構造的仿生,效能的仿生阿拉巴拉……,電腦(這裡我狹義的使用個人PC來舉例)我們常說的有輸入裝置(鍵盤呀滑鼠呀網路攝影機呀……)、處理裝置(CPU、GPU……)和輸出裝置(顯示器、音響……);然後你自個兒瞅瞅你自己的眼睛耳朵(輸入),大腦(處理),四肢(輸出) 當初設計電腦必須要這種構造的人難道不是瞅著自己來設計電腦的嗎?^_^

所以上電腦群組成原理的時候有什麼地方晦澀難以理解的時候,我就立刻解禁我高中的生物知識,然後就迎刃而解了~但是今天我這篇部落格是要講程式的呀,這把犢子扯的那麼遠看客們也難免心有憤懣,你切勿急躁,我馬上就帶你們飛!跟著我用仿生學的角度去理解電腦,那麼電腦程式是神馬呢?教科書上怎麼說?可以被電腦執行,那神馬東西會被人執行的呢?老婆的命令、老爸的呵斥、專案經理的需求變更……我們都會執行,貌似這就是人的程式了,這確實就是人的程式!下面我具體拿老婆的命令來詳解一下人得程式的執行過程;比如老婆說了一句”你給我滾出去睡沙發!“,首先這句話的處理流程是這樣的:

圖1

 

 

帶你們看電腦程式執行過程之前,我們要嚴肅的瞭解一點程式的編譯,也就是中的,我們把老婆的命令轉換成電訊號的過程。在電腦世界中有些好事者把這個玩意兒稱作編譯器(compiler),什麼gcc呀clang呀阿拉巴拉,說的編譯器這名字逼格好高~其實說白了就是個翻譯的東西,如我們人執行程式過程中,把老婆的話(也是人類的話)翻譯成大腦懂的話(電波),在電腦中就是把各種程式設計語言(c、c++、oc……)翻譯成0101011……讓電腦懂。編譯器的工作原理基本上都是三段式的,可以分為前端(Frontend)、最佳化器(Optimizer)、後端(Backend)。前端負責解析原始碼,檢查語法錯誤,並將其翻譯為抽象的文法樹(Abstract Syntax Tree)。最佳化器對這一中間代碼進行最佳化,試圖使代碼更高效。後端則負責將最佳化器最佳化後的中間代碼轉換為目標機器的代碼,這一過程後端會最大化的利用目標機器的特殊指令,以提高代碼的效能。

圖2

 

 

為什麼要弄成這三段式的呢?我肯定不會從什麼架構、結構啊最佳化……角度說起,因為我也不懂呀,哈哈 不過我可以講一個過去的故事給大家,大家試想一下編譯器是怎麼開發出來的呀,好傢夥,上網一搜LLVM編譯器是C++寫的,那c++的編譯器呢?其實不用那麼麻煩,現在把你的手借給我,讓我牽著你回到上個世紀70年代,裡奇正在為他新發明的C語言在寫編譯器呢,他在用組合語言!組合語言怎麼編譯變成二進位流呢?答案是使用01011機器碼編寫的編譯器;所以編譯器和電腦語言的進步就像這樣迭代發展的,再之後是用進階語言寫更進階的編譯器,進階的編譯器能編譯更進階的電腦語言……,雖然藍翔的挖掘機技術強,但問題還是來了,世界上電腦那麼多,各種不同的架構,人還好基本架構都一樣,但是電腦有Intel架構的又有ARM架構,怎麼能讓程式設計語言通過編譯分別產生不同架構的執行碼呢?所以這就是編譯器三段式這種模型的好處了,當我們要支援多種語言時,只需要添加多個前端就可以了。當需要支援多種目標機器時,只需要添加多個後端就可以了。對於中間的最佳化器,我們可以使用通用的中間代碼。gcc可以支援c、c++、java……等語言的編譯。

圖3

 

 

那麼一個HelloWord的程式的編譯和執行過程大家就按照圖1自行腦補吧

說了這麼多終於正片開始了~ 原來我的囉嗦,因為我就是叫做話癆戴^_^,本人從沒有開發過Mac os的應用所以本文主要範例程式碼和架構都是iOS下的,但是是因為C系語言的先行編譯指令,所以基本都能通用。雖然這篇文章有個宏大的開端,但是本文主要就是想探究一下編譯過程中的預先處理部分的部分預先處理指令,希望本文能夠做到的就是拋磚引玉,給比我菜的廣大猿友指引一條學習的方向。

在很久很久以前的Xcode不知道什麼版本,Build settings裡面還可以選擇不同的編譯器。

圖4

 

 

不同的編譯器,是否對於預先處理指令有差異,我也沒辦法考究了。還有其實、其實人家接觸iOS也只有3個月,我開發iOS使用的第一個IDE就是XCode6,如果坑了大家,那就索瑞~~

現在Xcode6裡面預設使用了Apple LLVM(Low Level Virtual Machine) 6.0的編譯器

圖5

 

 

各種編譯器的區別還有幾本對比知識可以參看LLVM和GCC的區別

關於蘋果的和gcc以及LLVM背後激情個故事看以看這個三好學生Chris Lattner的LLVM編譯工具鏈

那麼接下來就是正片的高潮啦——預先處理指令

高潮之前再加一個預高潮^_^,幹嘛要預先處理呢?回去看圖一,老婆說“你給我滾出去睡沙發!” 如果你沒有預先處理,你按照順序運行,先滾出去了你可能還不想睡覺,你在沙發上看電視看了幾個小時後才打算睡覺,這時候你發現你竟然忘了從房間拿枕頭和被子出來了,你這時候就去敲老婆的門,又是一頓臭罵,之後你才能睡覺……折騰不? 如果你進行了預先處理,當老婆說完指令,其中你擷取到關鍵字“睡沙發”,不管我滾出去之後睡不睡覺,我都先從房間把被子枕頭拿到沙發,這樣是不是效率高了很多?同樣對於C系的語言的開發,預先處理可謂舉足輕重,如果你閱讀過優秀的C原始碼,你一定看到了很多 #define #if #error …… 先行編譯對程式之後的編譯提供了很多方便以及最佳化,對於錯誤處理、包引用、跨平台……有著極大的協助。而且開發中使用先行編譯指令完成一些事情也是很屌的事情,並且你既然走上了一條改變世界的道路那麼當一個有逼格的程式猿的覺悟也需要覺醒呀

檔案包含#include

這個我真的不想多說,只要你大學C語言課程不是體育老師教得話,他們肯定跟你說過#include “”、#include <>的區別,他們肯定說過#include“xxx”包含和使用#include 包含的不同之處就是使用<>包含時,前置處理器會搜尋C函數庫標頭檔路徑下的檔案,而使用“”包含時首先搜尋程式所在目錄,其次搜尋系統Path定義目錄,如果還是找不到才會搜尋C函數庫標頭檔所在目錄。

所以我不想為了彌補你老師犯下的錯,我就不想重複了,有一點需要注意使用#include的時候包含檔案的時候是不能遞迴包含的,例如a.h檔案包含b.h,而b.h就不能再包含a.h了;還有就是重複包含(比如a.h包含了b.h,然後main.c中又包含了a.h和b.h)雖然是允許的但是這會降低編譯效能。那該怎麼辦呢?1、使用#import替代include 2、使用宏判斷(宏判斷下面會詳解),xcode很聰明,只要建立一個標頭檔a.h 裡面就自動就產生了

圖6

 

 

這個看不懂?你可以等看完#ifndef和#define之後就明白了,大概的原理就是,用宏定義判斷一個宏是否定義了,如果沒有定義則會定義這個宏,這樣以來如果已經包含過則這個宏定義肯定已經定義過了,即使再包含也不會重新定義了,下面的代碼也就不會包含進去。

這個是非C標準庫裡面的預先處理指令,但是Xcode中允許使用,所以也就介紹一下吧。#include_next是GNU(一群牛逼的人瘋狂開源的組織,可以說是Linux的靈魂)的一個擴充,並不是標準C中的指令 例如有個搜尋路徑鏈,在#include中,它們的搜尋順序依次是A,B,C,D和E。在B目錄中有個標頭檔叫a.h,在D目錄中也有個標頭檔叫a.h,如果在我們的原始碼中這樣寫#include ,那麼我們就會包含的是B目錄中的a.h標頭檔,如果我們這樣寫#include_next 那麼我們就會包含的是D目錄中的a.h標頭檔。#include_next 的意思按我們上面的引號包含中的解釋來說就是“在B目錄中的a.h標頭檔後面的目錄路徑(即C,D和E)中搜尋a.h標頭檔並包含進來)。#include_next 的操作會是這樣的,它將在A,B,C,D和E目錄中依次搜尋a.h標頭檔,那麼首先它會在B目錄中搜尋到a.h標頭檔,那它就會以B目錄作為分割點,搜尋B目錄後面的目錄(C,D和E),然後在這後面的目錄中搜尋a.h標頭檔,並把在這之後搜尋到的a.h標頭檔包含進來。這樣說的話大家應該清楚了吧。

#import

OC特有的就是一個智能的#include,解決了#include的重複包含的問題。

宏定義#define

這個使用的就太多了,個人認為是所有預先處理指令中最酷的!必須要學習!這裡我厚顏無恥的轉載OneV’s Den(喵神)的文章,他寫的非常的棒!宏定義的黑魔法 - 宏菜鳥起飛手冊,請叫我快樂的搬運工!

附上那頭小牛

#define NSLog(format, ...)   fprintf(stderr, "<%s : %d> %s\n",                                           [[[NSString stringWithUTF8String:__FILE__] lastPathComponent] UTF8String],  __LINE__, __func__);                                                        (NSLog)((format), ##__VA_ARGS__);                                           fprintf(stderr, "\n ------------------\n/ Hello David Day! \\\n\\ my Macro Log ~   /\n ------------------\n            \\\n             \\   ^__^\n                 (OO)\__________\n                 (__)\\          )\\/\\\n                     ||_______ _)\n                     ||       W |\n       YYy           ww        ww\n");

圖9

 

 

#undef

當你使用了#define宏定義後,則在整個程式的運行周期內這個宏都是有效,但有時候我們在某個邏輯裡希望這個宏失效不想使用,則會使用

#define NetworkOn -(void)closeNetwork{#undef NetworkOn}
條件編譯#if #else #endif

if就和我們常用的條件陳述式的if使用方式一樣,#if的後面跟上條件運算式,後面跟上一個#endif表示結束#if,雖說這玩意兒簡單,但是用的好,對於某些取巧的工作特別容易實現。比如你現在有這樣的需求,我的程式平時偵錯模式的時候需要列印一些log,但是發布模式的應用就不用再列印log了,怎麼辦?很多人就說發布的時候吧log語句一句一句的刪除唄~ 那客戶發爛咋說你寫的東西是狗屎讓你修改,所以你又要回來調試,當你調試的時候你菊花肯定一緊,以前的調試語句因為過於自信在發布的時候全都刪除了,又想不到發布後又被要求修改~,有基友就說了,那就不刪除log語句唄,反正是列印到控制台的資訊,使用者又看不到~,果然沒有安全意識,企業開發不是學雷鋒,不用把你的所有log都寫在日記本,有時候你的軟體被破解的原因就是因為你的調試資訊出賣了你。安全意識不可無,不然老王替你生孩子~~~~~。

怎麼做呢?

#if DEBUG func dlog<T>(object: T) {     println(object)}#elsefunc dlog<T>(object: T) {}#endif

DEBUG是xcode的預定義的宏,這個東西多的很呢,要慢慢挖掘呢。 以後列印log你都只使用dlog()這個函數,如果你是在偵錯模式的時候就會列印,否則就不會列印了。

其他例子:

判斷是否開啟ARC,有些庫需要ARC支援,則在編譯之前可以判斷使用者有沒有開啟ARC

#if !__has_feature(objc_arc)#error "啊 啊 啊~ 倫家需要ARC"#endif

同樣__has_feature(objc_arc)這玩意兒也是xcode預置的 , 首碼是這個的”__“都是預定宏;

又比如,對不同版本的os系統做策略

#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0#endif

又或者判斷裝置類型

#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)#if IS_IPAD#else#endif

這個東西簡單但是很常使用,正所謂IF在手,天下我有 哈哈哈

 #if define  #ifdef  #ifndef  #elif  #if define = #ifdef  #if !define = #ifndef  #elif = "else if"
錯誤、警告處理#error

如果編譯器遇到這貨,馬上就會罷工。再說Xcode的錯誤校正功能這麼強大,所以幾乎不可能在編譯過程中遇到#error了,所以說這貨沒用?非也~,我們是受過高等教育的高材生,我們要懂得辯證觀點還要瞭解價值定理!任何事物都有存在的價值的。雖說今天的IDE很好很強大,#error似乎沒什麼用了~但是還有有一群猿類孤高冷傲,隱居山林,他們鄙視一切IDE,他們堅信Notepad就是他們的屠龍寶刀……

對於這些虛幻飄渺的程式猿們,他們還是需要#error來給他們預報編譯前的錯誤的。我們說點有價值的,如果非要用#error,那在我們當下的開發中怎麼用?

現在#error還是有用的,尤其是你在開發一個庫的時候,這個庫的使用需要一定的條件,如果不滿足這個條件,你就不讓使用者編譯。這樣不就可以使用#error啦嘛

#if !__has_feature(objc_arc)#error "我的低調不是你裝逼的資本!這個庫需要開啟ARC,不然你別用!"#endif

那麼如果使用者沒有開啟ARC就無法進行編譯了,因為xcode看到#error就不編譯了,在這裡只有開啟了ARC,#error才會不見。

#warning

這個用法很簡單,只要後面跟上你想警告的話就OK了,這樣你就可以讓編譯器提醒這個警告。

圖10

 

 

如果你在Xcode中設定了

圖11

 

 

如果你設定成Yes,那麼你的waring就等於error,編譯不了的哦。

請再次叫我快樂的小搬運工~ 又是他 —->Onev’s Den寫的東西,我就是喜歡他,怎麼樣怎麼樣?

談談Objective-C的警告

編譯器控制#pragma

大家都說在所有的預先處理指令中,#Pragma 指令可能是最複雜的了,它的作用是設定編譯器的狀態或者是指示編譯器完成一些特定的動作。#pragma指令對每個編譯器給出了一個方法,在保持與C和C++語言完全相容的情況下,給出主機或作業系統專有的特徵。依據定義,編譯指示是機器或作業系統專有的,且對於每個編譯器都是不同的。

其格式一般為: #pragma Para。其中Para 為參數

我們就說說iOS下,常用的

#pragma mark

如果一個檔案代碼量很大,有時候找某段邏輯不太好找,你就可以使用#pragma mark!

比如這樣:

圖12

 

 

圖13

 

 

在方法導航哪裡就會出現你的mark了 是不是很方便呀

如果使用了 “#pragma mark -“ 如這樣:

#pragma mark -
#pragma mark -#pragma mark 這裡是applicationWillTerminate方法呀~- (void)applicationWillTerminate:(UIApplication *)application {    }

就會這樣,

圖14

 

 

自動分隔開了!!!

#pragma message(“”)

可以輸出調試資訊

控制編譯器行為不過多解釋了

#pragma clang diagnostic push#pragma clang diagnostic ignored “clang的參數”#pragma clang diagnostic pop

自行Clang使用手冊

pragma非常複雜需要你對編譯器底層非常的瞭解,只有當你開發一些比較底層的framework的時候才可能比較多用的,我是初學者,我不用我怕誰?

其他#line

在說這個東西的時候我們先來看一個預定義的宏,LINE,我們在《宏定義的黑魔法 - 宏菜鳥起飛手冊》自訂NSLog中見過吧

C語言中的__LINE__用以指示本行語句在源檔案中的位置資訊。而#line就是可以改變當前行的行號在編譯器中的表示,並且之後的行號也會相應的改變,比如

1 #include <stdio.h>2 main(){3     printf("%d\n",LINE);4 #line 100  5     printf("%d\n",LINE);6     printf("%d\n",LINE);7     };

輸出為:

3100101
(完)

 

iOS中的先行編譯指令

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.