Cocoa是什么,Cocoa是使用OC語(yǔ)言編寫的工具包,里面有大量的類庫(kù)、結(jié)構(gòu)體,其實(shí)就相當(dāng)于java中的標(biāo)準(zhǔn)API、C++中的標(biāo)準(zhǔn)庫(kù)。OC中沒(méi)有命名空間的概念,所以使用加前綴來(lái)防止命名沖突,因此你會(huì)看到大量的以NS 為前綴的類名、結(jié)構(gòu)體、枚舉等。
Cocoa框架由Foundation Kit、App Kit兩部分組成,前者是基礎(chǔ)工具庫(kù),后者主要是UI庫(kù)、高級(jí)對(duì)象等。
static 標(biāo)識(shí)的類變量定義在接口的外面,類變量只能本類訪問(wèn),除非提供類方法給外部訪問(wèn)這個(gè)類變量
@語(yǔ)法是OC特有的一種語(yǔ)法,C是沒(méi)有的。
OC中只有類的成員變量才有訪問(wèn)權(quán)限控制,@public、@protected、@private,默認(rèn)是@protected,類變量、類方法、成員方法是沒(méi)有訪問(wèn)修飾符的,所有的方法都是public的,所有的類變量都是私有的。
OC中的類方法只能類調(diào)用,如果使用對(duì)象調(diào)用就會(huì)報(bào)錯(cuò),而java這只是一個(gè)警告而已。
OC中定義類的@interface和java中的interface不是一回事,OC的@protocol和java中的interface才是一碼事。@interface跟java和 C++ class關(guān)鍵字差不多,在OC和C++中,都習(xí)慣將類定義寫在頭文件里,而java卻根本沒(méi)有頭文件的概念,至于為什么有這樣的差別,想是為了OC和 C++編譯器方便,大多數(shù)時(shí)候編譯器只需要知道頭文件類的定義就知道這個(gè)類有哪些屬性和方法了。
get為前綴的方法在OC中有特殊的意義,所以盡量使用成員變量名稱來(lái)作為getter方法的函數(shù)名。
OC是動(dòng)態(tài)語(yǔ)言,所以即便是@interface中沒(méi)有定義的方法在.m文件中實(shí)現(xiàn)了也是OK的,一般我們都是給別人提供頭文件,這種情況就相當(dāng)于把方法變相的私有化了,對(duì)于我這種喜歡在寫代碼過(guò)程中隨時(shí)加方法但又不想動(dòng)頭文件的人來(lái)說(shuō)還是比較爽的。
在OC中所有對(duì)象的存取都是用指針,C++中還有引用,OC中沒(méi)有,我寧愿你強(qiáng)迫我使用一種方法,也不愿你提供給我兩種方法我卻不知道使用哪一種方法的好。
OC的方法定義和調(diào)用算是一大“特色”:
方法的定義:-(返回類型) 方法名:(參數(shù)1類型)參數(shù)1變量 參數(shù)2標(biāo)簽:(參數(shù)2類型)參數(shù)2變量…
[類或者實(shí)例的指針?lè)椒? 參數(shù)1 標(biāo)簽2: 參數(shù)2… …],
為了好看,第一個(gè)參數(shù)一般不加標(biāo)簽名,當(dāng)然,標(biāo)簽名都可以隱藏的,但不建議這樣做, 因?yàn)楫?dāng)你接手了一個(gè)離職的人程序,其中的JAVA 程序調(diào)用了一個(gè)有五個(gè)甚至更多的參數(shù)的方法,但是你手里沒(méi)有這個(gè)方法的API,那么你很難猜得出來(lái)這五個(gè)參數(shù)到底都干什么用的,但是Objective- C調(diào)用的時(shí)候,每個(gè)參數(shù)前面都必須有方法的標(biāo)簽名,這樣你便能很容易的從標(biāo)簽名看出這個(gè)參數(shù)是什么意思。
調(diào)用類方法:
[Fraction t];
[[Fraction class] t];
Class clazz=[Fraction class];[clazz t];
class 來(lái)自于NSObject,相當(dāng)于JAVA 中的getClass()方法,也就是獲取這個(gè)類的Class 對(duì)象,
clazz 前面沒(méi)有,這是因?yàn)镃lass 已經(jīng)是一個(gè)指針。另外這種嵌套調(diào)用的方式,也要習(xí)慣,這就和JAVA 中的A.b().c()沒(méi)有什么區(qū)別。
獲取Class 有如下幾種方法:
[類或者對(duì)象 class]
[類或者對(duì)象 superclasss]
NSClassFromString(類名的字符串形式)
你也可以通過(guò)如下的函數(shù)把Class 轉(zhuǎn)換為字符串形式:
NSStringFromClass(Class)
OC中自定義類最好顯示繼承NSObject,可以獲得alloc、init、release等方法。
OC中使用nil表示null,但跟java中的null還是有區(qū)別的,java中調(diào)用null的方法,會(huì)報(bào)臭名昭著的空指針異常,而OC不會(huì),所以在OC你不必再為空指針而煩惱。
Cocoa中提供的很多函數(shù)和java中的api有很大的不一樣,java是純面向?qū)ο蟮?,所有的方法都必須在類中定義,但OC是兼容C語(yǔ)言的,所以C中的面向過(guò)程思想在OC中也是行得通的,因此Cocoa中很多東西都不是對(duì)象,而是C語(yǔ)言的函數(shù)、結(jié)構(gòu)體,比如NSLog()。
對(duì)象的初始化
前面看到實(shí)例化對(duì)象最多的方法是:Fraction frac=[[Fraction alloc] init];
這跟java不一樣,java對(duì)象創(chuàng)建只需要new一下,同時(shí)調(diào)用構(gòu)造方法(實(shí)際上虛擬機(jī)層面分為兩個(gè)步驟:new對(duì)象,執(zhí)行方法(包含構(gòu)造方法)),但是OC中分為兩步:分配內(nèi)存(同時(shí)給變量賦初值)、初始化。
alloc 是從NSObject 繼承而來(lái)的類方法,用于給對(duì)象分配存儲(chǔ)空間,所有的成員變量在此時(shí)對(duì)確定了自己的內(nèi)存位置,并被賦初值,整數(shù)類型為0,浮點(diǎn)數(shù)為0.0,BOOL 為NO,對(duì)象類型為nil,alloc 方法返回對(duì)象的指針。
init這個(gè)方法是從NSObject繼承而來(lái)的,你可以覆蓋它,當(dāng)然init不是構(gòu)造方法,只是一個(gè)普通方法而已,你甚至可以換成其他方法,只不過(guò)我們習(xí)慣在方法名前綴加上init。
還記得java中的構(gòu)造方法沒(méi)有返回值吧,所以為了達(dá)到j(luò)ava這種效果:Test t = new Test(2);OC中需要你手動(dòng)在普通方法(相當(dāng)于構(gòu)造方法)中return self。
NSObject中的description方法相當(dāng)于java中Object的toString方法,用于獲取對(duì)象的字符串表示。唯一不同的是OC中需要使用格式化字符串%@,才會(huì)觸發(fā)description被調(diào)用:
Fractionfrac=[[Fraction alloc] init];
NSLog(@”%@”,frac);
Objective- C的異常都繼承自NSException,當(dāng)然NSException 本身也繼承自NSObject。總體來(lái)說(shuō),OC中的異常體系和java中的異常體系差不多,唯一的差別就是java中有業(yè)務(wù)異常,意思就是說(shuō)你必須捕獲 他,如果不捕獲就會(huì)出現(xiàn)編譯錯(cuò)誤,OC中的異?;旧隙紝儆趈ava中的運(yùn)行時(shí)異常,如果你沒(méi)有捕獲,發(fā)生了異常程序就會(huì)崩潰,在一些循環(huán)批任務(wù)中尤其要注意。
OC中還有一個(gè)特色那就是他的弱類型id,他可以表示任何對(duì)象,實(shí)際上應(yīng)該是利用了指針地址的通用性,由于OC中操作對(duì)象都必須是指針,而指針本身又是一串地址,所以通過(guò)指針可以指向一切不同的對(duì)象。
繼承(感覺(jué)和java差不多?。?/p>
反射(這個(gè)和異常一樣,感覺(jué)跟java也很像)
選擇器(其實(shí)就是C中的函數(shù)指針,以及java中的Method類)
類別(Category),擴(kuò)充類的功能,但不可以聲明新的成員變量。
類別的應(yīng)用比較廣泛,譬如:第三方的Objective-C庫(kù)RegexKitLite 就是對(duì)NSString、NSMutableString做的類別,為Objective-C的字符類型增加了正則表達(dá)式的功能。
字符串
-(BOOL) isEqualToString: (NSString) s
比較兩個(gè)字符串是否相等,與JAVA 一致的地方是==比較指針,比較對(duì)象是否相同要用到equal 方法。
-(NSMutableString) appendString: (NSString) s
這與JAVA 的StringBuffer 的append 沒(méi)什么區(qū)別。
數(shù)組
Cocoa 使用NSArray 表示數(shù)組,但是它不能存儲(chǔ)基本數(shù)據(jù)類型、enum、struct、nil,只能存儲(chǔ)Objective-C 的對(duì)象。
NSArrayarray=[NSArray arrayWithObjects: @”O(jiān)ne”, @”Two”, @”Three”, nil];
從這個(gè)類方法arrayWithObjects 的定義可以看出,它使用nil 表示數(shù)組元素結(jié)束,這也是nil 不能存儲(chǔ)在NSArray 中的原因。
NSMutableArray 為長(zhǎng)度可變的數(shù)組,相當(dāng)于JAVA 中的List:
NSMutableArray?mArray=[NSMutableArray arrayWithCapacity: 10];
[mArray addObject: @”Apple”];//添加數(shù)組元素
NSEnumeratore = [mArray objectEnumerator];//獲取數(shù)組的迭代器,相當(dāng)于JAVA 中的Iterator,reserveObjectEnumerator 用于獲取反轉(zhuǎn)之后的數(shù)組迭代器。與JAVA 一致的地方是你在使用迭代器時(shí),不能對(duì)數(shù)組進(jìn)行添加、刪除操作。
字典(哈希表)
NSDictionary?用于存儲(chǔ)key-value 的數(shù)據(jù)結(jié)構(gòu),與JAVA 中的Map 類似。
NSDictionary dic=[NSDictionary dictionaryWithObjectsAndKeys: @”Apple”, @”A”, @”Google”,@”G”, nil];//dictionaryWithObjectAndKeys 后的可變參數(shù),每?jī)蓚€(gè)為一個(gè)value-key,以nil 表示結(jié)束。
NSLog(@”%@”,[dic objectForKey: @”A”]);//按照指定的key 查詢value
同樣的有NSMutableDictionary?表示長(zhǎng)度可變的字典。
NSMutableDictionarymDic=[NSMutableDictionary dictionaryWithCapacity: 10];
[mDic setObject: @”Apple” forKey: @”A”];//添加value-key 對(duì)
哈希Set
NSSet 表示以hash 方式計(jì)算存儲(chǔ)位置的集合,與JAVA 中的HashSet 是一致的。
在NSSet 中的每個(gè)對(duì)象都有一個(gè)唯一的hash 值,重復(fù)的對(duì)象將只能保留一個(gè)。因此,這引出了Objective-C中的對(duì)象比較問(wèn)題,這需要你實(shí)現(xiàn)從NSObject 繼承而來(lái)的如下兩個(gè)方法:
- (BOOL)?isEqual: (id) anObject;
- (NSUInteger)?hash;
這與JAVA 的對(duì)象比較沒(méi)有什么區(qū)別,兩個(gè)相等的對(duì)象必須有相同的hashCode,所以這兩個(gè)方法必須同時(shí)實(shí)現(xiàn)。
同樣的,NSMutableSet?表示長(zhǎng)度可變的哈希Set。
封裝類(相當(dāng)于java的包裝器)
前面的幾個(gè)容器類的對(duì)象都不能存放基本數(shù)據(jù)結(jié)構(gòu)、enum、struct、nil,怎么辦呢?
在JAVA中我們知道所有的基本數(shù)據(jù)類型都有對(duì)應(yīng)的封裝類,例如:int—-Integer、boolean—-Boolean,使用封裝類可以把基本數(shù)據(jù)類型包裝為對(duì)象,而封裝類本身又有方法可以返回原來(lái)的基本數(shù)據(jù)類型。Cocoa 使用NSValue?作為封裝類。
NSRect rect=NSMakeRect(1,2,30,50);//這個(gè)是在前面提到過(guò)的一個(gè)表示矩形的結(jié)構(gòu)體
NSValue v=[NSValue valueWithBytes: &rect objCType: @encode(NSRect)];
//java與oc之間的比較 valueWithBytes 要求傳入包裝數(shù)據(jù)的地址,也就是變量中存儲(chǔ)的數(shù)據(jù)的首地址,我們依然使用C 語(yǔ)言的&作為地址運(yùn)算符,計(jì)算指針存儲(chǔ)的首地址。
//objCType 要求傳入用于描述數(shù)據(jù)的類型、大小的字符串,使用@encode 指令包裝數(shù)據(jù)所屬的類型。
對(duì)于基本數(shù)據(jù)類型,你可以使用簡(jiǎn)便的NSNumber?來(lái)封裝,它是NSValue 的子類。
如果你想存儲(chǔ)空值到集合類,可以使用NSNull,它使用NSNulln =[NSNull null];的方式創(chuàng)建。
日期類型
Cocoa 中使用NSDate 類型表示日期。
數(shù)據(jù)緩沖區(qū)(其實(shí)就是java中的字節(jié)數(shù)組)
Cocoa 中使用NSData 類型來(lái)實(shí)現(xiàn)緩沖區(qū),用于存儲(chǔ)二進(jìn)制的數(shù)據(jù)類型,譬如:從網(wǎng)絡(luò)下載回來(lái)的文件等。
NSData?是長(zhǎng)度不可變的數(shù)據(jù)緩沖區(qū),還有一個(gè)NSMutableData?用來(lái)存儲(chǔ)長(zhǎng)度可變的數(shù)據(jù)緩沖區(qū)。
寫入和讀取屬性(簡(jiǎn)直就是java序列化和反序列化的翻版)
在iPhone 的.ipa 文件中,經(jīng)??梢钥吹?i>.plist 文件,它保存了程序的相關(guān)屬性,叫做屬性列表。其實(shí)它就是NSArray、NSDictionary、NSString、NSData 持久化之后的文件。
這幾個(gè)類型都有一個(gè)成員方法?writeToFile: (NSString) file atomically: BOOL 用于將自己寫入到一個(gè)文件。atomically 為YES 表示文件先存儲(chǔ)到臨時(shí)文件區(qū),如果文件保存成功,再替換掉原始文件,這樣的好處是可以防止在保存文件過(guò)程中出錯(cuò),但是僅適用于小文件,因?yàn)槟阆喈?dāng)于是在臨 時(shí)文件存儲(chǔ)區(qū)也放了一份,再加上原始文件,就是兩份文件,如果文件比較大,將會(huì)占用較多的用戶的磁盤空間。
如果你要持久化的類型不是上述的數(shù)組、字典、緩沖區(qū),那該怎么辦呢?Objective-C 中也有和JAVA 一樣的序列化、反序列化支持,使用NSCoding?協(xié)議。NSCoding 協(xié)議定義了如下兩個(gè)方法:
-(void)?encodeWithCoder: (NSCoder) coder;
-(id) initWithCoder: (NSCoder) decoder;
第一個(gè)方法相當(dāng)于編碼對(duì)象,第二個(gè)方法是解碼回對(duì)象,也就相當(dāng)于給類型定義了一個(gè)新的init 方法而已。
對(duì)象的復(fù)制
對(duì)象的復(fù)制就相當(dāng)于JAVA 中的clone()方法,也就是對(duì)象的深度復(fù)制,所謂深度復(fù)制就是重新分配一個(gè)存儲(chǔ)空間,并將原對(duì)象的內(nèi)容都復(fù)制過(guò)來(lái),從這些描述可以看出,復(fù)制也會(huì)分配空間,那就是你要對(duì)復(fù)制出來(lái)的對(duì)象release,就是前面所說(shuō)的alloc、new、copy 操作創(chuàng)建的對(duì)象,要手工release。
Objective-C 中的一個(gè)對(duì)象是否可以被復(fù)制,要看它的類型是否遵循NSCopying 協(xié)議,這個(gè)協(xié)議中有個(gè)復(fù)制方法-(id) copyWithZone: (NSZone) zone 需要我們?nèi)?shí)現(xiàn)。
多線程
Objective-C 的多線程編程與JAVA 語(yǔ)言極其類似分為原始的線程操作、線程池兩種,后者其實(shí)就是使用隊(duì)列等機(jī)制對(duì)前者的封裝。
JAVA 也一樣,原始的線程操作使用Thread 類,線程池的操作使用java.util.concurrent.中的類庫(kù)。
Objective-C 中NSThread?類型表示線程,NSCondition?用于執(zhí)行同步操作,在功能和用法上相當(dāng)于JAVA 中的java.util.concurrent.*包中的Lock 對(duì)象。
另外,Objective-C 也支持@synchronized 指令做代碼同步,寫法也和JAVA 中的很相似。
另外比較有趣的是,如果你想更新UI 上的某一個(gè)部件,就需要在發(fā)起的新線程里調(diào)用UI 所在的主線程上的一個(gè)方法,新線程不能直接訪問(wèn)主線程的方法,需要在run 方法中使用如下的方法:
- (void)?performSelectorOnMainThread: (SEL) aSelector withObject: (id) arg waitUntilDone: (BOOL) wait
如果對(duì)java的swing熟悉的話,應(yīng)該知道在UI界,大多數(shù)更新界面的都必須在一個(gè)單線程中執(zhí)行,因?yàn)槿绻陆缑媸嵌嗑€程的話會(huì)很難處理的,java swing處理的方法是更新界面的代碼放在專門更新UI的線程下執(zhí)行,這里IOS也是這么一個(gè)思想。
Objective-C 使用?NSOperation、NSOperationQueue 兩個(gè)類型實(shí)現(xiàn)線程池的操作,NSOpertion就好比JAVA 中的Runnable, 其中的main(名字有點(diǎn)兒奇怪)方法也就是你要執(zhí)行的操作,NSOperationQueue 是一個(gè)線程池隊(duì)列,你只需要把NSOperation 添加到NSOperationQueue,隊(duì)列就會(huì)retain 你的NSOperation,然后執(zhí)行其中的操作,直到執(zhí)行完畢。
隊(duì)列中會(huì)有多個(gè)操作,隊(duì)列會(huì)按照你添加的順序逐個(gè)執(zhí)行,也就說(shuō)隊(duì)列中的任務(wù)是串行的。
跟java一樣,如果線程池可以滿足你的業(yè)務(wù)需求,盡量使用線程池,而不是原始的NSThread,因?yàn)槭褂镁€程池屏蔽了許多線程自身需要處理的問(wèn)題,代碼也更加簡(jiǎn)潔。
KVC 與KVO(這個(gè)話題真高級(jí))
KVC 是 NSKeyValueCoding 的縮寫,它是Foundation Kit 中的一個(gè)NSObject 的Category,作用可以類比JAVA 中的反射機(jī)制,就是動(dòng)態(tài)訪問(wèn)一個(gè)對(duì)象中的屬性。
KVC 在解析key 的字符串的時(shí)候,是會(huì)比正常調(diào)用setter、getter 要慢的,而且編譯器無(wú)法在編譯器對(duì)你的方法調(diào)用做出檢查(因?yàn)槟愕膶傩悦际亲址?,只有運(yùn)行時(shí)才會(huì)知道你有沒(méi)有寫錯(cuò)),出錯(cuò)的幾率也會(huì)提高,所以請(qǐng)不要 隨意使用KVC,而省去setter、getter 方法。KVC 一般用于動(dòng)態(tài)綁定,也就是運(yùn)行時(shí)才能確定誰(shuí)調(diào)用哪個(gè)方法,編譯期并不確定。
KVO就是 NSKeyValueObserving的縮寫,它也是Foundation Kit中的一個(gè)NSObject的Category,KVO 基于KVC 實(shí)現(xiàn),基于觀察者設(shè)計(jì)模式(Observer Pattern)實(shí)現(xiàn)的一種通知機(jī)制,可以類比JAVA 中的JMS,通過(guò)訂閱的方式,實(shí)現(xiàn)了兩個(gè)對(duì)象之間的解耦,但又可以讓他們相互調(diào)用。