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