iOS開發中你真的會用#define麼!!!?

ZeroJ發表於2019-03-01

前言:

不得不說在C系語言(C, Objective-C, C++...)中巨集(macro)是個強大的東西, 雖然在基本的語法上面看上去是非常的簡單, 不過有時候正因為他的強大和方便, 就會導致在使用的時候, 其中會有很多的注意點, 如果不小心被忽略, 那麼將會帶來完全不想要的結果. 所以要想靈活的使用它, 那麼還是先了解一些比較好. 而且在iOS開發中如果你是使用OC, 那麼你可能經常會使用到#define(swift當前不支援巨集)

首先扔出幾個巨集的定義,呼叫這些巨集的時候分別是什麼結果, 看看你能夠在不看後面的情況下, 清楚多少, 當然, 如果很清楚, 自然可以忽略後文的八卦了..., 因為, 你絕對比我更瞭解巨集...

  1. #define PI 3.14
  • #define log(x) printf("this is test: x = %d", x)

  • #define log(x) printf("this is test: "#x" = %d", x)

  • #define power(x) x*x
  • #define RGBA(r, g, b, a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a]
  • #define print(...) printf(__VA_ARGS__)

  • #define RGB(r, g, b) {\ RGBA(r, g, b, 1.0f);\ }

  • #define weakify( x ) autoreleasepool{} __weak typeof(x) weak##x = x;

  • #define weakify(...) \\ autoreleasepool {} \\ metamacro_foreach_cxt(rac_weakify_,, __weak, __VA_ARGS__)
這裡先不解釋上面定義的幾個巨集了, 首先介紹下巨集的一些基本東西
  • 程式第一步是在預編譯之前會有一些操作, 例如刪除反斜線和換行符的組合, 將每個註釋用一個空格替代...,
  • 然後在進入預編譯的時候, 會尋找可能存在的預處理指定(由#開頭), 例如C中常用的#include, 或者oc中的#import, #define...很多(條件編譯語句...)
  • 處理#define的時候,然後前處理器會從#開始, 一直到執行到第一個換行符(寫程式碼的時候換行的作用), 自然, #define只會允許定義一行的巨集, 不過正因為上面提到的預處理之前會刪除反斜線和換行符的組合, 所以可以利用反斜線定義多行巨集, 在刪除反斜線和換行符的組合後, 邏輯上就成了一行的巨集了
  • 巨集作用在預編譯時期, 其真正的效果就是程式碼替換, 而且是直接替換(行內函數!!!), 這個和函式有著很大的區別, 並且正因為是直接替換, 在使用的時候就會有一些的注意點了, 這個在後面會給出例子
  • 巨集可以被稱為 類物件巨集, 類函式巨集(開篇給的幾個巨集中都已經囊括了這兩類)
  • 定義巨集的語法很簡單, 一個巨集定義由三部分組成 , 三分部之間用空格分開, #define, 巨集的名字, 主體 例如第一個巨集#define PI(巨集的名字) 3.14(主體), 這裡有個注意點就是, 巨集的命名和普通的變數命名規則相同
  • 巨集在預處理階段只進行文字的替換(相當於把程式碼拷貝貼上), 不會進行具體的計算(發生在編譯時期)

對於巨集的基本的東西就介紹到這裡了, 還有一些相關的東西就在下面解釋一下上面定義的幾個巨集的過程中提到
  • #define PI 3.14 這是巨集的最簡單的定義了, 可能也是大家應用最廣的, 就是使用巨集來定義一些常量(消除魔法數字)或字串..., 這一類可以被稱為類物件巨集, 方便程式碼閱讀和修改, 使用的時候直接使用定義的巨集的名字, PI, 那麼前處理器就會將程式碼中的PI替換為3.14
    float computeAreaWithRadius(float r) {
      return PI * r * r;
    }複製程式碼
  • #define log(x) printf("this is test: x = %d", x) 這是巨集的第二類定義, 即類函式巨集, 這一類的巨集和函式類似的寫法, ( )中可以寫變數, 用作函式的引數, 不過, 這個和函式的區別是, 巨集的引數不指定型別, 具體的引數型別在呼叫巨集的時候由傳入的引數決定(有點其他語言裡的泛型的意思), 這個可以算是和函式相比的優點, 下面測試一些這個巨集的使用, 結果你猜對了麼?

    #define log(x) printf("this is test: x = %d", x)
    int main(int argc, const char * argv[]) {
    
      int y = 12;
      log(y); // 輸出為  this is test: x = 12
    }複製程式碼
  • #define log(x) printf("this is test: "#x" = %d", x), 這個定義中和上面的區別是使用了一個#運算子, #運算子被用於利用巨集引數建立字串, 區分一下和上面的結果
    #define log(x) printf("this is test: "#x" = %d", x)
    int main(int argc, const char * argv[]) {
      int y = 12;
      log(y); 
    // 輸出為  this is test: y = 12 (而不是 x = 12, 或者 12 = 12)
    // 因為使用#和引數結合可以被替換為巨集引數對應的字串, "#x"表示字串x, 這裡輸入的引數為y, 則替換為y(不是12)
    log(2+4)// 輸出為 this is test: 2+4 = 6
    }複製程式碼
  • #define power(x) x*x 這個和上面一樣是一個類函式巨集, 這裡我原本的意願是計算 x*x即x的平方的值, 不過這樣的定義巨集在有些情況下是會出問題的, 這個例子就是告訴大家定義類函式巨集的時候就真的要小心, 不然客人結果並不是我們預期的
      #define power(x) x*x
      int x = 2;
      int pow1 = power(x); // pow1 = 2*2 = 4
      int pow2 = power(x+1); //  pow2 = 3 * 3 = 9  ??
      // 顯然對於pow1 = 4是沒有問題的
      // 不過對於pow2 = 9 這個結果是有問題的, 定義的巨集並沒有達到我們預想的效果 結果為 3*3
      // 因為: 上面提到過巨集是直接的程式碼替換, 這裡巨集展開後就成為了 x+1*x+1 = 2+1*2+1 = 5
      // 這裡因為運算優先順序的原因導致結果的不一樣, 所以pow應該(加上括號)定義為
    #define power(x) (x)*(x)複製程式碼
  • #define RGBA(r, g, b, a) [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:a] 這裡是個簡單的多引數的類函式巨集的定義, 這個巨集在使用OC開發的時候 大家可能都會喜歡使用
  • #define RGB(r, g, b) {\ RGBA(r, g, b, 1.0f);\ } 這個巨集是一個"多行巨集"定義的示例, 即在除了最後一行的最後加上反斜線(因為反斜線和換行符的組合在預編譯之前會被系統刪除), 同時這個巨集也說明了, 巨集的定義是可以巢狀的(有些編譯器可能不支援, xcode中是支援的...)
  • #define print(...) printf(__VA_ARGS__) 這個巨集使用了兩個新的東西...__VA_ARGS__, 這兩個是用來定義可變引數巨集的, 可以看到是很簡單的, 唯一一個注意點就是, ...要放在引數的最後, 如果你使用C定義可變引數的函式就會發現過程就很複雜了
    #define print(...) printf(__VA_ARGS__)
    int main(int argc, const char * argv[]) {
    print("測試可變引數 ---- %d", 12); // 輸出結果為: 測試可變引數 ---- 12
    }複製程式碼
  • #define weakify( x ) autoreleasepool{} __weak typeof(x) weak##x = x; 最後一個巨集介紹另外一個運算子 ## 這個是巨集定義中的連線運算子, 例如上面的weak##x 就是將weak和引數x連線在一起, 同時這一個巨集在iOS開發中是很有用的, 使用block的時候為了消除迴圈引用 通常使用weakSelf, 那麼就可以定義這樣一個巨集, 而不用每次都輸入上面一段重複的程式碼 __weak typeof(self) weakself = self, 那麼上面定義的巨集和這段程式碼一樣會生成一個弱引用的新變數, 不過上面定義的時候使用了autoreleasepool{}, 這一個自動釋放池本質上並沒有什麼用, 只不過對呼叫weakify會有影響, 需要使用@weakify(x), ?看上去逼格更高, 不過在RAC中weakify是另外的方式定義的, (開篇給出的第九個巨集定義)這個就可以自己下去研究一下了.
#define weakify( x ) autoreleasepool{} __weak typeof(x) weak##x = x;
加上 autoreleasepool{}使用巨集的時候就應該加上@
像這樣: 
- (void)delay {
    @weakify(self)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakself test];
    });
}
當然如果你沒有加autoreleasepool{}, 使用巨集就不用加上@了
#define weakify( x ) __weak typeof(x) weak##x = x;
像這樣: 
- (void)delay {
    weakify(self)
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        [weakself test];
    });
}複製程式碼

?這裡關於巨集的介紹就先這樣了, 使用巨集很多時候可以讓我們的程式碼更容易閱讀和修改, 同時也可以少寫很多的重複程式碼, 希望你在使用C系語言開發的時候能夠好好利用這個方便的東西, 如果你使用OC開發iOS那麼巨集對你而言也會是一大福利, 如果使用swift開發iOS, 那麼... 目前swift是不支援巨集定義的, 不過可以使用全域性的常量和全域性函式來替換一部分巨集的功能

相關文章