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

【分享本文】
Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
【贊助商連結】

    前言

    我們藉由加上註解 (comments) 的 Hello World 程式,來說明 Objective-C 程式的基本概念。以下是程式碼:

    // 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;
    }
    

    註解 (Comments)

    // 之後的文字敘述是註解,在 // 後的同一行文字內皆會被 Objective-C 編譯器當成註解而忽視,我們的程式在許多地方有註解。這種風格的註解是從 C99 後引入的。

    另一種註解風格是用一對 /**/ 將註解文字包起來,這是原本 C 語言所有的註解。但這種註解方式需要在註解文字兩端加入特定符號,使用上較不方便,目前筆者僅用於行內註解。

    讀者會發現註解是一般的文字敘述,而非程式碼。註解的目的是給程式設計師 (programmers) 看的,在編譯軟體的過程中會自動抹去。在實際上線的程式中,往往不會只有一個程式設計師經手程式碼,甚至原本的程式設計師離職後換另一批程式設計師繼續接手等;適度地在程式碼中加上註解,也是一種溝通的方式。本範例為了說明,每一行都加上註解,實際的程式碼不會逐行加註解,而是視需求加。

    引入 (#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 範例程式第一行都是引入這個頭文字檔。

    主函式 (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) 對程式碼進行進一步的抽象化。

    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 相關的標頭檔。

    程式執行的順序

    在傳統的命令式程式設計 (imperative programming) 下,我們會有幾個內隱的規則:

    • 循序前進
    • 選擇 (Selection) 控制結構
    • 迭代 (Iteration) 控制結構
    • 函式 (Function)
    • 物件 (Object)

    在預設情形下,程式執行的順序是由上向下執行。有些讀者可能以為所有的程式語言都是這樣,但其實不是如此,只是剛好主流的程式語言,像 C、C++、Java、C# 等,都符合這項規則。像 JavaScript 的整體運作模型是非同步的,就沒有按照這個前提運作。

    選擇控制結構可決定某段程式碼區塊是否要執行,讓程式的執行可以依情境而改變,而不是每一行程式碼都要執行。常見的選擇控制結構是 ifswitch,在 C 家族語言幾乎都看得到。

    迭代控制結構可以重覆執行某段程式碼區塊,不需要重覆撰寫相同的指令,程式碼就不會那麼累贅。依照迭代的方式,又可分為條件句 (conditioal)、計數器 (counter)、迭代器 (iterator) 等。常見的迭代控制結構有 whilefor,同樣也是在 C 家族語言可見。

    函式是程式碼重用最基本的形式,函式的目的是將某一段指令以有意義的名稱封裝起來,之後就可以重覆使用。函式具有參數 (parameters),可以微調函式的行為;此外,函式具有回傳值 (return value),可以將運算的結果回傳。當程式碼呼叫某個函式時,其實是跳躍到該函式所在的程式碼區塊,執行完該函式的指令後,再跳回原處。所以,函式也會改變程式行進的流程。

    物件本質上還是函式,只是物件帶有狀態 (stateful),而理想上的函式是無狀態的 (stateless)。物件的本質是資料 (data) 和行為 (behavior) 間的連動,而封裝 (encapsulation)、繼承 (inheritance)、多型 (polymorphism) 等物件導向特性是物件常見但非必備的性質。物件算是物件導向程式設計 (objective-oriented programming) 的課題,而物件導向程式設計是命令式程式設計的強化版。

    傳統的程式設計,在學完物件後,就算畢業了。但傳統的程式是以單線程為前提下去設計的,現在的程式會利用共時性或平行運算的特性加強程式的效率。不過,共時性或平行程式設計算是較進階的主題,日後再慢慢學習也不遲。

    【分享本文】
    Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email
    【支持站長】
    Buy me a coffeeBuy me a coffee
    【贊助商連結】
    【分類瀏覽】