根類的作用:
作為一門動態(tài)編程語言,Objective-C有很多動態(tài)的特性澈歉,因此Objective-C不僅需要編譯環(huán)境展鸡,同時還需要一個運行時系統(tǒng)來執(zhí)行編譯好的代碼。運行時系統(tǒng)扮演的角色類似于Objective-C的操作系統(tǒng)埃难,它負(fù)責(zé)完成對象生成莹弊,釋放時的內(nèi)存管理涤久,為發(fā)來的消息查找對應(yīng)的處理方法等。
通常情況下忍弛,程序中無法直接使用運行時系統(tǒng)提供的功能响迂。根類方法中提供了運行時系統(tǒng)的基本功能。根類相當(dāng)于運行時系統(tǒng)的一個接口细疚。
根類通過哪些方式提供了哪些功能對系統(tǒng)有很大的影響蔗彤。因此根類不同的系統(tǒng)之間是無開發(fā)出通用的程序的。
類和實例:
NSObject只有一個實例變量疯兼,就是Class類型的變量isa然遏。isa用于標(biāo)識實例對象屬于哪個類對象。因為isa決定著實例變量和類的關(guān)系吧彪,非常重要待侵,所以子類不可修改isa的值。另外姨裸,也不能通過直接訪問isa來查詢實例變量到底屬于哪個類秧倾,而要通過實例方法class來完成查詢。
下面對類和實例變量的相關(guān)方法進行說明:
- (Class)class//返回消息接受者所屬類的類對象
+ (Class)
class//返回類對象
//雖然可以使用類名作為消息的接受者來調(diào)用類方法傀缩,但當(dāng)類對象是其他消息的參數(shù)那先,或者將類對象賦值給變量的時候,需要通過這個類方法來獲取類對象
- (
id)self//返回消息接收者自身赡艰。是一個無任何實際動作但很有用的方法
- (
BOOL)isMemberOfClass:(Class)aClass//判斷消息接受者是不是參數(shù)aClass類的對象
- (
BOOL)isKindOfClass:(Class)aClass//判斷消息接受者是否是參數(shù)aClass類或者aClass類的子類的實例售淡。這個函數(shù)和isMemberOfClass:的區(qū)別在于當(dāng)消息的接收者是aClass的子類的實例時也會返回YES。
- (Class)superclass
//返回消息接收者所在類餓父類的類對象
+ (Class)superclass
//返回消息接收類的父類的類對象
實例對象的生成和釋放:
+ (id)alloc//生成消息接收類的實例對象瞄摊。通常和init或init開頭的方法連用勋又,生成實例對象的同時需要對其進行初始化苦掘。子類不允許重寫alloc换帜。
- (
void)dealloc//釋放實例對象。dealloc被作為release的結(jié)果調(diào)用鹤啡。除了在子類中重寫dealloc的情況之外惯驼,程序中不允許直接調(diào)用dealloc。
- (
onewayvoid)release//將消息的引用計數(shù)減1递瑰。引用計數(shù)變?yōu)?a href="" target="_blank">0時祟牲,dealloc方法被調(diào)用,消息接收者被釋放
- (
id)retain//為消息接收者的引用計數(shù)加1抖部,同時返回消息接收者说贝。
- (
id)autorelease//把消息的接收者加入到自動釋放池中,同時返回消息接收者慎颗。
- (NSUInteger)retainCount
//返回消息接收者的引用計數(shù)乡恕,可在調(diào)試時使用這個方法言询。NSUInteger是無符號整數(shù)類型。
- (
void)finalize//垃圾收集器在釋放接收者對象之前會執(zhí)行finalize方法
上面從dealloc到reatainCount都是手動引用計數(shù)管理內(nèi)存時使用的方法傲宜,使用ARC時不可用运杭,finalize僅供垃圾回收有效時使用。
初始化:
- (id) init//init可對alloc生成的實例對象進行初始化函卒。子類中可以重寫init或者定義新的以init開頭的初始化函數(shù)辆憔。
+ (
void)initialize//被用于類的初始化,也就是對類中共同使用的變量進行初始化設(shè)定等报嵌。這個方法會在類收到第一個消息之前被自動執(zhí)行虱咧,不允許手動調(diào)用。
+ (
id)new//new是alloc和init的組合沪蓬。new方法返回的實例對象的所有者就是調(diào)用new方法的對象彤钟。但是把alloc和init組合定義為new并沒有什么優(yōu)點。根據(jù)類的實現(xiàn)不同跷叉,new方法并不會每次都返回一個全新的實例對象逸雹。有時new方法會返回對象池中預(yù)先生成的對象,也有可能每次都返回同一個對象云挟。
對象的比較:
- (BOOL)isEqual:(id)anObject//消息接收者如果和參數(shù)anObject相等就返回YES梆砸。
- (NSUInteger)hash
//在把對象放入容器等的時候,返回系統(tǒng)內(nèi)部用的散列值园欣。
對象內(nèi)容的描述:
+ (NSString*)description//返回一個NSString類型的字符串帖世,表示消息接收者所屬類的內(nèi)容。通常都是這個類的類名沸枯。
- (NSString *)description
//返回一個NSString類型的字符串日矫,表示消息接收者的實例對象的內(nèi)容。通常是類名加id值绑榴。子類中也可以重新定義description的返回值哪轿。例如,NSString的實例會返回字符串的內(nèi)容翔怎,NSArray的實例會對數(shù)組中的每一個元素調(diào)用description窃诉,然后將調(diào)用結(jié)果用句號進行分割,并一起返回赤套。
消息發(fā)送機制:
選擇器和SEL類型:
程序中的方法名(選擇器)在編譯后會被一個內(nèi)部標(biāo)識符所替代飘痛,這個內(nèi)部標(biāo)識符所對應(yīng)的數(shù)據(jù)類型就是SEL類型。
OC為了能夠在程序中操作編譯后的選擇器容握,定義了@selector()指令宣脉,通過使用@selector()指令,就可以直接引用編譯后的選擇器剔氏。選擇器對應(yīng)的SEL類型的值和處理器相關(guān)塑猖。如果SEL類型的變量無效的話堪遂,可設(shè)其為NULL,或者也可以使用(SEL)0這種常見的表達方法萌庆。
用SEL類型的變量來發(fā)送消息的方法如下:
- (id)performSelector:(SEL)aSelector//向消息接收者發(fā)送aSelector代表的消息溶褪,返回這個消息執(zhí)行的結(jié)果。
- (
id)performSelector:(SEL
)aSelector
withObject:(
id)anObject//向消息接收者發(fā)送aSelector代表的消息践险,消息的參數(shù)為anObject猿妈,返回這個消息執(zhí)行的結(jié)果。
下面兩個消息表達式進行的處理是相同的:
[target description];
[target performSelector:
@selector(description)];
下面這個例子展示了如何根據(jù)條件動態(tài)決定執(zhí)行哪個方法:
SELmethod = [cond1] ?@selector(activate:) :@selector(hide:);id
obj = [cond2] ? myDocument : defaultDocument;
[target performSelector:method withObject:obj];
這種調(diào)用方式很像C語言中的函數(shù)指針巍虫,利用函數(shù)指針也可以實現(xiàn)和上面同樣的功能彭则。
函數(shù)指針是函數(shù)在內(nèi)存中的地址,指針對應(yīng)的函數(shù)是在編譯時決定的占遥,不能夠執(zhí)行指定之外的函數(shù)俯抖。SEL類型就相當(dāng)于方法名,根據(jù)消息接收者的不同瓦胎,來動態(tài)執(zhí)行不同的方法芬萍。
消息搜索:
對象受到一個消息后會執(zhí)行哪個方法是被動態(tài)決定的。
所有的實例變量都存在一個Class類型的isa變量搔啊,它就是類對象柬祠。當(dāng)收到消息后,運行時系統(tǒng)會從類內(nèi)開始檢查是否有和這個消息選擇器相同的方法负芋,找到就執(zhí)行漫蛔。沒找到就去父類中找,一直到根類旧蛾,如果還沒找到莽龟,就會提示執(zhí)行時錯誤。
如果每次收到消息都需要查找相應(yīng)的方法锨天,會造成很大的開銷毯盈。因此,運行時系統(tǒng)內(nèi)部會緩存一個散列表绍绘。當(dāng)下次在收到同樣的消息時奶镶,直接利用上次緩存的信息即可迟赃,不需要從頭搜索陪拘。
NSObject定義了一個可以動態(tài)查詢一個對象是否能夠響應(yīng)某個選擇器的方法:
- (BOOL)respondsToSelector:(SEL)aSelector//查詢消息接收者中是否有能夠響應(yīng)aSelector的方法,包括從父類繼承來的方法纤壁。
- (
BOOL)instancesRespondToSelector:(SEL)aSelector//查詢消息接收者所屬的類中是否有能夠響應(yīng)aSelector的方法左刽,包括從父類繼承來的方法。
以函數(shù)的形式來調(diào)用方法:
類中定義的方法通常是以函數(shù)的形式實現(xiàn)的酌媒,但通常在編程的時候并不會直接操作方法所對應(yīng)的函數(shù)欠痴。但如果想讓程序盡可能的快一點迄靠,或者需要按照C語言的慣例傳遞函數(shù)指針的時候,可以直接調(diào)用方法對應(yīng)的函數(shù)喇辽,以節(jié)省發(fā)送消息的開銷掌挚。另外,執(zhí)行動態(tài)加載方法的定義等時菩咨,也可以將方法作為函數(shù)調(diào)用吠式。但是,如果以函數(shù)的形式來調(diào)用方法的話抽米,將無法利用面向?qū)ο蟮膭討B(tài)綁定等功能特占。雖然消息發(fā)送同函數(shù)調(diào)用相比確實慢一點,但卻有面向?qū)ο蟮膭討B(tài)綁定云茸,多態(tài)等優(yōu)點是目。同這些優(yōu)點相比,速度上略微的損失是不值得一提的标捺。
通過下面的方法懊纳,可以獲得某個對象持有的方法的函數(shù)指針干像,這些方法都被定義在NSObject中:
- (IMP)methodForSelector:(SEL)aSelector//搜索和指定選擇器相對應(yīng)的方法烘挫,并返回指向該方法實現(xiàn)的函數(shù)指針井联。實例對象和類對象都可以使用這個方法年鸳。對實例對象使用時绕沈,會返回實例方法對應(yīng)的函數(shù)膝昆,對類對象使用時熄捍,會返回類對象對應(yīng)的函數(shù)典格。
+ (
IMP)instanceMethodForSelector:(SEL)aSelector//搜索和指定選擇器相對應(yīng)的實例方法列敲,并返回指向該實例方法實現(xiàn)的函數(shù)指針阱佛。
IMP是“implementation”的縮寫,它是一個函數(shù)指針戴而,指向了方法實現(xiàn)代碼的入口凑术。IMP的定義為:
typedefid(*IMP)(id,SEL, ...);
這個被指向的函數(shù)包括id(self指針),調(diào)用的SEL(方法名)所意,以及其他一些參數(shù)淮逊。例:
- (id)setBox:(id)obj1 title:(id)obj2;
foo是這個方法所屬類的一個實例變量。獲取指向setBox的函數(shù)指針扶踊,并通過該指針進行函數(shù)調(diào)用過程如下:
IMP
funcp;
funcp = [foo methodForSelector:
@selector
(setBox:title:)];
xyz = (*funcp)(foo,
@selector(setBox:title:), param1, param2);
對self進行賦值:
self是方法的一個隱含參數(shù)泄鹏,它代表的是收到消息的對象自身。
- (id)initWitMax:(int)a/*推薦使用這種寫法*/
{
if((self= [superinit]) !=nil
) {
max = a;
}
returnself
;
}
在OPENSTEP時代秧耗,OC初始化方法一般采用下面這種寫法备籽。但這里需要注意的是沒有用父類初始化方法的返回值對self進行賦值。子類的初始化方法和父類的初始化方法都是對同一個對象進行操作的分井,所以不需要顯示地對self進行賦值操作车猬。需要注意的是這種寫法也有可能出錯霉猛,除了初始化失敗外,父類的初始化方法也有可能并沒有返回self而是返回了其他對象珠闰。如類簇構(gòu)成的類在初始化方法中就沒有返回self惜浅。所以第一種方法是更為安全的做法。
- (
id)initWithMax:(int)a/*舊的寫法*/
{
[
super
init];
max = a;
returnself
;
}
另外伏嗜,在用ARC的時候赡矢,如果初始化方法的返回值沒有被用到,編譯就會發(fā)生錯誤阅仔。
發(fā)送消息的速度:
消息送信所需要的時間是函數(shù)調(diào)用所需時間的2倍吹散。另外,直接發(fā)送消息比用performSelector:發(fā)送消息速度更快八酒。
類對象和根類:
類對象的類被叫做元類空民。實例對象所屬的類是class。類對象所屬的類是元類羞迷。
OC中很多的概念都來自Smalltalk界轩,元類的概念就是其中之一。但現(xiàn)在的OC中已經(jīng)不存在元類的概念了衔瓮,程序中也不能操作元類浊猾。用于表示對象的id類型和表示類的Class類型實際上都是指向結(jié)構(gòu)的指針。
類對象和實例對象都存在一個成員變量isa热鞍,它是一個objc_class類型的指針葫慎。類對象中保存的是實例方法,元類對象中保存的是類方法薇宠,通過這樣的定義能夠統(tǒng)一實現(xiàn)實例方法和類方法的調(diào)用機制偷办。
任何一個類對象都是都是繼承了根類的元類對象的一個實例。也就是說澄港,類對象可以執(zhí)行根類對象的實例方法椒涯。
總結(jié):
所有類的實例對象都可以執(zhí)行根類的實例方法
如果在派生類在重新定義類實例方法,新定義的方法會被執(zhí)行
所有類的類對象都可以執(zhí)行根類的類方法
如果在派生類中重新定義了類方法回梧,新定義的方法會被執(zhí)行 废岂。
所有類的類對象都可以執(zhí)行根類的實例方法
即使在派生類中重新定義了實例方法,根類中的方法也會被執(zhí)行狱意。
如果在派生類中將實例方法作為類方法重新定義了的話湖苞,新定義的方法會被執(zhí)行。
Target-action paradigm(目標(biāo)-動作模式):
通過使用SEL類型的變量髓涯,能夠在運行時動態(tài)決定執(zhí)行哪個方法袒啼。實際上哈扮,Application框架就利用了這種機制實現(xiàn)了GUI控件對象間的通信纬纪。
來看看下面這個例子:
@interfacemyCell :NSObject
{
SEL action;
id target;
...
}
- ?(void)setAction:(SEL)aSelector;
- ?(void)setTarget:(id)anObject;
- ?(void)performClick:(id)sender;
...
@end
@implementation
myCell
- (void)setAction:(SEL)aSelector
{
action = aSelector;
}
- ?(void)setTarget:(id)anObject
{
target = anObject;
}
- ?(void)performClick:(id)sender
{
(void)[targetperformClick:action withObject:sender];
}
...
@end
Application框架的目標(biāo)-動作模式在發(fā)送消息時使用了下面這種形式定義的方法蚓再,即只有一個id類型的參數(shù),沒有返回值包各。這種形式的方法叫做動作方法摘仅。
- (void) xxxx:(id)sender;
當(dāng)用戶操作了某個GUI控件,action指定的消息就會被發(fā)送到事先設(shè)定好的target问畅,消息定義如上所示娃属,消息的參數(shù)通常都是GUI的控件id。這樣一來护姆,消息的接收者target就會知道到底哪個控件發(fā)送了什么樣的消息矾端。
setTarget:和setAction:來指定目標(biāo)和動作,使用ARC時推薦使用弱引用卵皂。
UIKit是iPhone和iPad中用來建立和管理應(yīng)用程序界面的框架秩铆。Application框架中的動作方法只有一種格式,UIKit框架中的動作方法則有以下三種格式:
- (void)xxxx;
- (void)xxxx:(id)sender;
- (void)xxxx:(id)sender forEvent:(UIEvent *)event;//表示與操作相關(guān)的事件信息
Xcode中的動作方法和Outlet的寫法:
綜合開發(fā)環(huán)境Xcode及其用于設(shè)計和測試GUI的工具Interface Builder中灯变,為了連接對象殴玛,在類的接口部分中聲明了一些宏,通過這些宏變量能夠?qū)⒋a連接到nib添祸。
動作方法的聲明如下滚粟,IBAction是一個宏,被定義為void:
- (IBAction) xxxx:(id)sender;
outlet的含義:
outlet可以被理解為一個插座刃泌,可以通過outlet從控件取出信息凡壤,或?qū)⑿碌男畔①x給控件。outlet通常是一個類的實例變量耙替,例如:一個指向NSButton類型的控件的實例變量聲明如下:
IBOutletNSButton *theButton;
這里的IBOutlet也是一個宏鲤遥,被定義為空,另外還有下面這樣一種定義方式:
IBOutletCollection(NSButton)NSArray*buttons;
這樣聲明之后林艘,就可以將Xcode上面的多個控件都連接到這個Outlet上盖奈。IBOutletCollection(NSButton)也是一個宏定義,編譯之后會被替換為空狐援。在多個控件需要統(tǒng)一處理的時候钢坦,這種定義方法會很方便。
屬性聲明也可以定義為Outlet啥酱,如下所示:
@property(weak)IBOutletNSButton *okButton;
Xcode可以把這個屬性聲明看作是Outlet爹凹,并用其來連接控件。另外也可以為這個屬性聲明增加訪問方法镶殷。
使用ARC進行開發(fā)的情況下禾酱,要注意避免形成對象之間的引用循環(huán)。所以,除了主要的對象之間的連接使用強引用之外颤陶,其余的對象之間進行連接時都推薦使用弱引用颗管。屬性聲明時,建議加上assign或者weak選項滓走。
Objective-C和Cocoa環(huán)境:
cocoa環(huán)境和Mac OS X:
我們經(jīng)常提到的cocoa環(huán)境通常是指AppKit和Foundation這兩個核心框架垦江,但有時候也包含Core Foundation或Core Date等框架。
Cocoa是為了提供構(gòu)建應(yīng)用程序所必需的功能而設(shè)計的搅方,所以才會利用下層的功能比吭。
Cocoa Touch 和iOS:
iPhone和iPad的操作系統(tǒng)iOS使用Cocoa Touch作為GUI環(huán)境。下圖展示了iOS的架構(gòu)以及和Cocoa Touch的關(guān)系姨涡。
有Foundation和UIKit框架組合而成的GUI環(huán)境稱為Cocoa Touch:
框架:
將開發(fā)軟件和執(zhí)行軟件所必需的圖形庫衩藤,頭文件和設(shè)定用的各種信息全部匯總在一起就構(gòu)成了框架。其中包括了應(yīng)用程序執(zhí)行時所必須的動態(tài)鏈接庫涛漂。
框架提供了程序運行的基本功能和GUI基礎(chǔ)慷彤。在這些基本功能之上,通過添加獨有的處理怖喻,就可以實現(xiàn)要實現(xiàn)的功能底哗。
在Mac OSX中,將開發(fā)和執(zhí)行軟件所必需的圖形庫锚沸、頭文件和設(shè)定用的各種信息全部匯總在一起就構(gòu)成了框架跋选。值得一提的是,其中包括了應(yīng)用程序執(zhí)行時所必須的動態(tài)鏈接庫哗蜈。
框架是應(yīng)用程序的骨架的意思前标。框架提供了程序運行的基本功能和GUI基礎(chǔ)距潘。在這些基本功能
之上炼列,通過添加獨有的處理,就可以實現(xiàn)要實現(xiàn)的功能音比。
Mac OSX中最重要的框架是Foundation框架俭尖、Application框架(也稱為Appkit框架或Application Kt框架)、Core Foundation框架和System框架洞翩。Foundation框架提供了包括NSObject在內(nèi)的Objective-C的基本類庫稽犁。Core Foundation框架是一組C語言接口,它們?yōu)閕OS應(yīng)用程序提供基本的數(shù)據(jù)管理和服務(wù)功能(詳情請參照附錄B)骚亿。Application框架包含了與Cocoa的GUI的基礎(chǔ)——窗口環(huán)境相關(guān)的類已亥。另外,System框架包含了與Cocoa最底層的Mach核心和Unix相關(guān)的類庫来屠。因為系統(tǒng)框架通常都和程序執(zhí)行相關(guān)虑椎,所以編譯程序的時候不需要指定-framewok選項震鹉。
還有一個Cocoa框架,實際上它是由Foundation框架捆姜、Application框架和CoreData框架組成的传趾。像這樣,將多個框架嵌套打包的技術(shù)稱為umbrella framework娇未。不過該技術(shù)僅僅是蘋果公司提供功能的一種方法墨缘,不建議普通開發(fā)者提供自己的umbrella framework星虹。
如上圖零抬,iOS中的Cocoa Touch由Foundation框架和UIKit框架構(gòu)成。iOS 的 Foundation框架和Mac OS X的Foundation框架有很大一部分是共用的宽涌,例如字符串或數(shù)組等的基本類平夜。UIKit是負(fù)責(zé)用戶接口的框架,其中定義了基本的GUI控件和用于處理觸摸屏幕之類的事件的類卸亮。另外忽妒,iOS可以通過利用Core Foundation框架中的數(shù)據(jù)結(jié)構(gòu)提供面向其他框架或設(shè)備的功能。
框架的構(gòu)成和頭文件:
框架的每個目錄通常包含以下要素:
類庫----框架同名文件
CodeResources----記錄了文件散列值的XML文件
Headers----包含了頭文件的目錄
Resources----包含了不同國家語言用的文件等各類資源等目錄
Versions----包含了框架的各種版本的目錄
#import<框架名/頭文件名>
全新的運行時系統(tǒng)
64位模型和整數(shù)類型:
數(shù)據(jù)類型發(fā)生了變化的話兼贸,主要受影響的是調(diào)用API時的參數(shù)和返回值的類型段直。
ILP32和LP64中的int類型都是32位,這種情況下溶诞,就算數(shù)據(jù)類型變更到了64位鸯檬,因為當(dāng)前的程序使用的整數(shù)類型還是32位,所以也不能利用到64位的優(yōu)點螺垢。為了解決這個問題喧务,Cocoa環(huán)境引入了NSInter類型,NSInteger在32位的數(shù)據(jù)模型下被定義為int枉圃,在64位數(shù)據(jù)模型下被定義位long功茴。除此之外,Cocoa中還定義了無符號的NSUInteger類型孽亲。
健壯的實例變量:
如果框架中的類發(fā)生了變化坎穿,應(yīng)用程序就一定得重新編譯才能夠在早期的運行時系統(tǒng)中繼續(xù)執(zhí)行。這稱為脆弱的二進制接口返劲。
現(xiàn)代運行時系統(tǒng)針對這個問題進行了改進赁酝,使實例變量發(fā)生變化的情況下,應(yīng)用程序不重新編譯也能夠繼續(xù)執(zhí)行旭等。這稱為健壯的實例變量酌呆。但實例變量的改變僅限于變量順序的變化或增加了新的實例變量這種程度,刪除了實例變量或改變了實例變量的類型時還是需要重新編譯搔耕。