Michelle Chen [Objective-C] 程式設計教學:基本概念

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

本文假定讀者沒寫過或僅寫過少量 Objective-C 程式,從頭說明相關概念。

Objective-C 程式碼所用的副檔名

如同 C 語言,Objective-C 的程式碼分為原始碼 (source) 和標頭檔 (header) 兩部分。前者是程式的內部實作,後者是程式的外部宣告。

由於 Objective-C 專案中可以混合 C、C++、Objective-C 三種語言的程式碼,在單一專案中可能會見到多種副檔名。

原始碼的副檔名如下:

  • .c :純 C 的原始碼
  • .cc.cpp.cxx :純 C++ 的原始碼
  • .m :Objective-C 的原始碼
  • .mm :Objective-C++ 的原始碼

標頭檔的副檔名如下:

  • .h :C 或 Objective-C 的標頭檔
  • .hh.hpp.hxx :C++ 或 Objective-C++ 的標頭檔

從標頭檔的副檔名無法區分是 C 或 Objective-C 的標頭檔,只能用相異的標頭檔名稱來區分。

選擇 Objective-C 編譯器

目前可用的 Objective-C 編譯器有 Clang 和 GCC。兩者所支援的 Objective-C 有些差異,大抵上是 GCC 落在 Clang 後面。在寫 Objective-C 程式時,就要從中擇一使用。

選擇的方式很簡單,只要看 Objective-C 專案所用的標準物件庫是 Cocoa 或是 GNUstep 即可。使用 Cocoa 時,一定是在蘋果平台上,自然就會用 Clang。大部分 GNUstep 使用者會用 GCC,所以用 GCC 相容性會比較好。

撰寫第一個程式

最簡單的 Objective-C 程式如下:

int main() {}

但這個程式太簡單了,什麼事都沒做。從這個程式也無法看出 Objective-C 程式的基本架構。我們會將其改寫成 Objective-C 版本的 Hello World 程式。以下是程式碼:

// Import Cocoa/GNUstep specific classes.
#import <Foundation/Foundation.h>

// `main` is the entry of an objective-c program.
int main(int argc, const char * argv[])
{
    // Use ARC for memory management.
    @autoreleasepool {
        // Print out the string "Hello World" with a time stamp.
        NSLog(@"Hello World");
    }

    // Return success status.
    return 0;
}

編譯 Objective-C 程式的過程

如同 C 語言,編譯 Objective-C 分為四個步驟:

  • 前處理 (preprocessing)
  • 編譯 (compiling)
  • 組譯 (assembly)
  • 連結 (linking)

前處理是用 Objective-C 的前置處理器對原始碼進行字串處理的過程。這個過程在本質上是字串代換。經前處理的原始碼會進到下一個步驟。

這裡的編譯會將 Objective-C 程式碼轉為等效的組合語言程式碼。由此可知 Objective-C 和 C 都是高階語言,因為這些語言的指令和組語指令無法直接一對一轉換。

經轉換後的組語程式碼會經組譯轉為目的檔。最後,目的檔會和外部函式庫連結,產出執行檔。

大小寫敏感性 (Case Sensitivity)

如同 C 語言,Objective-C 的程式碼大小寫有別。例如,fooFooFOO 視為三個不同的識別字 (identifier)。但這個例子算是反模式,實務上請不要這樣寫程式。

空白 (Space)、縮進 (Indentation)、換行 (End of Line)

Objective-C 程式碼會以空白區分識別字、保留字 (keyword) 等。但不會嚴格規範空白的數量,所以程式設計者可利用空白手動排版。

此外,Objective-C 不會嚴格規範縮進。台灣的 C 和 Objective-C 教材大多遵循 K&R 風格,用空白 (space) 來縮進,每層使用 4 個空白。

由於 Objectiive-C 在每行敘述結束時會使用 ; (分號) 表示敘述結束,不需要使用換行來區隔指令。適度換行的用意是讓程式碼比較美觀。

總而言之,Objective-C 承襲 C 的習慣,對程式碼的排版相對自由。但程式設計者仍會將程式碼排列整齊,是為了讓程式碼更美觀,日後維護起來會比較容易。

註解 (Comments)

Objective-C 使用兩種風格的註解:

  • 多行註解:由一對 /**/ 組成
  • 單行註解:以 // 開頭的文字,到該行結尾 (C99)

由於 Clang 和 GCC 均支援現代 C 語言,不用考慮 ANSI C 相容性。根據註解內容選用合適的註解形式即可。

主函式 (Main Function)

在 C 家族的程式語言中,會有主函式 (main function)。所謂的主函式是應用程式的進入點 (entry) 或起始點,應用程式會把主函式當成執行程式時的第一個函式。我們在後文會介紹函式 (function) 的寫法,但一開始不用過度在意函式的細節,只要把主函式當成是一個程式固有的功能即可。

沒有功能的最小主函式如下:

int main (int argc, const char * argv[])
{
    return 0;
}

在這個主函式中,argc 代表命令列參數的數量,argv 代表傳入的命令列參數,型別為 C 字串。如果連這兩個主函式的參數都不需要,可改寫如下:

int main (void)
{
    return 0;
}

C 或 Objective-C 會以主函式的回傳值代表主程式的狀態,若程式正確運行,最後會回傳 (return) 0,若程式運行有錯,通常會回傳 1。當然也可以回傳其他的數值,但 0 和 1 以外的數值在不同平台間沒有共識,故不建議直接以程式回傳的數值判斷程式的狀態,除了檢查回傳值是否為零以外。

一開始程式很小時,都寫在主函式內即可。日後程式變大了,就會學習用函式 (function) 和物件 (object) 對程式碼進行進一步的抽象化。

引入 (#import)

程式語言不會把所有的功能都放在語法內,而會將一些功能移到函式庫 (或物件庫) 中。除了透過標準函式庫提供內建功能外,有函式庫的程式語言保留了擴充的彈性;第三方開發者可以為這個語言開發新的功能,藉以慢慢建立一個語言社群、凝聚社群資源。

#import 是 Objective-C 中引入函式庫的語法。原本 C 語言中引入函式庫的語法是 #include,而 #import 是 Objective-C 特有的加強版。兩者的差別在於 #import 對於循環引用有保護機制,而 #include 完全沒有。理論上 C 頭文字檔 (header) 的 include guard 或 #pragma once 語法在 Objective-C 是沒有必要的,但為了避免有函式庫使用者用舊有語法而引發錯誤,還是可以加上去。

在 Objective-C 中,Foundation/Foundation.h 是基本物件庫的頭文字檔 (header),包括所有類別的基礎類別 (base class) NSObject 的宣告部分也是存在這個頭文字檔中。幾乎所有的 Objective-C 都會用到這個頭文字檔,除非那段程式只用到純 C 的部分,所以絕大部分的 Objective-C 範例程式第一行都是引入這個頭文字檔。

Objective-C 的記憶體管理模式

Objective-C 基於 C 語言,而 C 語言需要手動管理記憶體,除了少數專案在專案中引入一些第三方垃圾回收函式庫以外。Objective-C 在演進的過程中,出現以下三種記憶體管理模式:

  • 手動記憶體管理
  • ARC (Automatic Reference Counting)
  • 垃圾回收 (即將棄用)

在這三者之中,目前最推薦的方式是 ARC,但必要時仍然可以使用手動記憶體管理並在程式碼中混合兩種記憶體管理模式。限於文章篇幅,我們不會展示所有的記憶體管理模式,僅就本範例程式所用的方式來介紹。

目前較推薦的方式是用 @autoreleasepool 區塊包住需要使用 ARC 機制管理記憶體的程式碼區塊,使用 @autoreleasepool 區塊會自動啟用 ARC。如下:

@autoreleasepool {
    // Implement your code here.
}

使用 GCC 編譯 Objective-C 程式碼時,無法用上述新式語法,而要明確地使用記憶體池物件:

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

// Implement your code here.

[pool drain];

但這種寫法就要明確地對物件傳遞 autorelease 訊息 (message),細節留待後文再談。

如果可以的話,儘量使用 @autoreleasepool 區塊而不要直接使用 pool 物件,因為前者較有效率 (參考這裡)。

由於 GCC 不支援 ARC 等 Objective-C 2.0 後的新特性,如果專案程式碼有用到這些新特性,要使用 Clang 來編譯程式碼。如果藉由 GNUstep Make 來編譯專案的話,要修改 CCOBJCFLAGS 等參數。可參考以下範例:

CC=clang
OBJCFLAGS=-I/usr/lib/gcc/x86_64-linux-gnu/7/include

CC 用來指定 Objective-C 編譯器;OBJCFLAGS 用來傳入編譯 Objective-C 程式碼時使用的參數。以本例來說,我們額外傳入一個頭文字檔 (header) 的位置,對於 Clang 來說,這個位置不是標準位置,故需額外引入,才能找得到 objc 相關的標頭檔。

程式的離開狀態

程式在結束時,會回傳一個整數給系統,讓系統知道程式是否為正常結束。按照 C 家族語言的慣例,回傳 0 代表程式正常結束,回傳非零值 1 表示程式異常結束。

這裡強調的是零和非零,因為只有正常結束時回傳零這件事有共識,其餘的回傳值在不同程式間不一致。如果程式的使用對象為人而非機器,就不需要設計複雜的回傳值,可以用其他方式來表達程式異常結束,像是將文字訊息傳到標準錯誤等。

Objective-C 專案

Objective-C 原本沒有專案的概念,需要透過其他開發工具來管理專案。在寫蘋果平台軟體時,自然會用 Xcode 來管理專案。在寫 GNUstep 軟體時,可以用 GNUstep Make 或是自行寫 Makefile 來管理專案。

繼續深入

如果你覺得這篇 Objective-C 的技術文章對你有幫助,可以看看「多平台 Objective-C 程式設計」電子書。這本書有更多關於 Objective-C 程式設計的內容,同時適用於 Cocoa 和 GNUstep:

多平台 Objective-C 程式設計