Objective-C 入門知識
羅朝輝(http://www.shnenglu.com/kesalin)
轉(zhuǎn)載請注明出處
編程工作做久了,最初的新鮮感難免會消磨殆盡。幸好總是會有新的技術(shù)閃耀登場,重燃編輯人員的興趣,Mac OS X 就飽含這樣神奇的技術(shù)。
---Mark Dalrymple & Scott Knaster
1,從 HelloWorld 開始,
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// insert code here...
NSLog(@"Hello, World!");
[pool drain];
return 0;
}
#import 就相當(dāng)于 #include,用來通知編譯起應(yīng)該在頭文件中查詢定義,當(dāng) import 功能更強(qiáng)大,它可以保證頭文件只可以被包含一次。而在C/C++中我們是通過
#ifndef __HELLO_WORLD_H__
#define __HELLO_WORLD_H__
// …. do something
#endif // __HELLO_WORLD_H__
來實(shí)現(xiàn)的(在VC中可以使用 #program once 來實(shí)現(xiàn)這一目的)。
2,Objective C中的布爾數(shù)據(jù)類型與C語言中的不同,在C語言中是有專門的布爾數(shù)據(jù)類型 bool,且它具有 true 和 false 值;在 Objective C 中有相似的對應(yīng)類型 BOOL,它具有 YES 和 NO 值。這兩者是有區(qū)別的,Objective C 中的 BOOL 是實(shí)際上是 signed char 的一個 typedef,即:
typedef signed char BOOL;
#define YES 1
#define NO 0
所以 BOOL 實(shí)際上是一個有符號的 char 類型,YES 被定義為 1,而 NO 被定義為 0,但 BOOL 還可以是其他值,但均不是約定的 YES 或 NO 值。也就是非零的 BOOL 類型的數(shù)據(jù)不一定等于 YES,使用時要注意這一點(diǎn)。
3, NSObject 聲明了一個名為 isa 的實(shí)例變量,該變量保存一個指針,指向?qū)ο蟮念悺C總€成員方法調(diào)用都獲得2一個名為 self 的隱藏函數(shù),它是一個指向接收消息的對象的指針。方法使用 self 參數(shù)查找它們要使用的實(shí)例變量。
脆弱的基類問題:self 指向 isa,isa 是占四個字節(jié)的,之后便是類自身的成員變量區(qū)域,所以 selft + 4 + 成員變量與對象基地址之間的偏移量就能得到指向存儲成員變量的地址。因此,如果想向 NSObjcet 中增加新的變量,就無法做到了,因?yàn)樵诰幾g器生成的程序中,這些偏移量是通過硬編碼實(shí)現(xiàn)的。
蘋果通過在 Leopard 中引入新的 64 位 Objective-C(它使用間接尋址方式確定變量的位置)解決了這個問題。
4,super 是由 Objective-C 編譯器提供的,向 super 發(fā)送消息,實(shí)際上是在請求 Objective-C 向該類的超類發(fā)送消息。如果直接超類中沒有定義該消息,將按照通常的方式在繼承鏈中繼續(xù)查找對應(yīng)的消息。
5,在新生成的文件中使用公司名稱或自己的名稱代替默認(rèn)的占位符 __MyCompanyName__,在 Finder 中打開 Utilities 文件夾,打開其中的 Terminal 應(yīng)用程序,在命令行中按照如下格式輸入,回車就可以了。
defaults write com.apple.Xcode.PBXCustomTemplateMacroDefinites '{"ORGANIZATIONNAME"="kesalin@gmail.com";}'
6,XCode 3.2 快捷鍵
shift + command + E 顯示或者隱藏 Editor 窗口。
command + [ 左移代碼
command + ] 右移代碼
tab 代碼自動完成
Esc 打開自動完成列表(E表示枚舉類型,f表示函數(shù),#表示#define宏,m表示方法,C表示類)
control + . 不顯示自動完成列表而自動進(jìn)行推薦值正向循環(huán)
shift + control + . 不顯示自動完成列表而自動進(jìn)行推薦值反向循環(huán)
control + / 移至下一個占位符
command + control + s 創(chuàng)建快照
Edit->Edit all in scope 修改局部變量或參數(shù)
Edit->Refactor 全局修改類名(但無法修改注釋中出現(xiàn)的類名)
command + shift + D 打開文件快速查找對話框
command + option + 向上箭頭 查看文件的配套文件(如果當(dāng)前查看的為 m 文件,則打開 h 文件,反之亦然)
command + D 創(chuàng)建書簽
command + Y 調(diào)試
command + option + P 執(zhí)行到下一個斷點(diǎn)(相當(dāng)于 VS 下的 F5)
command + shift + O 執(zhí)行到下一步(相當(dāng)于 VS 下的 F10)
command + shift + I 跳入函數(shù)執(zhí)行(相當(dāng)于 VS 下的 F11)
command + shift + T 跳出函數(shù)執(zhí)行
control + F Forward right 右移光標(biāo)
control + B Backward left 左移光標(biāo)
control + P Previous lien 上移光標(biāo)
control + N Next line 下移光標(biāo)
control + A Line head 光標(biāo)移動到行首
control + E Line end 光標(biāo)移動到行尾
control + T Transpose 交換光標(biāo)兩邊的符合
control + D Delete 刪除光標(biāo)右邊的字符
control + K Kill 刪除光標(biāo)所在行中光標(biāo)后的代碼,
control + L 將插入點(diǎn)置于窗口正中。
查看文檔 按住 option 鍵并雙擊選擇的內(nèi)容,可以查找選中字符的文檔
格式化代碼 右鍵菜單,選擇 re-indent selection
折疊代碼, 點(diǎn)擊代碼左邊的焦點(diǎn)列,就可以進(jìn)行代碼的折疊。
添加可讀的標(biāo)記 在代碼中加入 #pragma mark STUFF,#pragma mark - 則是在菜單中插入分割線
或在代碼中加入如下 TODO:, FIDME,!!! 或 ???
7,NSString 的比較方法:
isEqualToString 比較兩個字符串的內(nèi)容,如果相等返回 YES,如果不相等返回 NO。
要比較兩個字符串可以使用 compare 函數(shù),其接口聲明如下:
-(NSComparisonResult) compare:(NSString *) string
options:(unsigned) mask;
mask 可以是如下位相或的值:
NSCaseInsensitiveSearch : 不區(qū)分大小寫
NSLiteralSearch : 進(jìn)行完全的比較,區(qū)分大小寫
NSNumericSearch : 比較字符串的字符個數(shù),而不是字符值。
typedef enum _NSComparisonResult {
NSOrderedAscending = -1;
NSOrderedSame,
NSOrderedDescending
} NSComparisonResult;
8, NSArray 只能存儲 Objective-C對象,而不能存儲 C 語言中的基本數(shù)據(jù)類型,如 int,float,enum,struct,或者 NSArray中的隨機(jī)指針,也不能將存儲 nil。
對于可變數(shù)組進(jìn)行枚舉操作時,有一點(diǎn)需要注意:你不能通過添加或著刪除對象這類方式來改變數(shù)組的容器。
不要試圖去創(chuàng)建 NSString,NSArray,NSDictionary的子類,而應(yīng)該用聚合方式來使用它們。因?yàn)樗鼈儗?shí)際上是以類族的方式實(shí)現(xiàn)的,即它們是一群隱藏在通用接口之下的與實(shí)現(xiàn)相關(guān)的類。比如,創(chuàng)建 NSString 對象時,實(shí)際上獲得的可能時 NSLiteralString,NSCFString,NSSimpleCString,NSBallOfString或者其他沒有寫入文檔的與實(shí)現(xiàn)相關(guān)的對象。
9,NSValue 可以包裝任意值,可以用 NSValue 將結(jié)構(gòu)體放入 NSArray 或 NSDictionary 中。可用如下的類方法創(chuàng)建新的 NSValue。
+(NSValue *) valueWidthBytes:(const void *) value
objCType:(const char *) type;
傳遞的參數(shù) value 時想要包裝的數(shù)值的地址,type 時一個用來描述這個數(shù)據(jù)類型的字符串,通常用來說明 struct 中實(shí)體的類型和大小。我們不必自己寫代碼來生存這個字符串,通過 @encode 編譯器指令(接收數(shù)據(jù)類型的名稱作為輸入?yún)?shù))來生成合適的字符串。例如:
NSRect rect = NSMakeRect(10, 10, 200, 200);
NSValue *value;
value = [NSValue valueWidthBytes: &rect,
objCType: @encode(NSRect)];
[array addObject: value];
10,內(nèi)存管理
Objective-C 使用引用計數(shù)技術(shù)來進(jìn)行內(nèi)存管理。當(dāng)使用 alloc,new,copy 創(chuàng)建一個對象時,對象的引用計數(shù)被設(shè)置為 1。每給對象發(fā)送一條 retain 消息都將增加對象的引用計數(shù);每給對象發(fā)送一條 release 消息都將簡述對象的引用計數(shù),當(dāng)引用計數(shù)歸 0 時,對象被銷毀,這時 Objective-C 自動向?qū)ο蟀l(fā)送一條 dealloc 消息。我們可以在對象中重寫 dealloc 方法,在其中釋放為對象分配的全部資源。注意:不要直接調(diào)用 dealloc 方法。給對象發(fā)送 retainCount 可以獲得當(dāng)前的引用計數(shù)值。
Cocoa 中有一個自動釋放池(autorelease pool),可以給 NSObject 的任何子類發(fā)送一條 autorelease 消息,該消息實(shí)際上時將該對象添加到 NSAutoreleasePool 中,表示將對象的所有權(quán)轉(zhuǎn)交給它所在的 autorelease pool 來管理,即預(yù)先設(shè)定在將來某個時間(一般就是 autorelease pool 釋放的時候)會將對象自動釋放。當(dāng) autorelease pool 被銷毀時,會向該池中的所有對象發(fā)送 release 消息。
Objective-C 內(nèi)存管理定律:
如果使用了 new,alloc 或 copy 方法獲得了一個對象,那么必須 release 或 autorelease 該對象。
如果保留了一個對象,那么必須保持 retain 和 release 相匹配。
Objective-C 2.0 支持垃圾回收機(jī)制(iOS 不支持),XCode 默認(rèn)時沒有開啟該功能的,可以在項(xiàng)目信息的 Build 選項(xiàng)卡中,查找 Required 項(xiàng),將其設(shè)置為 [-fobjc-gc-only] 即可。該選項(xiàng)讓代碼既支持垃圾回收又支持對象的 retain 和 release 機(jī)制。
11,對象的初始化:
有兩種等價的創(chuàng)建新對象的方法:[類名 new] 和 [[類名 alloc] init],按照 Cocoa 的慣例,要盡量使用后者。[[類名 alloc] init] 實(shí)際上是將兩個步驟:[類名 alloc] 和 [對象 init] 合并到一起了。Cocoa 要求給對象 alloc 內(nèi)存之后必須調(diào)用 init 進(jìn)行初始化。
12,類別 category
類別讓我們能夠不創(chuàng)建類的子類就能對類擴(kuò)展,但類別有兩方面的局限性:第一是無法向類中添加新的成員變量;第二是名稱沖突,即類別中的方法與現(xiàn)有的方法重名。當(dāng)發(fā)生名稱沖突時,類別具有更高的優(yōu)先級,可以給類別方法添加前綴以確保不發(fā)生名稱沖突。
類別主要有三大用途:
第一:將類的實(shí)現(xiàn)分散到多個不同文件或多個不同框架中;
第二:創(chuàng)建對私有方法的前向引用;
第三:向?qū)ο筇砑臃钦絽f(xié)議;
13,Cocoa 沒有任何真正的私有方法,只要知道對象支持的某個方法的名稱,即使該對象所在的類的接口中沒有該方法的聲明,你也可以調(diào)用該方法,但編譯器會提示一個警告。
14,委托
不需要在 @interface 中聲明委托的方法,要成為一個委托對象,只需要實(shí)現(xiàn)打算調(diào)用的方法。
非正式協(xié)議時 NSObject 的一個類別,它列出對象能夠響應(yīng)的方法,表示有一些你可能希望實(shí)現(xiàn)的方法,它們能讓你在需要的時候更好地完成工作。非正式協(xié)議常用于實(shí)現(xiàn)委托,委托時一種允許你輕松定制對象行為的技術(shù)。
15,選擇子 selector
可以使用 @selector() 預(yù)編譯指令指定選擇子,其中方法名放置在圓括號中。如: setObject:atIndex: 的選擇子為: @selector(setObject:atIndex:)。選擇子可以作為參數(shù)傳遞,也可以作為實(shí)例變量。
我們可以通過 NSObject 的 respondsToSelector: 方法來詢問一個對象是否能夠響應(yīng)特定的消息。例如:
if ([objectArray respondsToSelector: @selector(setObject:atIndex:)) {
objectArray->setObject(object, index);
}
16,正式的協(xié)議 protocol
正式的協(xié)議采用 protocol 關(guān)鍵字。聲明協(xié)議的語法類似于聲明類,但是要注意協(xié)議不能有成員變量,它相當(dāng)于 Java 中的 interface。
例如:Cocoa 聲明了一個協(xié)議---NSCopying:
@protocol NSCopying
- (id) copyWidthZone:(NSZone *) zone;
@end
要采納某個協(xié)議,可以在類的聲明中列出該協(xié)議的名稱,并用尖括號將協(xié)議名稱括起來,然后必須在 @implement 中實(shí)現(xiàn)協(xié)議中聲明為 required(默認(rèn)就是 required)的所有方法,而聲明為 optional 的方法可以。
下面讓類 MyObject 采納 NSCopying 協(xié)議:
@interface MyObject : NSObject <NSCopying>
{
}
@end
@implementation MyObject
- (id) copyWithZone: (NSZone *) zone
{
MyObject *objectCopy;
objectCopy = [[[self class] allocWidthZone: zone] init];
return (objectCopy);
}
@end
說明:[self class] 返回的是對象所屬的類。allocWidthZone 是一個類方法,因此在這里使用是合適的。
我們可以在實(shí)例變量和函數(shù)參數(shù)的數(shù)據(jù)類型中指定協(xié)議的名稱,這樣就可以約束該變量或參數(shù)必須滿足的條件(即遵循聲明的協(xié)議),編譯器會對此約束條件進(jìn)行檢查。如:
-(void) setObjectValue:(id<NSCopying>) obj;
在這里 setObjectValue 要求傳入的參數(shù)必須遵循 NSCopying 協(xié)議,即實(shí)現(xiàn)了 NSCopying 協(xié)議中聲明的所有方法。
注意:Objective-C 只在2.0中才添加 required 和 optional 關(guān)鍵字,在這之前,所有的協(xié)議中所有的方法都是 required 的。
17,控件編程:
IBOutlet 和 IBAction 它們是 ApplicationKit 提供的宏。
IBOutlet 的定義沒有任何作用,只是用來醒目的(我是一個Outlet啦),IBAction 定義為 void。
可以說IBOutlet 和 IBAction 都沒有什么實(shí)際作用,之所以用它們是為 Interface Builder 以及閱讀代碼的人
提供特殊標(biāo)記。
鏈接操作:在 Interface Builder 中進(jìn)行拖動鏈接,拖動的方向是不那么容易分得清的,但其實(shí)是有規(guī)律可循的。這個規(guī)律就是:鏈接草率是將需要了解某些內(nèi)容的對象拖動到該對象需要了解的對象。比如:
AppController 需要知道將哪個 NSTextField 用于用戶輸入,因此拖到方向是從 AppController 到文本域。
而 Button 需要告訴某個對象“嘿,有人按下我了!”,所以是從Buuton 的響應(yīng)事件拖動到 AppController。
控件執(zhí)行流程:
加載主 nib 文件 --> awakeFromNib --> 處理事務(wù)
18,謂詞 NSPredicate
創(chuàng)建謂詞:
NSPredicate *predicat;
predicate = [NSPredicate predicateWidthFormat: @"name == 'kesalin'"];
使用謂詞:
Bool match = [predicate evaluateWidthObject: myObject];
使用謂詞過濾數(shù)組:
-filteredArrayUsingPredicate: 是 NSArray 數(shù)組的類方法,它將對數(shù)組進(jìn)行過濾,將滿足謂詞判斷的值放置到返回數(shù)組中。例:
NSArray *resultObjects;
resultObjects = [objects filteredArrayUsingPredicate: predicate];
與 NSArray 相對應(yīng), NSMutableArray 也有相應(yīng)的方法:
-filterUsingPredicate 可以使用它來剔除不屬于該數(shù)組的所有項(xiàng)目。
使用謂詞是很方便,但是它對效率有損失,就如我們使用鍵值對訪問變量一樣。
格式說明符:
我們可以將格式說明符和變量名放入謂詞格式字符串中。我們可以將 printf 中常用的 %d,%f等放入謂詞中。如:
predicate = [NSPredicate predicateWithFormat: @"age > %d", 18];
支持的其他格式字符串:
%@ 字符串
%K 指定鍵路徑
例如:
predicate = [NSPredicate predicateWithFormat: @"%K > %d", @"age", 18];
將變量放入謂詞字符串中,使用方法類似于環(huán)境變量:
predicate = [NSPredicate predicateWithFormat: @"name == $NAME"];
19,Objective-C 中不存在類變量,可以使用文件范圍內(nèi)的全局變量來模擬類變量并為它們提供訪問器。例如:
@interface MyObject : NSObject
{
}
+ (int) age;
+ (void) setAge: (int) age;
@end
@implement MyObject
+ (int) age
{
return g_age;
}
+ (void) setAge: (int) age
{
g_age = age;
}
@end
20, C++ 具有很多 Objective-C 所沒有的特性:
多重繼承,命名空間,運(yùn)算符重載,模板,類變量,抽象類,STL等。
但我們可以通過類別和協(xié)議結(jié)合消息重定向技術(shù)(forwardInvocation)來模擬多重繼承,利用協(xié)議來實(shí)現(xiàn)抽象基類。
注意:使用重定向技術(shù)的運(yùn)行速度要慢于 C++。
Objective-C 運(yùn)行時進(jìn)入類結(jié)構(gòu)中查找相應(yīng)的代碼以供使用,其速度比使用虛表技術(shù)的 C++ 要慢,但是帶來了靈活性和便捷性。
Objective-C 很少使用子類化行為,因?yàn)橥ㄟ^類別和動態(tài)運(yùn)行時機(jī)制,可以向任何對象發(fā)送任何消息,可以將某些功能放到含有較少功能的類中,可以將功能放到最有意義的類中。一般來說只有當(dāng)創(chuàng)建某個全新的對象,或者需要從跟不上改變某個對象的行為,或者由于類不能實(shí)現(xiàn)某個功能而需要使用子類時,才需要載 Cocoa 中設(shè)置子類。而對于其他大多數(shù)情況,使用委托和數(shù)據(jù)遠(yuǎn)源的方式足以應(yīng)付了。由于 Objective-C 可以向任何對象發(fā)送任何消息,對象不必含有特定的子類或遵循特定的接口,這樣,單個類就可以成為任意個不同對象的委托或者數(shù)據(jù)源。