編寫 Objective-C 代碼

編寫 Objective-C 代碼

如果您未曾開發過 iOS 或 Mac OS X 平臺的程序,那就需要開始瞭解它們的首要程序設計語言 Objective-C。Objective-C 並不是一種很難的語言,如果能花一點時間學習,相信您會漸漸領會到它的優雅之處。Objective-C 程序設計語言使您能進行復雜的、面向對象的編程。通過提供用於定義類和方法的語法,它擴展了標準的 ANSI C 程序設計語言。它還促進類和接口(任何類可採用)的動態擴展。

如果您熟悉 ANSI C,那麼下述信息應該能幫助您學習 Objective-C 的基本語法。如果您使用其他面向對象程序設計語言進行過編程,您會發現許多傳統的面向對象概念,例如封裝、繼承、多態,都出現在 Objective-C 中。如果您不熟悉 ANSI C,在嘗試閱讀此文章時,最好先閱讀一下 C 語言的概述。

Objective-C 語言在《The Objective-C Programming Language》(Objective-C 程序設計語言)中有完整說明。

Objective-C 是 C 語言的超集

Objective-C 程序設計語言採用特定的語法,來定義類和方法、調用對象的方法、動態地擴展類,以及創建編程接口,來解決具體問題。Objective-C 作爲 C 程序設計語言的超集,支持與 C 相同的基本語法。您會看到所有熟悉的元素,例如基本類型(intfloat等)、結構、函數、指針,以及流程控制結構,如 if...else 語句和 for 語句。您還可以訪問標準 C 庫例程,例如在 stdlib.h 和stdio.h 中聲明的那些例程。

Objective-C 爲 ANSI C 添加了下述語法和功能:

  • 定義新的類

  • 類和實例方法

  • 方法調用(稱爲發消息

  • 屬性聲明(以及通過它們自動合成存取方法)

  • 靜態和動態類型化

  • 塊 (block),已封裝的、可在任何時候執行的多段代碼

  • 基本語言的擴展,例如協議和類別

如果您現在還不太熟悉 Objective-C 的這些方面,也不必擔心。隨着您讀完這篇文章的剩餘部分,將會逐漸瞭解它們。如果您是過程化程序開發人員,不懂面向對象的概念,那麼先將對象從本質上視爲具有關聯函數的結構,可能會有助於理解。這個概念與事實差不多,特別是在運行時實現方面。

除了提供在其他面嚮對象語言中已有的多數抽象和機制之外,Objective-C 還是一種非常動態的程序設計語言,而且這種動態是其最大優勢。這種動態體現在它允許在運行應用程序時(即運行時)纔去確定其行爲,而不是在生成期間就已固定下來。因此,Objective-C 的動態機制讓程序免受約束(編譯和鏈接程序時施加的約束);進而在用戶控制下,將大多數符號解析責任轉移到運行時。

類和對象

如同其他大多數面嚮對象語言那樣,Objective-C 中的類支持數據的封裝,並定義對這些數據執行的操作。對象是類的運行時實例。它包含自己的實例變量(由其類聲明)的內存副本,以及類方法的指針。您可以採用兩步法(稱爲分配和初始化)創建對象。

Objective-C 中某個類的規格需要兩個不同的部分:接口和實現。接口部分包含類聲明,並定義該類的公共接口。如同 C 代碼那樣,您定義頭文件和源代碼文件,將公共聲明與代碼的實現細節分開。(如果其他聲明是編程接口的一部分,並且打算專有,您可以將它們放在實現文件中。)這些文件的文件擴展名,列在下表中。

擴展名

源類型

.h

頭文件。頭文件包含類、類型、函數和常量聲明。

.m

實現文件。具有此擴展名的文件可以同時包含 Objective-C 代碼和 C 代碼。有時也稱爲源文件

.mm

實現文件。具有此擴展名的實現文件,除了包含 Objective-C 代碼和 C 代碼以外,還可以包含 C++ 代碼。僅當您實際引用您的 Objective-C 代碼中的 C++ 類或功能時,才使用此擴展名。

當您想要在源代碼中包括頭文件時,請在頭文件或源文件的前幾行之中,指定一個導入 (#import) 指令,#import 指令類似於 C 的#include 指令,不過前者確保同一文件只被包括一次。如果您要導入框架的大部分或所有頭文件,請導入框架的包羅頭文件 (umbrella header file),它具有與框架相同的名稱。導入(假設的)Gizmo 框架的頭文件的語法是:

#import <Gizmo/Gizmo.h>

下列框圖中的語法聲明名爲 MyClass 的類,它是從基礎類(或根類)NSObject 繼承而來的。(根類是供其他類直接或間接繼承的類。)類聲明以編譯器指令 @interface 開始,以 @end 指令結束。類名稱後面(以冒號分隔),是父類的名稱。在 Objective-C 中,一個類只能有一個父類。

類聲明

在 @interface 指令和 @end 指令之間,編寫屬性和方法的聲明。這些聲明組成了類的公共接口。(已聲明的屬性在“已聲明的屬性和存取方法”中有介紹。)分號標記每個屬性和方法聲明的結尾。如果類具有與其公共接口相關的自定函數、常量或數據類型,請將它們的聲明放在 @interface ...@end 塊之外。

類實現的語法與類接口文件類似。它以 @implementation 編譯器指令開始(接着是該類的名稱),以 @end 指令結束。中間是方法實現。(函數實現應在 @implementation ...@end 塊之外。)一個實現應該總是將導入它的接口文件作爲代碼的第一行。

#import "MyClass.h"
 
@implementation MyClass
- (id)initWithString:(NSString *)aName
{
    // code goes here
}
 
+ (MyClass *)myClassWithString:(NSString *)aName
{
    // code goes here
}
@end

對於包含對象的變量,Objective-C 既支持動態類型化,也支持靜態類型化。靜態類型化的變量,要在變量類型聲明中包括類名稱。動態類型化的變量,則要給對象使用類型 id。您會發現在某些情況下,會需要使用動態類型化的變量。例如,集 (collection) 對象,如數組,在它包含對象的類型未知的情況下,可能會使用動態類型化的變量。此類變量提供了極大的靈活性,也讓 Objective-C 程序擁有了更強大的活力。

下面的例子,展示了靜態類型化和動態類型化的變量聲明:

MyClass *myObject1;  // Static typing
id       myObject2;  // Dynamic typing
NSString *userName;  // From Your First iOS App (static typing)

請注意第一個聲明中的星號 (*)。在 Objective-C 中,執行對象引用的只能是指針。如果您還不能完全理解這個要求,不用擔心。並非一定要成爲指針專家才能開始 Objective-C 編程。只需要記住,在靜態類型化的對象的聲明中,變量的名稱前面應放置一個星號。id 類型意味着一個指針。

方法和發消息

如果您不熟悉面向對象編程,則將方法想像成一個規範特定對象的函數,可能會有所幫助。通過將一則消息發送到——或發消息給——一個對象,您可調用該對象的一個方法。Objective-C 中有兩種類型的方法:實例方法和類方法。

  • 實例方法,由類的實例來執行。換言之,在調用實例方法之前,必須先創建該類的實例。實例方法是最常見的方法類型。

  • 類方法,可由它所在的類直接執行。它不需要對象的實例作爲消息的接收者。

方法聲明包含方法類型標識符、返回類型、一個或多個簽名關鍵詞,以及參數類型和名稱信息。以下是 insertObject:atIndex: 實例方法的聲明。

方法聲明語法

對於實例方法,聲明前面是減號 (-);對於類方法,對應指示器是加號 (+)。下面的“類方法”將詳細介紹類方法。

一個方法的實際名稱 (insertObject:atIndex:) 是包括冒號字符在內的所有簽名關鍵詞的串聯。冒號字符表明有參數存在。在上述示例中,該方法採用兩個參數。如果方法沒有參數,則省略第一個(也是僅有的一個)簽名關鍵詞後面的冒號。

當您想要調用一個方法時,通過給實施該方法的對象發送一則消息來實現。(雖然“發送消息”常可與“調用方法”互換,但實際上,Objective-C 在運行時纔會執行實際地發送。)消息包含方法名稱,以及方法所需的參數信息(類型要匹配)。您發送到一個對象的所有消息,都被動態地分派,這樣使 Objective-C 類的多態行爲更加容易。(多態性是指不同類型的對象響應同一消息的能力。)有時被調用的方法,是由接收消息對象的類之超類來實現。

要分派消息,運行時需要一個消息表達式。消息表達式使用方括號([ 和 ])將消息本身(以及任何所需參數)括起來,同時將接收消息的對象放在最左邊方括號右側。例如,要將 insertObject:atIndex: 消息發送給 myArray 變量保存的對象,您會使用以下語法:

[myArray insertObject:anObject atIndex:0];

爲避免聲明大量局部變量來儲存臨時結果,Objective-C 讓您嵌套消息表達式。每個嵌套表達式的返回值,都用作另一個消息的一個參數或接收對象。例如,可以將上個示例中使用的任何變量替換爲取回值的消息。因此,如果您具有另一個名爲 myAppObject 的對象,並且此對象具有訪問數組對象的方法以及要插入數組的對象,則可以將上個示例編寫爲如下形式:

[[myAppObject theArray] insertObject:[myAppObject objectToInsert] atIndex:0];

Objective-C 還提供用於調用存取方法的點記法語法。存取方法獲取並設定對象的狀態,因此對於封裝很重要,是所有對象的重要功能。對象隱藏或封裝其狀態,並顯示接口,該接口是訪問該狀態的所有實例都通用的。使用點記法語法,您可以將上個示例重新編寫爲如下形式:

[myAppObject.theArray insertObject:myAppObject.objectToInsert atIndex:0];

您還可以使用點記法語法進行賦值:

myAppObject.theArray = aNewArray;

此語法只是編寫 [myAppObject setTheArray:aNewArray]; 的另一種方式。在點記法表達式中,您不能使用對動態類型化的對象(類型爲 id 的對象)的引用。

在“您的首個 iOS 應用程序”中,您已經使用點語法來進行變量賦值:

self.userName = self.textField.text;

類方法

儘管前幾個示例將消息發送到了類的實例,但您也可以將消息發送到類本身。(類是運行時創建的、類型爲 Class 的對象。)向類發送消息時,您指定的方法必須定義爲類方法,而非實例方法。類方法是一種功能,類似於 C++ 中的靜態類方法。

您通常這樣使用類方法:要麼將類方法用作工廠方法創建類的新實例,要麼訪問與該類關聯的一些共享信息。類方法聲明的語法,與實例方法聲明的語法相同,只是方法類型標識符使用加號 (+),而非減號。

以下示例說明如何將類方法用作類的工廠方法。在這種情況下,array 方法是 NSArray 類上的類方法——被 NSMutableArray 繼承——它分配並初始化類的新實例,並將其返回給您的代碼。

NSMutableArray *myArray = nil;  // nil is essentially the same as NULL
 
// Create a new array and assign it to the myArray variable.
myArray = [NSMutableArray array];

已聲明的屬性和存取方法

屬性通常是指某些由對象封裝或儲存的數據。它可以是標誌(如名稱或顏色),也可以是與一個或多個其他對象的關係。一個對象的類定義一個接口,該接口使其對象的用戶能獲取並設定所封裝屬性的值。執行這些操作的方法,稱爲存取方法

存取方法有兩種類型,每個方法都必須符合命名約定。“getter”存取方法返回屬性的值,且名稱與屬性相同。“setter”存取方法設定屬性的新值,且形式爲 setPropertyName:,其中屬性名稱的第一個字母大寫。正確命名的存取方法是 Cocoa 和 Cocoa Touch 框架的多種技術的關鍵元素,如鍵-值編碼 (KVC),它的機制是,通過對象的名稱間接訪問對象的屬性。

Objective-C 提供已聲明的屬性作爲一種方便的寫法,用於存取方法的聲明和實現。在“您的首個 iOS 應用程序”中,您聲明瞭userName 屬性:

@property (nonatomic, copy) NSString *userName;

使用已聲明的屬性後,就不必爲該類中用到的每個屬性實現 getter 和 setter 方法。而是使用屬性聲明,指定您想要的行爲。編譯器接着可以根據該聲明,創建或合成實際的 getter 和 setter 方法。已聲明的屬性減少了您必須編寫的樣板文件代碼量,因此使代碼更簡潔、更少機會出錯。使用已聲明的屬性或存取方法,來獲取和設定各項對象狀態。

您在類接口中包括方法聲明和屬性聲明。您在類的頭文件中聲明公共屬性;而在源文件的類擴展中聲明專有屬性。(有關類擴展的簡短說明及其示例,請參閱“協議和類別”。)控制器對象(如委託和視圖控制器)的屬性通常應該爲專有的。

屬性的基本聲明使用 @property 編譯器指令,後面緊跟屬性的類型信息和名稱。您還可以使用自定選項來配置屬性,以定義存取方法如何表現、屬性是否爲弱引用,以及是否爲只讀。選項位於圓括號中,前面是 @property 指令。

以下代碼行說明了更多的屬性聲明:

@property (copy) MyModelObject *theObject;  // Copy the object during assignment.
@property (readonly) NSView *rootView;      // Declare only a getter method.
@property (weak) id delegate;               // Declare delegate as a weak reference

編譯器自動合成所聲明的屬性。在合成屬性時,它創建自己的存取方法,以及“支持”該屬性的專有實例變量。實例變量的名稱與屬性的名稱相同,但具有下劃線前綴 (_)。只有在對象初始化和取消分配的方法中,您的應用程序應該直接訪問實例變量(而不是其屬性)。

如果您想要讓實例變量採用不同名稱,可以繞過自動合成,並明確地合成屬性。在類實現中使用 @synthesize 編譯器指令,讓編譯器產生存取方法,以及進行特殊命名的實例變量。例如:

@synthesize enabled = _isEnabled;

同時,在聲明屬性時,您可以指定存取方法的自定名稱,通常是使 Boolean 屬性的 getter 方法遵循約定形式,如下所示:

@property (assign, getter=isEnabled) BOOL enabled; // Assign new value, change name of getter method

是封裝工作單元的對象,即可在任何時間執行的代碼段。它們在本質上是可移植的匿名函數,可作爲方法和函數的參數傳入,或可從方法和函數中返回。塊本身具有一個已類型化的參數列表,且可能具有推斷或聲明的返回類型。您還可以將塊賦值給變量,然後像調用函數一樣調用它。

插入符號 (^) 是用作塊的語法標記。塊的參數、返回值和正文(即執行的代碼)存在其他類似的語法約定。下圖解釋了該語法,尤其是在將塊賦值給變量時。

圖像: ../Art/blocks_2x.png

您接着可以調用塊變量,就像它是一個函數一樣:

int result = myBlock(4); // result is 28

塊共享局部詞法作用範圍內的數據。塊的這項特徵非常有用,因爲如果您實現一個方法,並且該方法定義一個塊,則該塊可以訪問該方法的局部變量和參數(包括堆棧變量),以及函數和全局變量(包括實例變量)。這種訪問是隻讀的,但如果使用 __block 修飾符聲明變量,則可在塊內更改其值。即使包含有塊的方法或函數已返回,並且其局部作用範圍已銷燬,但是隻要存在對該塊的引用,局部變量仍作爲塊對象的一部分繼續存在。

作爲方法或函數參數時,塊可用作回調。被調用時,方法或函數執行部分工作,並在適當時刻,通過塊回調正在調用的代碼,以從中請求附加信息,或獲取程序特定行爲。塊使調用方在調用時能夠提供回調代碼。塊從相同的詞法作用範圍內採集數據(就像宿主方法或函數所做的那樣),而非將所需數據打包在“關聯”結構中。由於塊代碼無需在單獨的方法或函數中實現,您的實施代碼會更簡單且更容易理解。

Objective-C 框架具有許多含塊參數的方法。例如,Foundation 框架的 NSNotificationCenter 類聲明以下方法,該方法具有一個塊參數:

- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

此方法將一個觀察者添加到通知中心(通知在“採用設計模式使您的應用程序合理化”中有介紹)。指定名稱的一則通知發佈時,塊被調用以處理該通知。

    opQ = [[NSOperationQueue alloc] init];
    [[NSNotificationCenter defaultCenter] addObserverForName:@"CustomOperationCompleted"
             object:nil queue:opQ
        usingBlock:^(NSNotification *notif) {
        // handle the notification
    }];

協議和類別

協議可聲明由任何類實施的方法,即使實施該協議的那些類沒有共同的超類。協議方法定義了獨立於任何特定類的行爲。協議簡單定義了一個由其他類負責實現的接口。當您的類實施了一個協議的方法時,就是說您的類符合該協議。

從實踐角度而言,一個協議定義了一列方法,這些方法在對象之間建立合約,而無需那些對象成爲任何特定類的實例。此合約使那些對象能夠相互通信。一個對象想要告知另一個對象它遇到的事件,或者它可能想要尋求有關那些事件的建議。

UIApplication 類實現一個應用程序必需的行爲。UIApplication 類不是強制您對 UIApplication 進行子類化,來接收有關應用程序當前狀態的簡單通知,而是通過調用指定給它的委託對象的特定方法,爲您傳送那些通知。實施 UIApplicationDelegate 協議的對象,可以接收那些通知,並提供適當的響應。

在接口塊中,您指定您的類符合或採用一個協議,方法是將該協議的名稱,放在尖括號 (<...>) 中,並且放在它繼承的類的名稱後面。在“您的首個 iOS 應用程序”中,您指明瞭採用 UITextFieldDelegate 協議,代碼行如下:

@interface HelloWorldViewController :UIViewController <UITextFieldDelegate> {

您無需聲明所實現的協議方法。

協議的聲明,看起來類似於類接口的聲明,只是協議沒有父類,並且不定義實例變量(儘管它們可以聲明屬性)。以下示例展示使用一個方法進行一個簡單的協議聲明:

@protocol MyProtocol
- (void)myProtocolMethod;
@end

對於許多委託協議來說,採用一個協議僅僅是實現該協議定義的方法。有些協議要求您明確地聲明支持該協議,而協議可以指定必需方法和可選方法。

當您開始探索 Objective-C 框架的頭文件時,將很快遇到如下的代碼行:

@interface NSDate (NSDateCreation)

這行代碼通過使用圓括號將類別名稱括起來的語法約定,聲明瞭該類別。類別是 Objective-C 語言的一項功能,可讓您擴展類的接口,而無需對類進行子類化。類別中的方法成爲類類型的一部分(在程序的作用範圍內),而這些方法由類的所有子類繼承。您可以將消息發送給類(或其子類)的任何實例,以調用在類別中定義的方法。

您可以將類別用作一種手段,來對頭文件內的相關方法聲明進行分組。您甚至還可以將不同的類別聲明放在不同的頭文件中。框架在其所有頭文件中使用這些技巧,來達到清晰明確。

您還可以使用稱爲類擴展的匿名類別,在實現 (.m) 文件中聲明專有屬性和專有方法。類擴展看起來類似於類別,只是圓括號之間沒有文本。例如,以下是一個典型的類擴展:

@interface MyAppDelegate ()
@property (strong) MyDataObject *data;
@end

已定義的類型和編碼策略

Objective-C 有幾個不能用作變量名稱的術語,保留用於特殊用途。其中部分術語是以 @ 符號爲前綴的編譯器指令,如 @interface 和@end。其他保留的術語,包括已定義的類型以及與這些類型相配的字面常量。Objective-C 使用很多已定義的類型和字面常量,這些卻不會出現在 ANSI C 中。在某些情況下,這些類型和字面常量替換 ANSI C 相應的類型和字面常量。下表介紹幾種重要類型,包括每種類型的允許字面常量。

類型

描述和字面常量

id

動態對象類型。動態類型化的對象和靜態類型化的對象的否定字面常量,都是 nil

Class

動態類類型。其否定字面常量是 Nil

SEL

選擇器的數據類型 (typedef);此數據類型表示運行時的方法簽名。其否定字面常量是 NULL

BOOL

Boolean 類型。字面常量值是 YES 和 NO

在錯誤檢查和控制流代碼中,通常使用已定義類型和字面常量。在程序的控制流語句中,您可以測試合適的字面常量,來確定如何繼續。例如:

NSDate *dateOfHire = [employee dateOfHire];
if (dateOfHire != nil) {
    // handle this case
}

解釋一下此代碼,如果表示僱用日期的對象不是 nil(換言之,如果它是一個有效對象),則邏輯在某個方向繼續。以下是執行相同分支的簡寫形式:

NSDate *dateOfHire = [employee dateOfHire];
if (dateOfHire) {
    // handle this case
}

您甚至可以進一步減少代碼行數(假設無需引用 dateOfHire 對象):

if ([employee dateOfHire]) {
    // handle this case
}

您可採用大體相同方式,來處理 Boolean 值。在下面的示例中,isEqual: 方法返回一個 Boolean 值。

BOOL equal = [objectA isEqual:objectB];
if (equal == YES) {
    // handle this case
}

您可以採用與測試是否存在 nil 的代碼相同的方式,來縮短此代碼。

在 Objective-C 中,您可以將消息發送到 nil,而沒有副作用。事實上,完全沒有影響,只是如果方法應該返回一個對象,運行時就會返回 nil。只要返回的內容類型化爲一個對象,即可保證發送給 nil 的消息的返回值正常運行。

Objective-C 中其他兩個重要的保留術語,是 self 和 super。第一個術語 self 是可在消息實現內使用的局部變量,用於引用當前對象;它等同於 C++ 中的 this。您可以用保留字 super 替換 self,但在消息表達式中,只能作爲接收者。如果您將消息發送到self,運行時先在當前對象的類中查找方法實現;如果在那裏找不到方法,則在其超類中查找(依此類推)。如果您將消息發送到super,運行時先在超類中查找方法實現。

self 和 super 的主要用途,都是發送消息。當要調用的方法是由 self 的類實現時,您將消息發送到 self。例如:

[self doSomeWork];

self 還用於點記法,用於調用由已聲明屬性合成的存取方法。例如:

NSString *theName = self.name;

在繼承自超類的方法的覆蓋(即重新實現)中,通常將消息發送到 super。在這種情況下,被調用方法與被覆蓋方法的簽名,都是相同的。



提供反饋意見
發佈了32 篇原創文章 · 獲贊 6 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章