什么是Objective-C
創(chuàng)新互聯(lián)專注于企業(yè)全網(wǎng)整合營銷推廣、網(wǎng)站重做改版、柘城網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、H5網(wǎng)站設(shè)計、成都商城網(wǎng)站開發(fā)、集團(tuán)公司官網(wǎng)建設(shè)、成都外貿(mào)網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為柘城等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
Objective-C,簡稱OC,是一種通用、高級、面向?qū)ο蟮木幊陶Z言。它擴(kuò)展了標(biāo)準(zhǔn)的ANSI C編程語言,
將Smalltalk式的消息傳遞機(jī)制加入到ANSI C中。當(dāng)前主要支持的編譯器有GCC和Clang(采用LLVM作為后端)。
Objective-C的商標(biāo)權(quán)屬于蘋果公司,蘋果公司也是這個編程語言的主要開發(fā)者。
蘋果在開發(fā)NeXTSTEP操作系統(tǒng)時使用了Objective-C,之后被OS X和iOS繼承下來。
現(xiàn)在Objective-C與Swift是OS X和iOS操作系統(tǒng)、及與其相關(guān)的API、Cocoa和Cocoa Touch的主要編程語言。
Objective-C是C語言的嚴(yán)格超集。這意味著任何C語言程序不經(jīng)修改就可以直接通過Objective-C編譯器,
在Objective-C中使用C語言代碼也是完全合法的。Objective-C被描述為蓋在C語言上的薄薄一層,
因為Objective-C的原意就是在C語言主體上加入面向?qū)ο蟮奶匦?。OC項目中常用的拓展名如下:
擴(kuò)展名 內(nèi)容類型
.h 頭文件。頭文件包含類,類型,函數(shù)和常數(shù)的聲明。
.m 源代碼文件。這是典型的源代碼文件擴(kuò)展名,可以包含 Objective-C 和 C 代碼。
.mm 源代碼文件。帶有這種擴(kuò)展名的源代碼文件,除了可以包含Objective-C和C代碼以外還可以包含C++代碼。僅在你的Objective-C代碼中確實需要使用C++類或者特性的時候才用這種擴(kuò)展名。
Hello, World!
學(xué)習(xí)任何一門語言之前,基本都需要做的就是編寫并運行一個HelloWorld程序,對于OC而言則是如下:
#import
int main (int argc, const char * argv[])
{
@autoreleasepool {
NSLog (@"Hello, World!");
}
return 0;
}
使用clang進(jìn)行編譯:
clang -framework Foundation hello.m -o hello
運行:
$ ./hello
2019-04-05 09:33:22.579 hello[75742:3312942] Hello, World!
So easy!我們學(xué)習(xí)Objective-C時記住要重點關(guān)注概念而不是具體的語言細(xì)節(jié),避免陷入學(xué)而無用的境地。
關(guān)鍵概念
消息傳遞
Objective-C最大的特色是承自Smalltalk的消息傳遞模型(message passing),
此機(jī)制與今日C++式之主流風(fēng)格差異甚大。 Objective-C里,與其說對象互相調(diào)用方法,
不如說對象之間互相傳遞消息更為精確。此二種風(fēng)格的主要差異在于調(diào)用方法/消息傳遞這個動作。
C++里類別與方法的關(guān)系嚴(yán)格清楚,一個方法必定屬于一個類別,而且在編譯時(compile time)
就已經(jīng)緊密綁定,不可能調(diào)用一個不存在類別里的方法。但在Objective-C,類別與消息的關(guān)系比較松散,
調(diào)用方法視為對對象發(fā)送消息,所有方法都被視為對消息的回應(yīng)。所有消息處理直到運行時(runtime)
才會動態(tài)決定,并交由類別自行決定如何處理收到的消息。也就是說,一個類別不保證一定會回應(yīng)收到的消息,
如果類別收到了一個無法處理的消息,程序只會拋出異常,不會出錯或崩潰。
C++里,送一個消息給對象(或者說調(diào)用一個方法)的語法如下:
obj.method(argument);
Objective-C則寫成:
[obj method: argument];
此二種風(fēng)格各有優(yōu)劣。C++強(qiáng)制要求所有的方法都必須有對應(yīng)的動作,且編譯期綁定使得函數(shù)調(diào)用非??焖?。
缺點是僅能借由virtual關(guān)鍵字提供有限的動態(tài)綁定能力。Objective-C天生即具備鴨子類型之動態(tài)綁定能力,
因為運行期才處理消息,允許發(fā)送未知消息給對象??梢运拖⒔o整個對象集合而不需要一一檢查每個對象的類型,
也具備消息轉(zhuǎn)送機(jī)制。同時空對象nil接受消息后默認(rèn)為不做事,所以送消息給nil也不用擔(dān)心程序崩潰。
字符串
作為C語言的超集,Objective-C 支持 C 語言字符串方面的約定。也就是說,單個字符被單引號包括,
字符串被雙引號包括。然而,大多數(shù)Objective-C通常不使用C語言風(fēng)格的字符串。
反之,大多數(shù)框架把字符串傳遞給NSString對象。NSString類提供了字符串的類包裝,
包含了所有你期望的優(yōu)點,包括對保存任意長度字符串的內(nèi)建內(nèi)存管理機(jī)制,支持Unicode,printf風(fēng)格的格式化工具,
等等。因為這種字符串使用的非常頻繁,Objective-C提供了一個助記符@可以方便地從常量值創(chuàng)建NSString對象。
如下面的例子所示:
// 從一個C語言字符串創(chuàng)建Objective-C字符串
NSString* fromCString = [NSString stringWithCString:"A C string"
encoding:NSASCIIStringEncoding];
// 使用助記符@
NSString* name = @"PANN";
NSString* line = [NSString stringWithFormat:@"Hello, %s\n", @"String"];
類(class)
類是面向?qū)ο笳Z言中最重要的一個概念,Objective-C同樣支持類。下圖是一個名為MyClass的類聲明介紹:
class.png
聲明
遵循C語言的規(guī)范,類聲明一般定義在.h頭文件中。類聲明以關(guān)鍵字@interface作為開始,@end作為結(jié)束。
其中類方法前的+號表示類方法,-號表示實例方法。一個對應(yīng)的C++類定義如下:
public MyClass : NSObject {
protected:
int count;
id data;
NSString *name;
public:
id intWithString(NSString *aName);
static MyClass createMyClassWithString(NSString aName);
};
實現(xiàn)
遵循C語言的規(guī)范,類實現(xiàn)一般定義在對應(yīng)的.m文件中。類實現(xiàn)包含了公開方法的實現(xiàn),
以及定義私有(private) 變量及方法。 以關(guān)鍵字@implementation作為區(qū)塊起頭,@end結(jié)尾。
上述類的一個實現(xiàn)如下:
@implementation MyClass {
NSString *secret;
-(id) initWithString: (NSString*)aName {
self.name = aName;
return 0;
}
+(MyClass)createMyClassWithString:(NSString*)aName {
MyClass * my = [[MyClass alloc] init];
my.name = aName;
return my;
}
}
頭文件(類聲明)中定義的屬性默認(rèn)為protected,方法為public。而類實現(xiàn)中定義的屬性為private。
當(dāng)然也可以使用@public、@private等助記符來覆蓋默認(rèn)行為。
實例化
實例化即創(chuàng)建對象。Objective-C創(chuàng)建對象需通過alloc以及init兩個消息。alloc的作用是分配內(nèi)存,
init則是初始化對象。 init與alloc都是定義在NSObject里的方法,父對象收到這兩個信息并做出正確回應(yīng)后,
新對象才創(chuàng)建完畢。如上述類中:
MyClass * my = [[MyClass alloc] init];
在Objective-C 2.0里,若創(chuàng)建對象不需要參數(shù),則可直接使用new:
MyClass * my = [MyClass new];
僅僅是語法上的精簡,效果完全相同。
若要自己定義初始化的過程,可以重寫init方法,來添加額外的工作。(用途類似C++ 的構(gòu)造函數(shù)constructor),
如下:
(id) init {
if ( self=[super init] ) { // 必須調(diào)用父類的init
// do something here ...
}
return self;
}
方法(method)
在上節(jié)介紹類的時候已經(jīng)見過了一些方法的定義和使用,第一次接觸Objective-C的人肯定會覺得很奇怪(比如我就覺得這語法比Golang還奇葩),
但是只要接收了這種設(shè)定,還是可以慢慢習(xí)慣的。
聲明
下圖為Objective-C內(nèi)置數(shù)組類型的insertObject方法聲明:
method.png
方法實際的名字(insertObject:atIndex:)是所有方法標(biāo)識關(guān)鍵的級聯(lián),包含了冒號。冒號表明了參數(shù)的出現(xiàn)。
如果方法沒有參數(shù),你可以省略第一個(也是唯一的)方法標(biāo)識關(guān)鍵字后面的冒號。本例中,這個方法有兩個參數(shù)。
該函數(shù)轉(zhuǎn)換成類似的C++表示如下:
void insertObject:atIndex:(id anObject, NSUInteger index);
調(diào)用
調(diào)用一個方法實際上就是傳遞消息到對應(yīng)的對象。這里消息就是方法標(biāo)識符以及傳遞給方法的參數(shù)信息。
發(fā)送給對象的所有消息都會動態(tài)分發(fā),這樣有利于實現(xiàn)Objective-C類的多態(tài)行為。
也就是說,如果子類定義了跟父類的具有相同標(biāo)識符的方法,那么子類首先收到消息,
然后可以有選擇的把消息轉(zhuǎn)發(fā)(也可以不轉(zhuǎn)發(fā))給他的父類。
消息被中括號( [ 和 ] )包括。括號中接收消息的對象在左邊,消息及其參數(shù)在右邊。
例如,給myArray變量傳遞消息insertObject:atIndex:消息,可以使用如下的語法:
[myArray insertObject:anObj atIndex:0];
消息允許嵌套。也就是說,假如你有一個myAppObject對象,該對象有g(shù)etArray方法獲取數(shù)組,
有g(shù)etObjectToInsert方法獲取元素,那么嵌套的消息可以寫成:
[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];
屬性(attribute)
屬性沒什么好說的,和C++的類屬性類似。不過在Objective-C 2.0引入了新的語法以聲明變量為屬性,
并包含一可選定義以配置訪問方法的生成。屬性總是為公共的,其目的為提供外部類訪問(也可能為只讀)
類的內(nèi)部變量的方法。屬性可以被聲明為“readonly”,即只讀的,也可以提供儲存方法包括“assign”,
“copy”或“retain”(簡單的賦值、復(fù)制或增加1引用計數(shù))。默認(rèn)的屬性是原子的,
即在訪問時會加鎖以避免多線程同時訪問同一對象,也可以將屬性聲明為“nonatomic”(非原子的),
避免產(chǎn)生鎖。
定義屬性的例子如下:
@interface Person : NSObject {
@public
NSString *name;
@private
int age;
}
@property(copy) NSString *name;
@property(readonly) int age;
-(id)initWithAge:(int)age;
@end
synthesize
屬性的訪問方法由@synthesize關(guān)鍵字來實現(xiàn),它由屬性的聲明自動的產(chǎn)生一對訪問方法。
另外,也可以選擇使用@dynamic關(guān)鍵字表明訪問方法為手動提供。
@implementation Person
@synthesize name;
@dynamic age;
-(id)initWithAge:(int)initAge
{
age = initAge; // 注意:直接賦給成員變量,而非屬性
return self;
}
-(int)age
{
return 18; // 注意:并非返回真正的年齡
}
@end
訪問
屬性可以利用傳統(tǒng)的消息表達(dá)式、點表達(dá)式或"valueForKey:"/"setValue:forKey:"方法對來訪問。如下:
Person *aPerson = [[Person alloc] initWithAge: 53];
// 修改屬性
aPerson.name = @"Steve";
[aPerson setName: @"Steve"];
// 讀取屬性
NSString *tmp;
tmp = [aPerson name]; // 消息表達(dá)式
tmp = aPerson.name; // 點表達(dá)式
tmp = aPerson->name; // 直接訪問成員變量
tmp = [aPerson valueForKey:@"name"]; // property訪問
協(xié)議(Protocol)
協(xié)議是一組沒有實現(xiàn)的方法列表,任何的類均可采納協(xié)議并具體實現(xiàn)這組方法。簡而言之就是接口,
可以類比Java的interface,或者C++的純虛函數(shù),表述一種is-a的概念。
協(xié)議以關(guān)鍵字@protocol作為區(qū)塊起始,@end結(jié)束,中間為方法列表。如下:
@protocol Mutex
(void)lock;
@end
若要聲明實現(xiàn)該協(xié)議,可以使用尖括號<>,如下:
@interface SomeClass : SomeSuperClass
@end
一旦SomeClass表明他采納了Mutex協(xié)議,SomeClass就有義務(wù)實現(xiàn)Mutex協(xié)議中的兩個方法:
@implementation SomeClass
(void)lock {
// 實現(xiàn)lock方法
}
(void)unlock {
// 實現(xiàn)unlock方法
}
@end
動態(tài)類型
類似于Smalltalk,Objective-C具備動態(tài)類型:即消息可以發(fā)送給任何對象實體,無論該對象實體的公開接口中有沒有對應(yīng)的方法。
雖然Objective-C具備動態(tài)類型的能力, 但編譯期的靜態(tài)類型檢查依舊可以應(yīng)用到變量上。
以下三種聲明在運行時效力是完全相同的, 但是三種聲明提供了一個比一個更明顯的類型信息,
附加的類型信息讓編譯器在編譯時可以檢查變量類型,并對類型不符的變量提出警告。
下面三個方法,差異僅在于參數(shù)的形態(tài):
setMyValue1:(id) foo;
setMyValue2:(id
Objective-C中的id類型類似于void指針,但是被嚴(yán)格限制只能使用在對象上。
消息轉(zhuǎn)發(fā)
一個對象收到消息之后,他有三種處理消息的可能手段,第一是回應(yīng)該消息并運行方法,若無法回應(yīng),
則可以轉(zhuǎn)發(fā)消息給其他對象,若以上兩者均無,就要處理無法回應(yīng)而拋出的例外。只要進(jìn)行三者之其一,
該消息就算完成任務(wù)而被丟棄。若對"nil"(空對象指針)發(fā)送消息,該消息通常會被忽略,
只不過對于某些編譯器選項可能會拋出異常。
Objective-C運行時在Object中定義了一對方法:
轉(zhuǎn)發(fā)方法:
(retval_t) forward:(SEL) sel :(arglist_t) args; // with GCC
響應(yīng)方法:
(retval_t) performv:(SEL) sel :(arglist_t) args; // with GCC
GCC和NeXT/Apple編譯器的區(qū)別是返回值和參數(shù)類型不同。
希望實現(xiàn)轉(zhuǎn)發(fā)的對象只需用新的方法覆蓋以上方法來定義其轉(zhuǎn)發(fā)行為而無需重寫響應(yīng)方法performv::,
因為后者只是單純的對響應(yīng)對象發(fā)送消息并傳遞參數(shù)。其中,SEL類型是Objective-C中消息的類型。
類別(Category)
Objective-C借用并擴(kuò)展了Smalltalk實現(xiàn)中的"分類"概念,用以幫助達(dá)到分解代碼的目的。
一個分類可以將方法的實現(xiàn)分解進(jìn)一系列分離的文件。程序員可以將一組相關(guān)的方法放進(jìn)一個分類,
使程序更具可讀性。舉例來講,可以在字符串類中增加一個名為"拼寫檢查"的分類,
并將拼寫檢查的相關(guān)代碼放進(jìn)這個分類中。
分類中的方法是在運行時被加入類中的,這一特性允許程序員向現(xiàn)存的類中增加方法,
而無需持有原有的代碼, 或是重新編譯原有的類。
例如若系統(tǒng)提供的字符串類的實現(xiàn)中不包含拼寫檢查的功能,可以增加這樣的功能而無需更改原有的字符串類的代碼。
在運行時,分類中的方法與類原有的方法并無區(qū)別,其代碼可以訪問包括私有類成員變量在內(nèi)的所有成員變量。
若分類聲明了與類中原有方法同名的函數(shù),則分類中的方法會被調(diào)用。因此分類不僅可以增加類的方法,
也可以代替原有的方法。這個特性可以用于修正原有代碼中的錯誤,更可以從根本上改變程序中原有類的行為。
若兩個分類中的方法同名,則被調(diào)用的方法是不可預(yù)測的。
分類的聲明如下:
@interface ClassName (CategoryName)
@end
下面是一個具體的例子,通過MyAdditions分類,動態(tài)的給NSString類中添加getCopyRightString方法:
#import
@interface NSString(MyAdditions)
+(NSString *)getCopyRightString;
@end
@implementation NSString(MyAdditions)
+(NSString *)getCopyRightString {
return @"Copyright evilpan.com 2019";
}
@end
int main(int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *copyrightString = [NSString getCopyRightString];
NSLog(@"Accessing Category: %@", copyrightString);
[pool drain];
return 0;
}