Objective-C 入門教程

來自 http://www.runoob.com/w3cnote/objective-c-tutorial.html

Objective-C 是一種簡單的計算機語言,設計爲可以支持真正的面向對象編程。

Objective-C 通過提供類定義,方法以及屬性的語法,還有其他可以提高類的動態擴展能力的結構等,擴展了標準的 ANSI C 語言。類的語法和設計主要是基於 Smalltalk,最早的面向對象編程語言之一。

如果你以前使用過其他面向對象編程語言,那麼下面的信息可以幫助你學習 Objective-C 的基本語法。許多傳統的面向對象概念,例如封裝,繼承以及多態,在 Objective-C 中都有所體現。這裏有一些重要的不同,但是這些不同在這文章會表現出來,而且如果你需要還有更多詳細的信息存在。

如果你從來沒有使用任何編程語言編過程序,那麼你至少需要在開始之前,對相關概念進行一些基礎的瞭解。對象的使用和對象對象架構是 iPhone 程序設計的基礎,理解他們如何交互對創建你的程序非常重要。想了解面向對象概念的,請參看使用 Objective-C 進行面向對象編程。

Objective-C:C的超集

Objective-Objective-C是C語言的嚴格超集--任何C語言程序不經修改就可以直接通過Objective-C編譯器,在Objective-C中使用C語言代碼也是完全合法的。Objective-C被描述爲蓋在C語言上的薄薄一層,因爲Objective-C的原意就是在C語言主體上加入面向對象的特性。

Objective-C代碼的文件擴展名

擴展名 內容類型
.h      頭文件。頭文件包含類,類型,函數和常數的聲明。
.m      源代碼文件。這是典型的源代碼文件擴展名,可以包含 Objective-CC 代碼。
.mm     源代碼文件。帶有這種擴展名的源代碼文件,除了可以包含Objective-CC代碼以外還可以包含C++代碼。僅在你的Objective-C代碼中確實需要使用C++類或者特性的時候才用這種擴展名。

當你需要在源代碼中包含頭文件的時候,你可以使用標準的 #include 編譯選項,但是 Objective-C 提供了更好的方法。#import 選項和 #include 選項完全相同,只是它可以確保相同的文件只會被包含一次。Objective-C 的例子和文檔都傾向於使用 #import,你的代碼也應該是這樣的。

語法

Objective-C的面向對象語法源於Smalltalk消息傳遞風格。所有其他非面向對象的語法,包括變量類型,預處理器(preprocessing),流程控制,函數聲明與調用皆與C語言完全一致。但有些C語言語法合法代碼在objective-c中表達的意思不一定相同,比如某些布爾表達式,在C語言中返回值爲true,但在Objective-C若與yes直接相比較,函數將會出錯,因爲在Objective-C中yes的值只表示爲1。

第一個 Objective-C 程序,基於Xcode 4.3.1:

#import <Foundation/Foundation.h>

int main(int argc, char *argv[]) {

    @autoreleasepool {
        NSLog(@"Hello World!");
    }

   return 0;
}

消息傳遞

Objective-C最大的特色是承自Smalltalk的消息傳遞模型(message passing),此機制與今日C++式之主流風格差異甚大。Objective-C裏,與其說對象互相調用方法,不如說對象之間互相傳遞消息更爲精確。此二種風格的主要差異在於調用方法/消息傳遞這個動作。C++裏類別與方法的關係嚴格清楚,一個方法必定屬於一個類別,而且在編譯時(compile time)就已經緊密綁定,不可能調用一個不存在類別裏的方法。但在Objective-C,類別與消息的關係比較鬆散,調用方法視爲對對象發送消息,所有方法都被視爲對消息的迴應。所有消息處理直到運行時(runtime)纔會動態決定,並交由類別自行決定如何處理收到的消息。也就是說,一個類別不保證一定會迴應收到的消息,如果類別收到了一個無法處理的消息,程序只會拋出異常,不會出錯或崩潰。

C++裏,送一個消息給對象(或者說調用一個方法)的語法如下:

obj.method(argument);

Objective-C則寫成:

[obj method: argument];

此二者並不僅僅是語法上的差異,還有基本行爲上的不同。

這裏以一個汽車類(car class)的簡單例子來解釋Objective-C的消息傳遞特性:

[car fly];

典型的C++意義解讀是”調用car類別的fly方法”。若car類別裏頭沒有定義fly方法,那編譯肯定不會通過。但是Objective-C裏,我們應當解讀爲”發提交一個fly的消息給car對象”,fly是消息,而car是消息的接收者。car收到消息後會決定如何迴應這個消息,若car類別內定義有fly方法就運行方法內之代碼,若car內不存在fly方法,則程序依舊可以通過編譯,運行期則拋出異常。

此二種風格各有優劣。C++強制要求所有的方法都必須有對應的動作,且編譯期綁定使得函數調用非常快速。缺點是僅能借由virtual關鍵字提供有限的動態綁定能力。Objective-C天生即具備鴨子類型之動態綁定能力,因爲運行期才處理消息,允許發送未知消息給對象。可以送消息給整個對象集合而不需要一一檢查每個對象的類型,也具備消息轉送機制。同時空對象nil接受消息後默認爲不做事,所以送消息給nil也不用擔心程序崩潰。

字符串

作爲C語言的超集,Objective-C 支持 C 語言字符串方面的約定。也就是說,單個字符被單引號包括,字符串被雙引號包括。然而,大多數Objective-C通常不使用C語言風格的字符串。反之,大多數框架把字符串傳遞給NSString對象。NSString類提供了字符串的類包裝,包含了所有你期望的優點,包括對保存任意長度字符串的內建內存管理機制,支持Unicode,printf風格的格式化工具,等等。因爲這種字符串使用的非常頻繁,Objective-C提供了一個助記符可以方便地從常量值創建NSString對象。要使用這個助記符,你需要做的全部事情,是在普通的雙引號字符串前放置一個@符號,如下面的例子所示:

NSString* myString = @"My String\n";
NSString* anotherString = [NSString stringWithFormat:@"%d %s", 1, @"String"];

// 從一個C語言字符串創建Objective-C字符串
NSString*  fromCString = [NSString stringWithCString:"A C string"
encoding:NSASCIIStringEncoding];

如同所有其他的面嚮對象語言,類是 Objective-C 用來封裝數據,以及操作數據的行爲的基礎結構。對象就是類的運行期間實例,它包含了類聲明的實例變量自己的內存拷貝,以及類成員的指針。Objective-C 的類規格說明包含了兩個部分:定義(interface)與實現(implementation)。定義(interface)部分包含了類聲明和實例變量的定義,以及類相關的方法。實現(implementation)部分包含了類方法的實際代碼。

下圖展現了聲明一個叫做 MyClass 的類的語法,這個類繼承自 NSObject 基礎類。類聲明總是由 @interface 編譯選項開始,由 @end 編譯選項結束。類名之後的(用冒號分隔的)是父類的名字。類的實例(或者成員)變量聲明在被大括號包含的代碼塊中。實例變量塊後面就是類聲明的方法的列表。每個實例變量和方法聲明都以分號結尾。

類的定義文件遵循C語言之慣例以.h爲後綴,實現文件以.m爲後綴。

類聲明圖

類聲明圖

Interface

定義部分,清楚定義了類的名稱、數據成員和方法。 以關鍵字@interface作爲開始,@end作爲結束。

@interface MyObject : NSObject {
    int memberVar1; // 實體變量
    id  memberVar2;
}

+(return_type) class_method; // 類方法

-(return_type) instance_method1; // 實例方法
-(return_type) instance_method2: (int) p1;
-(return_type) instance_method3: (int) p1 andPar: (int) p2;
@end

方法前面的 +/- 號代表函數的類型:加號(+)代表類方法(class method),不需要實例就可以調用,與C++ 的靜態函數(static member function)相似。減號(-)即是一般的實例方法(instance method)。

這裏提供了一份意義相近的C++語法對照,如下:

class MyObject : public NSObject {
protected:
    int memberVar1;  // 實體變量
    void * memberVar2;

  public:
    static return_type class_method(); // 類方法

    return_type instance_method1();    // 實例方法
    return_type instance_method2( int p1 );
    return_type instance_method3( int p1, int p2 );
}

Objective-C定義一個新的方法時,名稱內的冒號(:)代表參數傳遞,不同於C語言以數學函數的括號來傳遞參數。Objective-C方法使得參數可以夾雜於名稱中間,不必全部附綴於方法名稱的尾端,可以提高程序可讀性。設定顏色RGB值的方法爲例:

- (void) setColorToRed: (float)red Green: (float)green Blue:(float)blue; /* 宣告方法*/

[myColor setColorToRed: 1.0 Green: 0.8 Blue: 0.2]; /* 呼叫方法*/

這個方法的簽名是setColorToRed:Green:Blue:。每個冒號後面都帶着一個float類別的參數,分別代表紅,綠,藍三色。

Implementation

實現區塊則包含了公開方法的實現,以及定義私有(private)變量及方法。 以關鍵字@implementation作爲區塊起頭,@end結尾。

@implementation MyObject {
  int memberVar3; //私有實體變數
}

+(return_type) class_method {
    .... //method implementation
}
-(return_type) instance_method1 {
     ....
}
-(return_type) instance_method2: (int) p1 {
    ....
}
-(return_type) instance_method3: (int) p1 andPar: (int) p2 {
    ....
}
@end

值得一提的是不只Interface區塊可定義實體變量,Implementation區塊也可以定義實體變量,兩者的差別在於訪問權限的不同,Interface區塊內的實體變量默認權限爲protected,宣告於implementation區塊的實體變量則默認爲private,故在Implementation區塊定義私有成員更匹配面向對象之封裝原則,因爲如此類別之私有信息就不需曝露於公開interface(.h文件)中。

創建對象

Objective-C創建對象需通過alloc以及init兩個消息。alloc的作用是分配內存,init則是初始化對象。 init與alloc都是定義在NSObject裏的方法,父對象收到這兩個信息並做出正確迴應後,新對象才創建完畢。以下爲範例:

MyObject * my = [[MyObject alloc] init];

在Objective-C 2.0裏,若創建對象不需要參數,則可直接使用new

MyObject * my = [MyObject new];

僅僅是語法上的精簡,效果完全相同。

若要自己定義初始化的過程,可以重寫init方法,來添加額外的工作。(用途類似C++ 的構造函數constructor)

方法

Objective-C 中的類可以聲明兩種類型的方法:實例方法和類方法。實例方法就是一個方法,它在類的一個具體實例的範圍內執行。也就是說,在你調用一個實例方法前,你必須首先創建類的一個實例。而類方法,比較起來,也就是說,不需要你創建一個實例。

方法聲明包括方法類型標識符,返回值類型,一個或多個方法標識關鍵字,參數類型和名信息。下圖展示 insertObject:atIndex: 實例方法的聲明。聲明由一個減號(-)開始,這表明這是一個實例方法。方法實際的名字(insertObject:atIndex:)是所有方法標識關鍵的級聯,包含了冒號。冒號表明了參數的出現。如果方法沒有參數,你可以省略第一個(也是唯一的)方法標識關鍵字後面的冒號。本例中,這個方法有兩個參數。

方法聲明語法

方法聲明語法

當你想調用一個方法,你傳遞消息到對應的對象。這裏消息就是方法標識符,以及傳遞給方法的參數信息。發送給對象的所有消息都會動態分發,這樣有利於實現Objective-C類的多態行爲。也就是說,如果子類定義了跟父類的具有相同標識符的方法,那麼子類首先收到消息,然後可以有選擇的把消息轉發(也可以不轉發)給他的父類。

消息被中括號( [ 和 ] )包括。中括號中間,接收消息的對象在左邊,消息(包括消息需要的任何參數)在右邊。例如,給myArray變量傳遞消息insertObject:atIndex:消息,你需要使用如下的語法:

[myArray insertObject:anObj atIndex:0];

爲了避免聲明過多的本地變量保存臨時結果,Objective-C允許你使用嵌套消息。每個嵌套消息的返回值可以作爲其他消息的參數或者目標。例如,你可以用任何獲取這種值的消息來代替前面例子裏面的任何變量。所以,如果你有另外一個對象叫做myAppObject擁有方法,可以訪問數組對象,以及插入對象到一個數組,你可以把前面的例子寫成如下的樣子:

[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];

雖然前面的例子都是傳遞消息給某個類的實例,但是你也可以傳遞消息給類本身。當給類發消息,你指定的方法必須被定義爲類方法,而不是實例方法。你可以認爲類方法跟C++類裏面的靜態成員有點像(但是不是完全相同的)。

類方法的典型用途是用做創建新的類實例的工廠方法,或者是訪問類相關的共享信息的途徑。類方法聲明的語法跟實例方法的幾乎完全一樣,只有一點小差別。與實例方法使用減號作爲方法類型標識符不同,類方法使用加號( + )。

下面的例子演示了一個類方法如何作爲類的工廠方法。在這裏,arrayWithCapacity是NSMutableArray類的類方法,爲類的新實例分配內容並初始化,然後返回給你。

NSMutableArray*   myArray = nil; // nil 基本上等同於 NULL

// 創建一個新的數組,並把它賦值給 myArray 變量
myArray = [NSMutableArray arrayWithCapacity:0];

屬性

屬性是用來代替聲明存取方法的便捷方式。屬性不會在你的類聲明中創建一個新的實例變量。他們僅僅是定義方法訪問已有的實例變量的速記方式而已。暴露實例變量的類,可以使用屬性記號代替getter和setter語法。類還可以使用屬性暴露一些“虛擬”的實例變量,他們是部分數據動態計算的結果,而不是確實保存在實例變量內的。

實際上可以說,屬性節約了你必須要寫的大量多餘的代碼。因爲大多數存取方法都是用類似的方式實現的,屬性避免了爲類暴露的每個實例變量提供不同的getter和setter的需求。取而代之的是,你用屬性聲明指定你希望的行爲,然後在編譯期間合成基於聲明的實際的getter和setter方法。

屬性聲明應該放在類接口的方法聲明那裏。基本的定義使用@property編譯選項,緊跟着類型信息和屬性的名字。你還可以用定製選項對屬性進行配置,這決定了存取方法的行爲。下面的例子展示了一些簡單的屬性聲明:

@interface Person : NSObject {
    @public
        NSString *name;
    @private
        int age;
}

@property(copy) NSString *name;
@property(readonly) int age;

-(id)initWithAge:(int)age;
@end

屬性的訪問方法由@synthesize關鍵字來實現,它由屬性的聲明自動的產生一對訪問方法。另外,也可以選擇使用@dynamic關鍵字表明訪問方法會由程序員手工提供。

@implementation Person
@synthesize name;
@dynamic age;

-(id)initWithAge:(int)initAge
{
    age = initAge; // 注意:直接賦給成員變量,而非屬性
    return self;
}

-(int)age
{
    return 29; // 注意:並非返回真正的年齡
}
@end

屬性可以利用傳統的消息表達式、點表達式或”valueForKey:”/”setValue:forKey:”方法對來訪問。

Person *aPerson = [[Person alloc] initWithAge: 53];
aPerson.name = @"Steve"; // 注意:點表達式,等於[aPerson setName: @"Steve"];
NSLog(@"Access by message (%@), dot notation(%@), property name(%@) and direct instance variable access (%@)",
      [aPerson name], aPerson.name, [aPerson valueForKey:@"name"], aPerson->name);

爲了利用點表達式來訪問實例的屬性,需要使用”self”關鍵字:

-(void) introduceMyselfWithProperties:(BOOL)useGetter
{
    NSLog(@"Hi, my name is %@.", (useGetter ? self.name : name)); // NOTE: getter vs. ivar access
}

類或協議的屬性可以被動態的讀取。

int i;
int propertyCount = 0;
objc_property_t *propertyList = class_copyPropertyList([aPerson class], &propertyCount);

for ( i=0; i < propertyCount; i++ ) {
    objc_property_t *thisProperty = propertyList + i;
    const char* propertyName = property_getName(*thisProperty);
    NSLog(@"Person has a property: '%s'", propertyName);
}

快速枚舉

比起利用NSEnumerator對象或在集合中依次枚舉,Objective-C 2.0提供了快速枚舉的語法。在Objective-C 2.0中,以下循環的功能是相等的,但性能特性不同。

// 使用NSEnumerator
NSEnumerator *enumerator = [thePeople objectEnumerator];
Person *p;

while ( (p = [enumerator nextObject]) != nil ) {
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用依次枚舉
for ( int i = 0; i < [thePeople count]; i++ ) {
    Person *p = [thePeople objectAtIndex:i];
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}
// 使用快速枚舉
for (Person *p in thePeople) {
    NSLog(@"%@ is %i years old.", [p name], [p age]);
}

快速枚舉可以比標準枚舉產生更有效的代碼,由於枚舉所調用的方法被使用NSFastEnumeration協議提供的指針算術運算所代替了。

協議(Protocol)

協議是一組沒有實現的方法列表,任何的類均可採納協議並具體實現這組方法。

Objective-C在NeXT時期曾經試圖引入多重繼承的概念,但由於協議的出現而沒有實現之。

協議類似於Java與C#語言中的”接口”。在Objective-C中,有兩種定義協議的方式:由編譯器保證的”正式協議”,以及爲特定目的設定的”非正式協議”。

非正式協議爲一個可以選擇性實現的一系列方法列表。非正式協議雖名爲協議,但實際上是掛於NSObject上的未實現分類(Unimplemented Category)的一種稱謂,Objetive-C語言機制上並沒有非正式協議這種東西,OSX 10.6版本之後由於引入@optional關鍵字,使得正式協議已具備同樣的能力,所以非正式協議已經被廢棄不再使用。

正式協議類似於Java中的”接口”,它是一系列方法的列表,任何類都可以聲明自身實現了某個協議。在Objective-C 2.0之前,一個類必須實現它聲明匹配的協議中的所有方法,否則編譯器會報告錯誤,表明這個類沒有實現它聲明匹配的協議中的全部方法。Objective-C 2.0版本允許標記協議中某些方法爲可選的(Optional),這樣編譯器就不會強制實現這些可選的方法。

協議經常應用於Cocoa中的委託及事件觸發。例如文本框類通常會包括一個委託(delegate)對象,該對象可以實現一個協議,該協議中可能包含一個實現文字輸入的自動完成方法。若這個委託對象實現了這個方法,那麼文本框類就會在適當的時候觸發自動完成事件,並調用這個方法用於自動完成功能。

Objective-C中協議的概念與Java中接口的概念並不完全相同,即一個類可以在不聲明它匹配某個協議的情況下,實現這個協議所包含的方法,也即實質上匹配這個協議,而這種差別對外部代碼而言是不可見的。正式協議的聲明不提供實現,它只是簡單地表明匹配該協議的類實現了該協議的方法,保證調用端可以安全調用方法。

語法

協議以關鍵字@protocol作爲區塊起始,@end結束,中間爲方法列表。

@protocol Locking
- (void)lock;
- (void)unlock;
@end

這是一個協議的例子,多線程編程中經常要確保一份共享資源同時只有一個線程可以使用,會在使用前給該資源掛上鎖 ,以上即爲一個表明有”鎖”的概念的協議,協議中有兩個方法,只有名稱但尚未實現。

下面的SomeClass宣稱他採納了Locking協議:

@interface SomeClass : SomeSuperClass <Locking>
@end

一旦SomeClass表明他採納了Locking協議,SomeClass就有義務實現Locking協議中的兩個方法。

@implementation SomeClass
- (void)lock {
  // 實現lock方法...
}
- (void)unlock {
  // 實現unlock方法...
}
@end

由於SomeClass已經確實遵從了Locking協議,故調用端可以安全的發送lock或unlock消息給SomeClass實體變量,不需擔心他沒有辦法迴應消息。

插件是另一個使用抽象定義的例子,可以在不關心插件的實現的情況下定義其希望的行爲。

動態類型

類似於Smalltalk,Objective-C具備動態類型:即消息可以發送給任何對象實體,無論該對象實體的公開接口中有沒有對應的方法。對比於C++這種靜態類型的語言,編譯器會擋下對(void*)指針調用方法的行爲。但在Objective-C中,你可以對id發送任何消息(id很像void*,但是被嚴格限制只能使用在對象上),編譯器僅會發出”該對象可能無法迴應消息”的警告,程序可以通過編譯,而實際發生的事則取決於運行期該對象的真正形態,若該對象的確可以迴應消息,則依舊運行對應的方法。

一個對象收到消息之後,他有三種處理消息的可能手段,第一是迴應該消息並運行方法,若無法迴應,則可以轉發消息給其他對象,若以上兩者均無,就要處理無法迴應而拋出的例外。只要進行三者之其一,該消息就算完成任務而被丟棄。若對”nil”(空對象指針)發送消息,該消息通常會被忽略,取決於編譯器選項可能會拋出例外。

雖然Objective-C具備動態類型的能力,但編譯期的靜態類型檢查依舊可以應用到變量上。以下三種聲明在運行時效力是完全相同的,但是三種聲明提供了一個比一個更明顯的類型信息,附加的類型信息讓編譯器在編譯時可以檢查變量類型,並對類型不符的變量提出警告。

下面三個方法,差異僅在於參數的形態:

- setMyValue:(id) foo;

id形態表示參數”foo”可以是任何類的實例。

- setMyValue:(id <aProtocol>) foo;

id表示”foo”可以是任何類的實例,但必須採納”aProtocol”協議。

- setMyValue:(NSNumber*) foo;

該聲明表示”foo”必須是”NSNumber”的實例。

動態類型是一種強大的特性。在缺少泛型的靜態類型語言(如Java 5以前的版本)中實現容器類時,程序員需要寫一種針對通用類型對象的容器類,然後在通用類型和實際類型中不停的強制類型轉換。無論如何,類型轉換會破壞靜態類型,例如寫入一個”整數”而將其讀取爲”字符串”會產生運行時錯誤。這樣的問題被泛型解決,但容器類需要其內容對象的類型一致,而對於動態類型語言則完全沒有這方面的問題。

轉發

Objective-C允許對一個對象發送消息,不管它是否能夠響應之。除了響應或丟棄消息以外,對象也可以將消息轉發到可以響應該消息的對象。轉發可以用於簡化特定的設計模式,例如觀測器模式或代理模式。

Objective-C運行時在Object中定義了一對方法:

轉發方法:

- (retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC
- (id) forward:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

響應方法:

- (retval_t) performv:(SEL) sel :(arglist_t) args;  // with GCC
- (id) performv:(SEL) sel :(marg_list) args; // with NeXT/Apple systems

希望實現轉發的對象只需用新的方法覆蓋以上方法來定義其轉發行爲。無需重寫響應方法performv::,由於該方法只是單純的對響應對象發送消息並傳遞參數。其中,SEL類型是Objective-C中消息的類型。

以下代碼演示了轉發的基本概念:

Forwarder.h 文件代碼:

#import <objc/Object.h>

@interface Forwarder : Object
{
    id recipient; //該對象是我們希望轉發到的對象。
}

@property (assign, nonatomic) id recipient;

@end

Forwarder.m 文件代碼:

#import "Forwarder.h"

@implementation Forwarder

@synthesize recipient;

- (retval_t) forward: (SEL) sel : (arglist_t) args
{
    /*
     *檢查轉發對象是否響應該消息。
     *若轉發對象不響應該消息,則不會轉發,而產生一個錯誤。
     */
    if([recipient respondsTo:sel])
       return [recipient performv: sel : args];
    else
       return [self error:"Recipient does not respond"];
}

Recipient.h 文件代碼:

#import <objc/Object.h>

// A simple Recipient object.
@interface Recipient : Object
- (id) hello;
@end

Recipient.m 文件代碼:

#import "Recipient.h"

@implementation Recipient

- (id) hello
{
    printf("Recipient says hello!\n");

    return self;
}

@end

main.m 文件代碼:

#import "Forwarder.h"
#import "Recipient.h"

int main(void)
{
    Forwarder *forwarder = [Forwarder new];
    Recipient *recipient = [Recipient new];

    forwarder.recipient = recipient; //Set the recipient.
    /*
     *轉發者不響應hello消息!該消息將被轉發到轉發對象。
     *(若轉發對象響應該消息)
     */
    [forwarder hello];

    return 0;
}

利用GCC編譯時,編譯器報告:

$ gcc -x objective-c -Wno-import Forwarder.m Recipient.m main.m -lobjc
main.m: In function `main':
main.m:12: warning: `Forwarder' does not respond to `hello'
$

如前文所提到的,編譯器報告Forwarder類不響應hello消息。在這種情況下,由於實現了轉發,可以忽略這個警告。 運行該程序產生如下輸出:

$ ./a.out
Recipient says hello!

類別 (Category)

在Objective-C的設計中,一個主要的考慮即爲大型代碼框架的維護。結構化編程的經驗顯示,改進代碼的一種主要方法即爲將其分解爲更小的片段。Objective-C借用並擴展了Smalltalk實現中的”分類”概念,用以幫助達到分解代碼的目的。

一個分類可以將方法的實現分解進一系列分離的文件。程序員可以將一組相關的方法放進一個分類,使程序更具可讀性。舉例來講,可以在字符串類中增加一個名爲”拼寫檢查”的分類,並將拼寫檢查的相關代碼放進這個分類中。

進一步的,分類中的方法是在運行時被加入類中的,這一特性允許程序員向現存的類中增加方法,而無需持有原有的代碼,或是重新編譯原有的類。例如若系統提供的字符串類的實現中不包含拼寫檢查的功能,可以增加這樣的功能而無需更改原有的字符串類的代碼。

在運行時,分類中的方法與類原有的方法並無區別,其代碼可以訪問包括私有類成員變量在內的所有成員變量。

若分類聲明瞭與類中原有方法同名的函數,則分類中的方法會被調用。因此分類不僅可以增加類的方法,也可以代替原有的方法。這個特性可以用於修正原有代碼中的錯誤,更可以從根本上改變程序中原有類的行爲。若兩個分類中的方法同名,則被調用的方法是不可預測的。

其它語言也嘗試了通過不同方法增加這一語言特性。TOM在這方面走的更遠,不僅允許增加方法,更允許增加成員變量。也有其它語言使用面向聲明的解決方案,其中最值得注意的是Self語言。

C#與Visual Basic.NET語言以擴展函數的與不完全類的方式實現了類似的功能。Ruby與一些動態語言則以”monkey patch”的名字稱呼這種技術。

使用分類的例子

這個例子創建了Integer類,其本身只定義了integer屬性,然後增加了兩個分類Arithmetic與Display以擴展類的功能。雖然分類可以訪問類的私有成員,但通常利用屬性的訪問方法來訪問是一種更好的做法,可以使得分類與原有類更加獨立。這是分類的一種典型應用—另外的應用是利用分類來替換原有類中的方法,雖然用分類而不是繼承來替換方法不被認爲是一種好的做法。

Integer.h 文件代碼:

#import <objc/Object.h>

@interface Integer : Object
{
@private
    int integer;
}

@property (assign, nonatomic) integer;

@end

Integer.m 文件代碼:

#import "Integer.h"

@implementation Integer

@synthesize integer;

@end

Arithmetic.h 文件代碼:

#import "Integer.h"

@interface Integer(Arithmetic)
- (id) add: (Integer *) addend;
- (id) sub: (Integer *) subtrahend;
@end

Arithmetic.m 文件代碼:

#import "Arithmetic.h"

@implementation Integer(Arithmetic)
- (id) add: (Integer *) addend
{
    self.integer = self.integer + addend.integer;
    return self;
}

- (id) sub: (Integer *) subtrahend
{
    self.integer = self.integer - subtrahend.integer;
    return self;
}
@end

Display.h 文件代碼:

#import "Integer.h"

@interface Integer(Display)
- (id) showstars;
- (id) showint;
@end

Display.m 文件代碼:

#import "Display.h"

@implementation Integer(Display)
- (id) showstars
{
    int i, x = self.integer;
    for(i=0; i < x; i++)
       printf("*");
    printf("\n");

    return self;
}

- (id) showint
{
    printf("%d\n", self.integer);

    return self;
}
@end

main.m 文件代碼:

#import "Integer.h"
#import "Arithmetic.h"
#import "Display.h"

int
main(void)
{
    Integer *num1 = [Integer new], *num2 = [Integer new];
    int x;

    printf("Enter an integer: ");
    scanf("%d", &x);

    num1.integer = x;
    [num1 showstars];

    printf("Enter an integer: ");
    scanf("%d", &x);

    num2.integer = x;
    [num2 showstars];

    [num1 add:num2];
    [num1 showint];

    return 0;
}

利用以下命令來編譯:

gcc -x objective-c main.m Integer.m Arithmetic.m Display.m -lobjc

在編譯時間,可以利用省略#import “Arithmetic.h” 與[num1 add:num2]命令,以及Arithmetic.m文件來實驗。程序仍然可以運行,這表明了允許動態的、按需的加載分類;若不需要某一分類提供的功能,可以簡單的不編譯之。

垃圾收集

Objective-C 2.0提供了一個可選的垃圾收集器。在向後兼容模式中,Objective-C運行時會將引用計數操作,例如”retain”與”release”變爲無操作。當垃圾收集啓用時,所有的對象都是收集器的工作對象。普通的C指針可以以”__strong”修飾,標記指針指向的對象仍在使用中。被標記爲”__weak”的指針不被計入收集器的計數中,並在對象被回收時改寫爲”nil”。iOS上的Objective-C 2.0實現中不包含垃圾收集器。垃圾收集器運行在一個低優先級的後臺線程中,並可以在用戶動作時暫停,從而保持良好的用戶體驗。

參考鏈接:https://zh.wikipedia.org/wiki/Objective-C

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章