1、Objective-C是一門語言,而 Cocoa 是這門語言用于 MacOS X 開發(fā)的一個類庫。它們的關(guān)系類似于 C++ 和 Qt,Java和 Spring 一樣。
2、每一個對象都是每一個對象都是 id類型的。 id 是一個指針。
3、nil等價于指向?qū)ο蟮?nbsp;NULL 指針。
4、Nil等價于指針 nil 的類。
5、SEL用于存儲選擇器 selector的值。所謂選擇器,就是不屬于任何類實例對象的函數(shù)標識符。這些值可以由 @selector獲取。選擇器可以當做函數(shù)指針,但實際上它并不是一個真正的指向函數(shù)的指針。
6、我們前面看到的類 NSObject,NSString都有一個前綴 NS。這是 Cocoa框架的前綴(Cocoa 開發(fā)公司是 NeXTStep)。
7、[object doSomething];
這并不是方法調(diào)用,而是發(fā)送一條消息?瓷先ゲ]有什么區(qū)別,實際上,這是Objective-C的強大之處。例如,這種語法允許你在運行時態(tài)添加方法。
8、嚴格說來,每一個類都應該是 NSObject的子類。
9、id類型是動態(tài)類型檢查的,相比來說,NSObject *則是靜態(tài)類型檢查。
10、Objective-C里面沒有泛型,那么,我們就可以使用 id很方便的實現(xiàn)類似泛型的機制了。
11、在 Objective-C里面,屬性 attributes被稱為實例數(shù)據(jù) instance data,成員函數(shù)member functions被稱為方法 methods。
12、實例方法以減號 -開頭,而 static 方法以 + 開頭。
13、在 Objective-C中,只有成員數(shù)據(jù)可以是 private,protected和 public 的,默認是 protected 。方法只能是 public 的。
14、Objective-C中的繼承只能是 public 的,不可以是 private 和 protected 繼承。
15、Objective-C中不允許聲明 static 屬性。
16、Objective-C中可以向任何對象發(fā)送任何消息。如果目標對象不能處理這個消息,它就會將消息忽略(這會引發(fā)一個異常,但不會終止程序)。
17、self指當前對象(類似 C++ 的 this),super指父對象。
18、重載的情況。C++和 Objective-C 使用截然不同的兩種方式去區(qū)分:前者使用參數(shù)類型,后者使用參數(shù)標簽。
19、@selector的值是在編譯器決定的,因此它并不會減慢程序的運行效率。
20、嚴格說來,選擇器并不是一個函數(shù)指針。它的底層實現(xiàn)是一個 C字符串,在運行時被注冊為方法的標識符。
21、最后,你應該記得我們曾經(jīng)說過 Objective-C 里面的 self 指針,類似于 C++ 的this 指針,是作為每一個方法的隱藏參數(shù)傳遞的。其實這里還有第二個隱藏參數(shù),就是_cmd。_cmd 指的是當前方法。
-(void) f:(id)parameter //等價于 C 函數(shù) void f(id self, SEL _cmd, id parameter)
22、代理 delegation是 Cocoa 框架中 UI 元素的一個很常見的部分。代理可以將消息轉(zhuǎn)發(fā)給一個未知的對象。通過代理,一個對象可以將一些任務(wù)交給另外的對象。
23、Objective-C也有繼承的概念,但是不能多重繼承。協(xié)議 protocol和分類categories模擬實現(xiàn)多重繼承。
24、在 Objective-C中,所有方法都是虛的。
25、純虛方法則是使用正式協(xié)議 formal protocols來實現(xiàn)。
26、混合使用協(xié)議、分類和子類的唯一限制在于,你不能同時聲明子類和分類。不過,你可以使用兩步來繞過這一限制:
@interface Foo1 : SuperClass //ok
@end @interface Foo2 (Category) //ok
@end
// 下面代碼會有編譯錯誤
@interface Foo3 (Category) : SuperClass
@end
// 一種解決方案
@interface Foo3 : SuperClass //第一步
@end
@interface Foo3 (Category) //第二步
@end
27、在 Objective-C中,所有對象都是動態(tài)分配的。
重點關(guān)注:
28、self = [super init...]
在上一篇提到的代碼中,最不可思議的可能就是這句 self = [super init...];叵胍幌,self是每個方法的一個隱藏參數(shù),指向當前對象
。因此,這是一個局部變量。那么,為什么我們要改變一個局部變量的值呢?事實上,self必須要改變。我們將在下面解釋為什么要這樣做
。
[super init] 實際上返回不同于當前對象的另外一個對象。單例模式就是這樣一種情況。然而,有一個 API可以用一個對象替換新分配的
對象。Core Data(Apple提供的 Cocoa 里面的一個 API)就是用了這種 API,對實例數(shù)據(jù)做一些特殊的操作,從而讓這些數(shù)據(jù)能夠和數(shù)據(jù)庫
的字段關(guān)聯(lián)起來。當繼承 NSManagedObject類的時候,就需要仔細對待這種替換。在這種情形下,self就要指向兩個對象:一個是 alloc
返回的對象,一個是 [super init]返回的對象。修改 self 的值對代碼有一定的影響:每次訪問實例數(shù)據(jù)的時候都是隱式的。正如下面的代
碼所示:
@interface B : A {
int i;
}
@end
@implementation B
-(id) init {
// 此時,self 指向 alloc返回的值
// 假設(shè) A 進行了替換操作,返回一個不同的
self id newSelf = [super init];
NSLog(@"%d", i); //輸出 self->i 的值
self = newSelf; //有人會認為 i 沒有變化
NSLog(@"%d", i); //事實上,此時的 self->i, 實際是 newSelf->i,
// 和之前的值可能不一樣了
return self;
}
@end
...
B* b = [[B alloc] init];
self = [super init] 簡潔明了,也不必擔心以后會引入 bug。然而,我們應該注意舊的 self指向的對象的命運:它必須被釋放。第一規(guī)則
很簡單:誰替換 self指針,誰就要負責處理舊的 self指針。在這里,也就是 [superinit]負責完成這一操作
29、初始化錯誤
初始化出錯可能發(fā)生在三個地方:
1. 調(diào)用 [super init...]之前:如果構(gòu)造函數(shù)參數(shù)非法,那么初始化應該立即停止;
2. 調(diào)用 [super init...]期間:如果父類調(diào)用失敗,那么當前的初始化操作也應該停止;
3. 調(diào)用 [super init...]之后:例如資源分配失敗等。
在上面每一種情形中,只要失敗,就應該返回 nil;相應的處理應該由發(fā)生錯誤的對象去完成。這里,我們主要關(guān)心的是1, 3情況。要釋放當
前對象,我們調(diào)用 [self release]即可。
30、在調(diào)用 dealloc之后,對象的析構(gòu)才算完成。因此,dealloc的實現(xiàn)必須同初始化方法兼容。事實上,alloc將所有的實例數(shù)據(jù)初始化成 0
是相當有用的。
@interface A : NSObject {
unsigned int n;
}
-(id) initWithN:(unsigned int)value;
@end
@implementation A
-(id) initWithN:(unsigned int)value {
// 第一種情況:參數(shù)合法嗎?
if (value == 0) //我們需要一個正值
{
[self release];
return nil;
}
// 第二種情況:父類調(diào)用成功嗎?
if (!(self = [super init])) //即是 self 被替換,它也是父類
return nil; //錯誤發(fā)生時,誰負責釋放 self?
// 第三種情況:初始化能夠完成嗎?
n = (int)log(value);
void* p = malloc(n); //嘗試分配資源
if (!p) // 如果分配失敗,我們希望發(fā)生錯誤
{
[self release];
return nil;
}
}
@end
31、有三種方法可以增加引用計數(shù)器,也就意味著僅僅有有限種情況下才要使用release釋放對象:
使用 alloc顯式實例化對象;
使用 copy[WithZone:]或者 mutableCopy[WithZone:]復制對象(不管這種克隆是偽);
使用 retain retainretainretain。
32、直接指定(完整代碼)
-(void) setString:(NSString*)newString {
// 沒有強鏈接,舊值被改變了
self->string = newString; //直接指定
}
33、使用 retain指定(完整代碼)
// ------ 不正確的實現(xiàn) ------
-(void) setString:(NSString*)newString
{
self->string = [newString retain];
// 錯誤!內(nèi)存泄露,沒有引用指向舊的"string",因此再也無法釋放
}
-(void) setString:(NSString*)newString
{
[self->string release];
self->string = [newString retain];
// 錯誤!如果 newString == string(這是可能的),
// newString引用是 1,那么在 [self->string release]之后
// 使用 newString 就是非法的,因為此時對象已經(jīng)被釋放
}
-(void) setString:(NSString*)newString
{
if (self->string != newString)
[self->string release]; //正確:給 nil 發(fā)送 release是安全的
self->string = [newString retain]; //錯誤!應該在 if 里面
// 因為如果 string == newString,
// 計數(shù)器不會被增加
}
// ------ 正確的實現(xiàn) ------
// 最佳實踐:C++程序員一般都會"改變前檢查"
-(void) setString:(NSString*)newString
{
// 僅在必要時修改
if (self->string != newString) {
[self->string release]; //釋放舊的
self->string = [newString retain]; // retain新的
}
}
// 最佳實踐:自動釋放舊值
-(void) setString:(NSString*)newString
{
[self->string autorelease]; //即使 string == newString也沒有關(guān)系,
// 因為 release 是被推遲的
self->string = [newString retain];
//... 因此這個 retain 要在 release之前發(fā)生
}
// 最佳實踐:先 retain在 release
-(void) setString:(NSString*)newString
{
[self->newString retain]; //引用計數(shù)器加 1(除了 nil)
[self->string release]; // release時不會是 0
self->string = newString; //這里就不應該再加 retain 了
}
34、復制(完整代碼)
無論是典型的誤用還是正確的解決方案,都和前面使用 retain指定一樣,只不過把retain換成 copy。
35、當返回實例數(shù)據(jù)指針時,外界就可以很輕松地修改其值。這可能是很多 getter不希望的結(jié)果,因為這樣一來就破壞了封裝性。
@interface Button
{
NSMutableString* label;
}
-(NSString*) label;
@end
@implementation Button
-(NSString*) label
{
return label; //正確,但知道內(nèi)情的用戶可以將其強制轉(zhuǎn)換成NSMutableString,
// 從而改變字符串的值
}
-(NSString*) label
{
// 解決方案 1 :
return [NSString stringWithString:label];
// 正確:實際返回一個新的不可變字符串
// 解決方案 2 :
return [[label copy] autorelease];
// 正確:返回一個不可變克隆,其值是一個 NSString(注意不是 mutableCopy)
}
@end
36、循環(huán)retain
必須緊身避免出現(xiàn)循環(huán) retain。如果對象 A retain對象 B,B和 C 相互 retain,那么B和 C 就陷入了循環(huán) retain:
A → B←→ C
如果 A release B,B不會真正釋放,因為 C 依然持有 B。C也不能被釋放,因為 B 持有 C。因為只有 A能夠引用到 B,所以一旦 A release B,就再也沒有對象能夠引用這個循環(huán),這樣就不可避免的造成內(nèi)存泄露。這就是為什么在一個樹結(jié)構(gòu)中,一般是父節(jié)點 retain 子節(jié)點,而子節(jié)點不 retain父節(jié)點。
37、如果開啟垃圾收集器,retain、release和 autorelease 都被重定義成什么都不做
38、異常處理
Objective-C 中使用 @try…@catch…@finally
Cocoa 中有一個 NSException類,推薦使用此類作為一切異常類的父類。因此,catch(NSException *e)相當于 C++ 的 catch(…)。
39、線程安全
@synchronized
由 @synchronized(…)包圍的塊會自動加鎖,保證一次只有一個線程使用。在處理并發(fā)時,這并不是最好的解決方案,但卻是對大多數(shù)關(guān)鍵塊的最簡單、最輕量、最方便的解決方案
@implementation MyClass
-(void) criticalMethod:(id) anObject {
@synchronized(self) {
// 這段代碼對其他 @synchronized(self) 都是互斥的
// self 是同一個對象
}
@synchronized(anObject) {
// 這段代碼對其他 @synchronized(anObject) 都是互斥的
// anObject是同一個對象
}
}
@end
40、字符串
Objective-C 中唯一的 static對象
41、如果沒有給出屬性的參數(shù),那么將使用默認值;否則將使用給出的參數(shù)值。這些參數(shù)值可以是:
readwrite(默認)或者readonly:設(shè)置屬性是可讀寫的(擁有g(shù)etter/setter)或是只讀的(只有g(shù)etter);
assign(默認),retain或copy:設(shè)置屬性的存儲方式;
nonatomic:不生成線程安全的代碼,默認是生成的(沒有atomic關(guān)鍵字);
getter=…,setter=…:改變訪問器默認的名字。
42、對于 setter,默認行為是 assign;retain或者 copy 用于數(shù)據(jù)成員被修改時的操作。
43、RTTI(Run-TimeTypeInformation)
RTTI 即運行時類型信息,能夠在運行的時候知道需要的類型信息。
對象在運行時獲取其類型的能力稱為內(nèi)省。
class, superclass, isMemberOfClass, isKindOfClass
conformsToProtocol
respondsToSelector, instancesRespondToSelector