Objective-C

Objective-C是一種通用、高階、面向物件的程式語言。它擴充套件了標準的ANSI C程式語言，將Smalltalk式的訊息傳遞機制加入到ANSI C中。目前主要支援的編譯器有GCC和Clang。Objective-C的商標權屬於蘋果公司，蘋果公司也是這個程式語言的主要開發者。蘋果在開發NeXTSTEP作業系統時使用了Objective-C，之後被macOS,iPadOS,watchOS和iOS繼承下來。現在Objective-C與Swift是OS X和iOS作業系統、及與其相關的API、Cocoa和Cocoa Touch的主要程式語言。歷史.Objective-C 主要由 公司的布萊德·考克斯 在 1980 年代發明。1981年 Brad Cox 和 Tom Love 還在 ITT 公司技術中心任職時，接觸到了 SmallTalk語言。Cox 當時對軟體設計和開發問題非常感興趣，他很快地意識到 SmallTalk語言 在系統工程構建中具有無法估量的價值，但同時他和 Tom Love 也明白，目前 ITT 公司的電子通訊工程相關技術中，C 語言被放在很重要的位置。於是 Cox 撰寫了一個 C 語言的前處理器，打算使 C 語言具備些許 Smalltalk 的本領。Cox 很快地實現了一個可用的 C 語言擴充套件，此即為 Objective-C語言的前身。到了 1983 年，Cox 與 Love 合夥成立了 Productivity Products International公司，將 Objective-C 及其相關函式庫商品化販售，並在之後將公司改名為StepStone。1986年，Cox 出版了一本關於 Objective-C 的重要著作《Object-Oriented Programming, An Evolutionary Approach》，書內詳述了 Objective-C 的種種設計理念。1988年，斯蒂夫·喬布斯平臺為主的軟體公司。1992年，自由軟體基金會的 GNU 開發環境增加了對 Objective-C 的支援。1994年，NeXT Computer公司和Sun Microsystem聯合釋出了一個針對 NEXTSTEP 系統的標準典範，名為 OPENSTEP。OPENSTEP 在自由軟體基金會的實現名稱為 GNUstep。1996年12月20日，蘋果公司宣佈收購 NeXT Software 公司，NEXTSTEP/OPENSTEP環境成為蘋果作業系統下一個主要發行版本OS X的基礎。這個開發環境的版本被蘋果公司稱為Cocoa。2005年，蘋果電腦僱用了克里斯·拉特納及LLVM開發團隊，clang及LLVM成為蘋果公司在GCC之外的新編譯器選擇，在 Xcode 4.0之後均採用 LLVM 作為預設的編譯器。最新的 Modern Objective-C 特性也都率先在 Clang 上實現。語法.Objective-C是C語言的嚴格超集－－任何C語言程式不經修改就可以直接透過Objective-C編譯器，在Objective-C中使用C語言程式碼也是完全合法的。Objective-C被描述為"蓋在C語言上的薄薄一層"，因為Objective-C的原意就是在C語言主體上加入面向物件的特性。Objective-C的面向物件語法源於Smalltalk訊息傳遞風格。所有其他非面向物件的語法，包括變數型別，前處理器，流程控制，函式宣告與呼叫皆與C語言完全一致。但有些C語言語法合法程式碼在objective-c中表達的意思不一定相同，比如某些布林表示式，在C語言中返回值為true，但在Objective-C若與true直接相比較，函式將會出錯，因為在Objective-C中true的值只表示為1.Hello World.這裡示範了一個基礎的Hello World程式。基於Xcode 4.3.1:int main(int argc, char *argv[]) {@autoreleasepool {NSLog(@"Hello World!");return 0;訊息傳遞.Objective-C最大的特色是承自Smalltalk的訊息傳遞模型才會動態決定，並交由類別自行決定如何處理收到的訊息。也就是說，一個類別不保證一定會回應收到的訊息，如果類別收到了一個無法處理的訊息，程式只會丟擲異常，不會出錯或崩潰。C++裡，送一個訊息給物件的語法如下：obj.method(argument);Objective-C則寫成：[obj method: argument];此二者並不僅僅是語法上的差異，還有基本行為上的不同。這裡以一個汽車類的簡單例子來解釋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也不用擔心程式崩潰。類的定義與實現.Objective-C中強制要求將類的介面分為兩個部分。類的定義檔案遵循C語言之慣例以.h為字尾，實現檔案以.m為字尾。介面.定義部分，清楚定義了類的名稱、成員變數和方法。以關鍵字@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方法前面的 +/- 號代表函式的型別：加號。這裡提供了一份意義相近的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作為區段起頭，@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中。建立物件.Objective-C建立物件需透過alloc以及init兩個訊息。alloc的作用是分配記憶體，init則是初始化物件。init與alloc都是定義在NSObject裡的方法，父物件收到這兩個資訊並做出正確回應後，新物件才建立完畢。以下為範例：MyObject * my = [[MyObject alloc] init];在Objective-C 2.0裡，若建立物件不需要引數，則可直接使用newMyObject * my = [MyObject new];僅僅是語法上的精簡，效果完全相同。若要自己定義初始化的過程，可以重寫init方法，來新增額外的工作。- (id) init {if ( self=[super init] ) { // 必須呼叫父類的init// do something here ...return self;協議.協議是一組沒有實現的方法列表，任何的類均可採納協議並具體實現這組方法。Objective-C在[[NeXT]]時期曾經試圖引入[[多重繼承]]的概念，但由於[[協議 (面向物件程式設計)|協議]]的出現而沒有實現之。[[協議 (面向物件程式設計)|協議]]類似於[[Java]]與[[C♯|C#]]語言中的“[[介面 (程式設計)|介面]]”。在Objective-C中，有兩種定義協議的方式：由編譯器保證的“正式協議”，以及為特定目的設定的“非正式協議”。非正式協議為一個可以選擇性實現的一系列方法列表。非正式協議雖名為協議，但實際上是掛於NSObject上的未實現分類的一種稱謂，Objetive-C語言機制上並沒有非正式協議這種東西，OSX 10.6版本之後由於引入@optional關鍵字，使得正式協議已具備同樣的能力，所以非正式協議已經被廢棄不再使用。正式協議類似於Java中的"介面"，它是一系列方法的列表，任何類都可以宣告自身實現了某個協議。在Objective-C 2.0之前，一個類必須實現它宣告符合的協議中的所有方法，否則編譯器會報告錯誤，表明這個類沒有實現它宣告符合的協議中的全部方法。Objective-C 2.0版本允許標記協議中某些方法為可選的，這樣編譯器就不會強制實現這些可選的方法。協議經常應用於Cocoa中的[[Delegate (CLI)|委託]]及事件觸發。例如文字框類通常會包括一個[[Delegate (CLI)|委託]]物件，該物件可以實現一個協議，該協議中可能包含一個實現文字輸入的自動完成方法。若這個委託物件實現了這個方法，那麼文字框類就會在適當的時候觸發自動完成事件，並呼叫這個方法用於自動完成功能。Objective-C中協議的概念與Java中介面的概念並不完全相同，即一個類可以在不宣告它符合某個協議的情況下，實現這個協議所包含的方法，也即實質上符合這個協議，而這種差別對外部程式碼而言是不可見的。正式協議的宣告不提供實現，它只是簡單地表明符合該協議的類實現了該協議的方法，保證呼叫端可以安全呼叫方法。語法協議以關鍵字@protocol作為區段起始，@end結束，中間為方法列表。@protocol Locking- (void)lock;- (void)unlock;@end這是一個協議的例子，多執行緒程式設計中經常要確保一份共享資源同時只有一個執行緒可以使用，會在使用前給該資源掛上鎖，以上即為一個表明有“鎖”的概念的協議，協議中有兩個方法，只有名稱但尚未實現。下面的SomeClass宣稱他採納了Locking協議：@interface SomeClass : SomeSuperClass@end一旦SomeClass表明他採納了Locking協議，SomeClass就有義務實現Locking協議中的兩個方法。@implementation SomeClass- (void)lock {// 實現lock方法...- (void)unlock {// 實現unlock方法...@end由於SomeClass已經確實遵從了Locking協議，故呼叫端可以安全的傳送lock或unlock訊息給SomeClass實體變數，不需擔心他沒有辦法回應訊息。[[外掛]]是另一個使用抽象定義的例子，可以在不關心外掛的實現的情況下定義其希望的行為。動態型別.類似於Smalltalk，Objective-C具備[[動態型別]]：即訊息可以傳送給任何物件實體，無論該物件實體的公開介面中有沒有對應的方法。對比於C++這種靜態型別的語言，編譯器會擋下對，編譯器僅會發出「該物件可能無法回應訊息」的警告，程式可以透過編譯，而實際發生的事則取決於執行期該物件的真正形態，若該物件的確可以回應訊息，則依舊執行對應的方法。一個物件收到訊息之後，他有三種處理訊息的可能手段，第一是回應該訊息並執行方法，若無法回應，則可以轉發訊息給其他物件，若以上兩者均無，就要處理無法回應而拋出的例外。只要進行三者之其一，該訊息就算完成任務而被丟棄。若對“nil”傳送訊息，該訊息通常會被忽略，取決於編譯器選項可能會丟擲例外。雖然Objective-C具備動態型別的能力，但[[編譯期]]的靜態型別檢查依舊可以應用到變數上。以下三種宣告在[[執行期|執行時]]效力是完全相同的，但是三種宣告提供了一個比一個更明顯的型別資訊，附加的型別資訊讓編譯器在編譯時可以檢查變數型別，並對型別不符的變數提出警告。下面三個方法，差異僅在於引數的形態：- setMyValue:(id) foo;id形態表示引數“foo”可以是任何類的例項。- setMyValue:(id ) foo;id表示“foo”可以是任何類的例項，但必須採納“aProtocol”協議。- setMyValue:(NSNumber*) foo;該宣告表示“foo”必須是“NSNumber”的例項。動態型別是一種強大的特性。在缺少[[泛型]]的靜態型別語言中實現[[容器_(計算機科學)|容器]]類時，程式設計師需要寫一種針對通用型別物件的容器類，然後在通用型別和實際型別中不停的[[強制型別轉換]]。無論如何，型別轉換會破壞靜態型別，例如寫入一個“[[整數]]”而將其讀取為“[[字串]]”會產生執行時錯誤。這樣的問題被泛型解決，但容器類需要其內容物件的型別一致，而對於動態型別語言則完全沒有這方面的問題。轉發.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::，由於該方法只是單純的對響應物件傳送訊息並傳遞引數。其中，codice_1型別是Objective-C中訊息的型別。例子.這裡包括了一個演示轉發的基本概念的程式示例。@interface Forwarder : Objectid recipient; //該物件是我們希望轉發到的物件。@property (assign, nonatomic) id recipient;@end@implementation Forwarder@synthesize recipient;- (retval_t) forward: (SEL) sel : (arglist_t) args/**檢查轉發物件是否響應該訊息。*若轉發物件不響應該訊息，則不會轉發，而產生一個錯誤。if([recipient respondsTo:sel])return [recipient performv: sel : args];elsereturn [self error:"Recipient does not respond"];// A simple Recipient object.@interface Recipient : Object- (id) hello;@end@implementation Recipient- (id) helloprintf("Recipient says hello!\n");return self;@endint 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 -lobjcmain.m: In function `main':main.m:12: warning: `Forwarder' does not respond to `hello'如前文所提到的，編譯器報告Forwarder類不響應hello訊息。在這種情況下，由於實現了轉發，可以忽略這個警告。執行該程式產生如下輸出：$ ./a.outRecipient says hello!類別 (Category).在Objective-C的設計中，一個主要的考慮即為大型程式碼框架的維護。[[結構化程式設計]]的經驗顯示，改進程式碼的一種主要方法即為將其分解為更小的片段。Objective-C借用並擴充套件了Smalltalk實現中的“分類”概念，用以幫助達到分解程式碼的目的。一個分類可以將方法的實現分解進一系列分離的檔案。程式設計師可以將一組相關的方法放進一個分類，使程式更具可讀性。舉例來講，可以在字串類中增加一個名為“拼寫檢查”的分類，並將拼寫檢查的相關程式碼放進這個分類中。進一步的，分類中的方法是在[[執行期|執行時]]被加入類中的，這一特性允許程式設計師向現存的類中增加方法，而無需持有原有的程式碼，或是重新編譯原有的類。例如若系統提供的字串類的實現中不包含[[拼寫檢查]]的功能，可以增加這樣的功能而無需更改原有的字串類的程式碼。在執行時，分類中的方法與類原有的方法並無區別，其程式碼可以存取包括私有類成員變數在內的所有成員變數。若分類聲明瞭與類中原有方法同名的函式，則分類中的方法會被呼叫。因此分類不僅可以增加類的方法，也可以代替原有的方法。這個特性可以用於修正原有程式碼中的錯誤，更可以從根本上改變程式中原有類的行為。若兩個分類中的方法同名，則被呼叫的方法是不可預測的。其它語言也嘗試了透過不同方法增加這一語言特性。在這方面走的更遠，不僅允許增加方法，更允許增加成員變數。也有其它語言使用[[基於原型程式設計|面向宣告]]的解決方案，其中最值得注意的是[[Self語言]]。C#與Visual Basic.NET語言以的與不完全類的方式實現了類似的功能。[[Ruby]]與一些動態語言則以"[[猴補丁|Monkey patch]]"的名字稱呼這種技術。使用分類的例子.這個例子建立了Integer類，其本身只定義了integer屬性，然後增加了兩個分類Arithmetic與Display以擴充套件類的功能。雖然分類可以訪問類的私有成員，但通常利用屬性的[[方法 (電腦科學)|存取方法]]來存取是一種更好的做法，可以使得分類與原有類更加獨立。這是分類的一種典型應用—另外的應用是利用分類來替換原有類中的方法，雖然用分類而不是繼承來替換方法不被認為是一種好的做法。@interface Integer : Object@privateint integer;@property (assign, nonatomic) integer;@end@implementation Integer@synthesize integer;@end@interface Integer(Arithmetic)- (id) add: (Integer *) addend;- (id) sub: (Integer *) subtrahend;@end@implementation Integer(Arithmetic)- (id) add: (Integer *) addendself.integer = self.integer + addend.integer;return self;- (id) sub: (Integer *) subtrahendself.integer = self.integer - subtrahend.integer;return self;@end@interface Integer(Display)- (id) showstars;- (id) showint;@end@implementation Integer(Display)- (id) showstarsint i, x = self.integer;for(i=0; i name);為了利用點表示式來存取例項的屬性，需要使用“self”關鍵字：-(void) introduceMyselfWithProperties:(BOOL)useGetterNSLog(@"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], &amp;propertyCount);for ( i=0; i &lt; 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中，以下迴圈的功能是相等的，但效能特性不同。// 使用NSEnumeratorNSEnumerator *enumerator = [thePeople objectEnumerator];Person *p;while ( (p = [enumerator nextObject]) != nil ) {NSLog(@"%@ is %i years old.", [p name], [p age]);// 使用依次列舉for ( int i = 0; i &lt; [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協議提供的指標算術運算所代替了。Modern Objective-C.蘋果公司在 WWDC2012 大會上介紹了大量 Objective-C 的新特性，能夠讓程式設計師更加高效地編寫程式碼，這些新特性在 Xcode4.4 版本中已經可以使用。語言分析.Objective-C是非常「實際」的語言。它用一個很小的、用C寫成的[[執行時系統|執行庫]]，使得應用程式的大小增加很少，與此相比，大部分OO系統需要極大的執行時虛擬機器來執行。Objective-C寫成的程式通常不會比其原始碼和函式庫一類的最佳化，使得Objective-C的應用程式一般比類似的C或C++程式更大。Objective-C可以在現存基礎上實現，而不需要編寫一個全新的編譯器。這個特性使得Objective-C能利用大量現存的C程式碼、庫、工具和程式設計思想等資源。現存C庫可以用Obj-C包裝器來提供一個Objective-C使用的OO風格介面包裝。以上這些特性極大地降低了進入Objective-C的門檻，這是1980年代Smalltalk在推廣中遇到的最大問題。爭議.在最初的版本，Objective-C並不支援[[垃圾回收 (電腦科學)|垃圾回收]]；此事在當時是爭論的焦點之一。很多人考慮到 Smalltalk 回收時有漫長的「死亡時間」，令整個系統失去功用， Objective-C 為避免此問題才不實現垃圾回收。某些第三方版本加入了這個功能，而[[蘋果公司]]最終也在其Mac OS X 10.5版本中實現了垃圾回收。另一個廣受批評的問題是Objective-C不包括[[名稱空間]]機制。取而代之的是程式設計師必須在其類別名稱加上字首，由於字首往往較短，這時常引致衝突。在2007年，在Cocoa程式設計環境中，所有Mac OS X類別和函式均有「NS」作為字首，例如NSObject或NSButton來清楚分辨它們屬於Mac OS X核心；使用「NS」是由於這些類別的名稱在NeXTSTEP開發時定下。雖然Objective-C是C的嚴格物件; zh-cn:物件; zh-tw:物件。和C++不同，Objective-C不支援[[運運算元多載]]。Categories和protocols不但可以提供很多多重繼承的好處，而且沒有很多缺點，例如額外執行時間過重和二進位制不相容。#重定向 [[Template:Citation needed]]由於Objective-C使用動態執行時型別，而且所有的方法都是函式呼叫。不過Objective-C擁護者認為Objective-C本就不應應用於C++或Java常見的底層抽象，Objective-C的應用方向是對效能要求不大的應用[[Category:C語言家族]][[Category:IOS開發]][[Category:面向物件的程式語言]][[Category:基於類的程式語言]][[Category:GNUstep]][[Category:1986年建立的程式語言]]