首先Objective-C是C的一個超集。
其次Objective-C是一個面向對象的語言。
#import = #include
在頭文件定義的都是公共的(方法 or 變量)
在m文件裏面定義的都是私有的(方法 or 變量)
@property 這個後面的變量在聲明的時候就一起聲明瞭兩個方法(getter setter)
第一節 總括
這一節是對Objective-C(以後簡稱ObjC)的簡要介紹,目的是使讀者對ObjC有一個概括的認識。
-
面象的讀者:在閱讀本文之前,應具備使用與C類似的編程語言(如C,C++,JAVA)的一些經驗,同時熟悉面向對象編程。
-
ObjC簡介:ObjC是以SmallTalk爲基礎,建立在C語言之上,是C語言的超集。20世紀80年代早期由 Brad J.Cox設計,2007年蘋果公司發佈了ObjC 2.0,並在iPhone上使用ObjC進行開發。
- ObjC學習內容:習的內容主要包括語法和Cocoa框架兩部分。本文主要對語法進行介紹。
- IDE:編寫ObjC程序最主要的編譯環境是Xcode,它是蘋果官方提供的IDE,官網中的SDK包括Xcode,可以通過下載SDK來獲得它。但是Xcode只支持MacOS X,所以如果要在其它環境下編寫ObjC程序,要使用其它IDE。Linux/FreeBSD用GNUStep,Windows NT5.x(2000,XP)要先安裝cywin或mingw,然後安裝GNUStep。同時僅僅通過文本編輯器,GCC的make工具也可以用於開發。
注:如果要使用到Cocoa的話,只能在Apple公司的Xcode上。 - 框架: ObjC編程中主要用到的框架是Cocoa,它是MacOS X中五大API之一,它由兩個不同的框架組成FoundationKit 和ApplicationKit。 Foundation框架擁有100多個類,其中有很多有用的、面向數據的低級類和數據類型,如NSString,NSArray, NSEnumerator和NSNumber。ApplicationKit包含了所有的用戶接口對象和高級類。這些框架本文不做重點介紹,如果要深入瞭解可以去看Xcode自帶的文檔。
- 特別之處:初次接觸ObjC時,會發現許多和其它語言不同的地方,會看到很多的+,-,[ ,] ,@, NS等符號,這些符號在以後的編程中將經常看到,這部分內容在第二節中介紹。先熟悉一下ObjC的代碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#import "ClassA.h" #import <stdio.h> int main( int argc, const char *argv[] ) { ClassA *c1 = [[ClassA alloc] init]; ClassA *c2 = [[ClassA alloc] init]; // print count printf( "ClassA count: %i\n", [ClassA initCount] ); ClassA *c3 = [[ClassA alloc] init]; // print count again printf( "ClassA count: %i\n", [ClassA initCount] ); [c1 release]; [c2 release]; [c3 release]; return 0; } |
除了這些語言要素上的不同,ObjC也提供了一些很好的特性,如類別,扮演(Posing)等,這些在運行時的特性使得編程更加靈活。
-
優缺點: 每一個語言都有其優缺點,ObjC也不例外,這就要求在選擇語言時權衡利弊。對於ObjC,只要善於利用它的優點,你會發現它是一個簡單,靈活,高效的語言。以下列舉了它的一些特點:
優點: 類別、扮演(Posing)、動態類型、指針計算、彈性信息傳遞、不是一個過度複雜的c衍生語言、可通過Objective-c++與c++結合
缺點: 沒有命名空間、沒有操作符重載、不像c++那樣複雜
第二節對C的擴展
1.擴展名
ObjC是ANSI版本C的一個超集,它支持相同的C語言基本語法。與C一樣,文件分爲頭文件和源文件,擴展名分別爲.h和.m。如果要加入c++的語法,需要用到.mm,這裏不做介紹。
.h 頭文件。頭文件包涵類的定義、類型、方法以及常量的聲明
.m 源文件。這個典型的擴展名用來定義源文件,可以同時包含C和Objective-C的代碼。
2.#import
在ObjC裏,包含頭文件有比#include更好的方法#import。它的使用和#include相同,並且可以保證你的程序只包含相同的頭文件一次。相當於#include+ #pragma once的組合。
例如要包含Foundation框架中的Foundation.h文件,可以像下面這樣。
1 |
#import<Foundation/Foundation.h>
|
注:每個框架有一個主的頭文件,只要包含了這個文件,框架中的所有特性都可以被使用。
3.@符號
@符號是ObjC在C基礎上新加的特性之一。常見到的形式有@”字符串”,%@ , @interface,@implement等。@”字符串”表示引用的字符串應該作爲Cocoa的NSString元素來處理。@interface等則是對於C的擴展,是ObjC面向對象特性的體現。
注:這裏提一個小技巧,只要看到@符號,就可以認爲它是對於C的一個擴展。
4.NSLog()
在ObjC中用的打印函數是NSLog(),因爲ObjC是加了一點”特殊語料”的C語言,所以也可以用printf()但是NSLog()提供了一些特性,如時間戳,日期戳和自動加換行符等,用起來更方便,所以推薦使用NSLog()。下面是兩種輸出的對比。
使用NSLog()輸出任意對象的值時,都會使用%@格式說明。在使用這個說明符時,對象通過一個名爲description的方法提供自己的NSLog()格式。
下面分別是使用NSLog()和使用printf()的相應輸出:
2010-10-15 14:54:21。42610_15[1973:207] Hello World!
Hello World!
注:NS前綴告訴你函數來自Cocoa而不是其他工具包。
5.BOOL
BOOL是ObjC中的布爾類型,它和C中的bool有如下區別
BOOL YES(1),NO(0)
bool true(!0),false(0)
6.id
這是ObjC新加的一個數據類型,它是一般的對象類型,能夠存儲任何類型的方法。
7.nil
在ObjC中,相對於C中的NULL,用的是nil。這兩者是等價的。下面是nil的定義。
#define nil NULL
第三節創建對象
1.接口和實現
在ObjC中定義一個類需要有兩個部分:接口和實現。接口文件包含了類的聲明,定義了實例變量和方法。實現文件包含了具體的函數的實現代碼。下圖顯示了一個叫MyClass的類,它繼承自NSObject基類。類的定義總是從@interface開始到@end結束。在類名後面的是父類的名稱。實例變量被定義在兩個花括號之間。在實例變量下面的是方法的定義。一個分號用來結束一個變量或者方法。
下面的代碼顯示了MyClass這個類的實現代碼。就像類的定義規則一樣,類實現文件也被兩個標識框起來,一個是@implementation,還有一個是@end。這兩個指令標識符告訴編譯器程序從哪裏開始編譯到哪裏結束。類中的方法名稱的定義和它接口文件中的定義是一樣的,除了實現文件中有具體的代碼以外。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
@implementation MyClass -(id)initWithString:(NSString *) aName { if (self = [super init]) { count count = 0; data = nil; name = [aName copy]; return self; } } +(MyClass *)createMyClassWithString: (NSString *) aName { return [[[self alloc] initWithString:aName] autorelease]; } @end |
當你要把一個對象保存進變量,要使用指針類型。ObjC同時支持強和弱變量對象。強類型對象在變量類型定義的時候包含了類名。弱對象使用id類型作爲實例變量。下面的例子同時顯示了定義MyClass中的強弱兩種類型的變量
1 2 |
MyClass* myObject1; // Strong typing id myObject2; // Weak typing |
2.方法
一個方法定義包含了方法類型,返回類型,一個或者多個關鍵詞,參數類型和參數名。在ObjC中一個類中的方法有兩種類型:實例方法,類方法。實例方法前用(-)號表明,類方法用(+)表明,通過下圖可以看到,前面有一個(-)號,說明這是一個實例方法。
在ObjC中,調用一個方法相當於傳遞一個消息,這裏的消息指的是方法名和參數。所有的消息的分派都是動態的,這個體現了ObjC的多態性。消息調用的方式是使用方括號。如下面的例子中,向myArray對象發送insertObject:atIndex:這個消息。
1 |
[myArray insertObject:anObj atIndex:0]; |
這種消息傳遞允許嵌套
1 |
[[myAppObject getArray] insertObject:[myAppObject getObjectToInsert] atIndex:0];
|
前面的例子都是把消息傳遞給實例變量,你也可以把消息傳遞給類本身。這時要用類方法來替代實例方法 。你可以 把他想象成靜態C++類(當然不完全相同)。
類方法的定義只有一個不一樣那就是用加號(+)代替減號(-)。下面就是使用一個類方法。
1 2 3 |
NSMutableArray* myArray = nil; // nil is essentially the same as NULL // Create a new array and assign it to the myArray variable. myArray = [NSMutableArray arrayWithCapacity:0]; |
3.屬性
屬性提供了比方法更方便的訪問方式。通過property標識符來替代getter和setter方法。使用方法就是在類接口文件中用@property標識符,後面跟着變量的屬性,包括 copy, tetain, assign ,readonly , readwrite,nonatomic,然後是變量名。同時在實現文件中用@synthesize標識符來取代getter和setter方法。
1 2 3 4 |
@property BOOL flag; @property (copy) NSString* nameObject; // Copy the object during assignment. @property (readonly) UIView* rootView; // Create only a getter method |
接口文件中使用@property
1 |
@synthesize flag,nameObject,rootView;
|
實現文件中使用@synthesize
屬性的另一個好處就是,可以使用點(.)語法來訪問,如下所示:
1 2 |
myObject.flag = YES; CGRect viewFrame = myObject.rootView.frame; |
第四節繼承
繼承的語法如下,冒號後的標識符是需要繼承的類。
1 |
@interface Circle : NSObject
|
1.不支持多繼承
要注意的是ObjC只支持單繼承,如果要實現多繼承的話,可以通過類別和協議的方式來實現,這兩種方法將在後面進行介紹。
2.Super關鍵字
ObjC提供某種方式來重寫方法,並且仍然調用超類的實現方式。當需要超類實現自身的功能,同時在前面或後面執行某些額外的工作時,這種機制非常有用。爲了調用繼承方法的實現,需要使用super作爲方法調用的目標。下面是代碼示例:
1 2 3 4 5 6 7 8 9 |
@implementation Circle -(void)setFillColor: (ShapeColor) c { if(c== kRedColor){ c = kGreenColor; } [super setFillColor: c]; } @end |
Super來自哪裏呢?它既不是參數也不是實例變量,而是由ObjC編譯器提供的某種神奇功能。向super發送消息時,實際上是在請求ObjC向該類的超類發送消息。如果超類中沒在定義該消息,ObjC將按照通常的方式在繼承鏈中繼續查找對應的消息。
第五節 對象初始化
1.分配與初始化
對象的初始化有兩種方法:一種是[類名new], 第二種是[[類名 alloc]init]。這兩種方法是等價的,不過,通常的Cocoa慣例是使用alloc和init,而不使用new.一般情況下,Cocoa程序員只是在他們不具備足夠的水平來熟練使用alloc和init方法時,纔將new作爲輔助方法使用。
[[類名alloc]init]有兩個動作。alloc是分配動作,是從操作系統獲得一塊內存並將其指定爲存放對象的實例變量的位置。同時,alloc方法還將這塊內存區域全部初始化爲0。與分配動作對應的是初始化。有如下兩種初始化寫法。
1 2 3 4 5 |
Car *car = [[Class alloc] init];
//寫法1
Car *car = [Car alloc];
[car init];
//寫法2
|
應該使用第一種寫法,因爲init返回的對象可能不是以前的那個。
2.編寫初始化方法
下面是一段初始化的代碼
1 2 3 4 5 6 7 |
-(id)init { if(self = [super init]){ engine = [Engine new]; … } } |
使用self= [super init]的作用是使超類完成它們自己的初始化工作。同時因爲init可能返回的是不同的對象,實例變量所在的內存位置到隱藏的self參數之間的跳離又是固定的,所以要這樣使用。
注:這部分可以參考書[1]144頁。
第六節協議
這裏的協議是正式協議,相對的還有非正式協議,這在類別一節中有介紹。正式協議是一個命名的方法列表。它要求顯式地採用協議。採用協議意味着要實現協議的所有方法。否則,編譯器會通過生成警告來提醒你。
1.聲明協議
1 2 3 |
@protocol NSCopying -(id) copyWithZone:(NSZone *)zone; @end |
2.採用協議
1 2 3 4 5 |
@interface Car : NSObject <NSCopying , NSCoding> { // instance variables } @end |
協議可以採用多個,並且可以按任意順序列出這些協議,沒有什麼影響。
3.ObjC 2.0的新特性
ObjC2.0增加了兩個新的協議修飾符:@optional和@required,因此你可以像下面這樣編寫代碼:
1 2 3 4 5 6 7 8 |
@protocol BaseballPlayer -(void)drawHugeSalary; @optional -(void)slideHome; -(void)catchBall; @required -(void)swingBat; @end |
因此,一個採用BaseballPlayer協議的類有兩個要求實現的方法:-drawHugeSalary和-swingBat,還有3個不可選擇實現的方法:slideHome,catchBall和throwBall。
第七節委託
Cocoa中的類經常使用一種名爲委託(delegate)的技術,委託是一種對象,另一個類的對象會要求委託對象執行它的某些操作。常用的是,編寫委託對象並將其提供給其他一些對象,通常是提供給Cocoa生成的對象。通過實現特定的方法,你可以控制Cocoa中的對象的行爲。
通過下面的例子,可以更清楚地理解委託的實現原理。其中A對象需要把一些方法委託給其它對象來實現,例子中就是對象B,B實現了含A對象特定方法的協議ADelegate,從而可以在B中實現A委託的方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
@protocol ADelegate <NSObject> - (void)aDelegateMethod; …… @end @interface A : NSObject { …… id <ADelegate> delegate; } @property (readwrite, assign) id<ADelegate> delegate; …… @end @implementation A @synthesize delegate; - (void)aMethod { [delegate aDelegateMethod]; ...... } @end //A類 @interface B : NSObject <ADelegate> @end @implementation B - (id)init { [[A sharedA] setDelegate:self]; } - (void)aDelegateMethod{ //B中實現A委託的方法 } @end //B類 |
注:實現委託還可以使用類別,在第八節中將做介紹
第八節 類別
類別允許你在現有的類中加入新功能,這些類可以是框架中的類,並且不需要擴充它。
1.聲明類別
1 2 3 |
@interface NSString (NumberConvenience)
-(NSNumber *) lengthAsNumber;
@end
|
該聲明表示,類別的名稱是NumberConvenience,而且該類別將向NSString類中添加方法。
2.實現類別
1 2 3 4 5 6 7 |
@implementation NSString (NumberConvenience) -(NSNumber *) lengthAsNumber { unsigned int length = [self length]; return ([NSNumber numberWithUnsignedInt: length]); } @end |
3.侷限性
類別有兩方面的侷限性。第一,無法向類中添加新的實例變量。類別沒有位置容納實例變量。第二,名稱衝突,即類別中的方法與現有的方法重名。當發生名稱衝突時,類別具有更高的優先級。這點可以通過增加一個前綴的方法解決。
4.非正式協議和委託類別
實現委託除了第七節中應用協議的方式,還可以使用類別。具體做法就是把委託對象要實現的方法聲明爲一個NSObject的類別。如下面的代碼所示:
1 2 3 4 |
@interface NSObject(NSSomeDelegateMethods)
-(void)someMethod;
…
@end
|
通過將這些方法聲明爲NSObject的類別,使得只要對象實現了委託方法,任何類的對象都可以成爲委託對象。創建一個NSObject的類別稱爲“創建一個非正式協議”。非正式協議只是一種表達方式,它表示“這裏有一些你可能想實現的方法”,第六節介紹的協議可以叫做正式協議。
非正式協議的作用類似於使用許多@optional的正式協議,並且前者正逐漸被後者所代替。
5.選擇器
選擇器只是一個方法名稱,它以ObjC運行時使用的特殊方式編碼,以快速執行查詢。你可以使用@selector()預編譯指令指定選擇器,其中方法名位於圓括號中。如一個類中setEngine:方法的選擇器是:
1 |
@selector(setEngine:)
|
因爲選擇器可以被傳遞,可以作爲方法的參數使用,甚至可以作爲實例變量存儲。這樣可以生成一些非常強大和靈活的構造。
第九節Posing
Posing有點像類別,但不太一樣。它允許你擴充一個類,並且全面性地扮演(pose)這個超類。例如:你有一個擴充NSArry的NSArrayChild對象。如果你讓NSArrayChild扮演NSArry,則在你的代碼中所有的NSArray都會自動被替代爲NSArrayChild.
1 2 3 4 5 6 7 8 9 10 11 |
@interface FractionB: Fraction -(void) print; @end @implementation FractionB -(void) print { printf("(%i/%i)", numerator, denominator ); } @end //Fraction.m |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
int main( int argc, const char *argv[] ) { Fraction *frac = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; //print it printf("The fraction is: " ); [frac print]; printf("\n" ); //make FractionB pose as Fraction [FractionB poseAsClass: [Fraction class]]; Fraction *frac2 = [[Fraction alloc] initWithNumerator: 3 denominator: 10]; // print it printf("The fraction is: " ); [frac2 print]; printf( "\n" ); //free memory [frac release]; [frac2 release]; return 0; } // Main.m |
輸出
The fraction is: 3/10
The fraction is: (3/10)
這個程序的輸出中,第一個fraction會輸出3/10,而第二個會輸出(3/10),這是FractionB中實現的方式。poseAsClass這個方法是NSObject的一部分,它允許子類扮演超類。
第十節動態識別 (Dynamictypes)
下面是應用動態識別時所用到的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
-(BOOL)isKindOfClass: classObj //是否是其子孫或一員 -(BOOL)isMemberOfClass: classObj // 是否是其一員 -(BOOL)respondsToSelector: selector // 是否有這種方法 +(BOOL)instancesRespondToSelector: selector // 類的對象是否有這種方法 -(id)performSelector: selector // 執行對象的方法 |
通過下面的代碼可以更清楚地理解動態類型的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
#import "Square.h" #import "Rectangle.h" #import <stdio.h> int main( int argc, const char *argv[] ) { Rectangle *rec = [[Rectangle alloc] initWithWidth: 10 height: 20]; Square *sq = [[Square alloc] initWithSize: 15]; // isMemberOfClass // true if( [sq isMemberOfClass: [Square class]] == YES ) { printf( "square is a member of square class\n" ); } // false if ( [sq isMemberOfClass: [Rectangle class]] == YES ) { printf( "square is a member of rectangle class\n" ); } // false if( [sq isMemberOfClass: [NSObject class]] == YES ) { printf("square is a member of object class\n" ); } // isKindOfClass // true if ( [sq isKindOfClass: [Square class]] == YES ) { printf("square is a kind of square class\n" ); } // true if( [sq isKindOfClass: [Rectangle class]] == YES ) { printf("square is a kind of rectangle class\n" ); } // true if( [sq isKindOfClass: [NSObject class]] == YES ) { printf("square is a kind of object class\n" ); } // respondsToSelector // true if( [sq respondsToSelector: @selector( setSize: )] == YES ) { printf("square responds to setSize: method\n" ); } // false if( [sq respondsToSelector: @selector( nonExistant )] == YES ) { printf("square responds to nonExistant method\n" ); } // true if( [Square respondsToSelector: @selector( alloc )] == YES ) { printf("square class responds to alloc method\n" ); } // instancesRespondToSelector // false if( [Rectangle instancesRespondToSelector: @selector( setSize: )] == YES ) { printf("rectangle instance responds to setSize: method\n" ); } // true if( [Square instancesRespondToSelector: @selector( setSize: )] == YES ) { printf("square instance responds to setSize: method\n" ); } // free memory [rec release]; [sq release]; return 0; } |
輸出:
square is a member of square class
square is a kind of square class
square is a kind of rectangle class
square is a kind of object class
square responds to setSize: method
square class responds to alloc method
square instance responds to setSize: method