8,類方法以及私有方法
本系列講座有著很強(qiáng)的前后相關(guān)性,如果你是第一次閱讀本篇文章,為了更好的理解本章內(nèi)容,筆者建議你最好從本系列講座的第1章開始閱讀,請(qǐng)點(diǎn)擊 這里 。
?
Objective-C里面區(qū)別于實(shí)例方法,和Java或者C++一樣,也支持類方法。類方法(Class Method) 有時(shí)被稱為工廠方法(Factory Method)或者方便方法(Convenience method)。工廠方法的稱謂明顯和一般意義上的工廠方法不同,從本質(zhì)上來(lái)說(shuō),類方法可以獨(dú)立于對(duì)象而執(zhí)行,所以在其他的語(yǔ)言里面類方法有的時(shí)候被稱為 靜態(tài)方法。就像@interface曾經(jīng)給我們帶來(lái)的混亂一樣,現(xiàn)在我們就不去追究和爭(zhēng)論工廠方法的問(wèn)題了,我們看到Objective-C的文章說(shuō)工廠 方法,就把它當(dāng)作類方法好了。
?
在Objective-C里面,最受大家歡迎的類方法應(yīng)該是alloc,我們需要使用alloc來(lái)為我們的對(duì)象分配內(nèi)存。可以想象,如果沒有alloc,我們將要如何來(lái)為我們的類分配內(nèi)存!
?
和其他的語(yǔ)言類似,下面是類方法的一些規(guī)則,請(qǐng)大家務(wù)必記住。
?
1,類方法可以調(diào)用類方法。
2,類方法不可以調(diào)用實(shí)例方法,但是類方法可以通過(guò)創(chuàng)建對(duì)象來(lái)訪問(wèn)實(shí)例方法。
3,類方法不可以使用實(shí)例變量。類方法可以使用self,因?yàn)閟elf不是實(shí)例變量。
4,類方法作為消息,可以被發(fā)送到類或者對(duì)象里面去(實(shí)際上,就是可以通過(guò)類或者對(duì)象調(diào)用類方法的意思)。
?
如果大家觀察一下Cocoa的類庫(kù),會(huì)發(fā)現(xiàn)類方法被大量的應(yīng)用于方便的對(duì)象創(chuàng)建和操作對(duì)象的,考慮到類方法的上述的特性,同學(xué)們?cè)谠O(shè)計(jì)自己的類的時(shí)候,為 了謀求這種方便,可以考慮使用類方法來(lái)創(chuàng)建或者操作對(duì)象。筆者認(rèn)為,這個(gè)就是類方法的潛規(guī)則,在本章的范例程序里面,筆者將要遵守這個(gè)潛規(guī)則。
?
在上一章我們講了一下實(shí)例變量的作用域,實(shí)例變量的作用域的方式和其他面向?qū)ο蟮恼Z(yǔ)言沒有什么不同。對(duì)于方法,非常遺憾的是,Objective- C并沒有為我們提供諸如public,private和protected這樣的限定,這就意味著在Objective-C里面,從理論上來(lái)說(shuō)所有的方法 都是公有的。但是,我們可以利用Objective-C的語(yǔ)言的特性,我們自己來(lái)實(shí)現(xiàn)方法的私有化。當(dāng)然我們自己的私有化手段沒有得到任何的編譯器的支 持,只是告訴使用者:“這是一個(gè)私有的方法,請(qǐng)不要使用這個(gè)方法”。所以,無(wú)論作為類的設(shè)計(jì)者和使用者都應(yīng)該清楚在Objective-C里面的方法私有 化的所有手段,這樣就在類的設(shè)計(jì)者和使用者之間達(dá)成了一種默契,這種方式明顯不是Objective-C語(yǔ)法所硬性規(guī)定的,所以也可以把這種手法成為一種 潛規(guī)則。
?
關(guān)于潛規(guī)則 經(jīng)常看英文文檔的同學(xué),應(yīng)該可以遇到這樣一個(gè)詞,de facto standard,也就是筆者所說(shuō)的潛規(guī)則。?
本章所述的方法的私有化是一種有缺陷的手段,有一定的風(fēng)險(xiǎn)而且也沒有完全實(shí)現(xiàn)私有化,在后面的章節(jié)里面筆者會(huì)陸續(xù)的給出其他的實(shí)現(xiàn)方法私有化的方法。
?
另外,Objective-C里面有一個(gè)其他不支持指針的語(yǔ)言沒有的一個(gè)動(dòng)態(tài)特性,那就是程序在執(zhí)行的時(shí)候,可以動(dòng)態(tài)的替換類的手段。動(dòng)態(tài)的方法替 換有很多種應(yīng)用,本章實(shí)現(xiàn)了一個(gè)類似java里面的final函數(shù)。和final函數(shù)不同的是,如果子類重寫了這個(gè)方法,編譯器不會(huì)報(bào)錯(cuò),但是執(zhí)行的時(shí)候 總是執(zhí)行的你的超類的方法。
?
類方法,方法私有化和動(dòng)態(tài)方法替換將是本章的主題。
8.1,本章程序的執(zhí)行結(jié)果
在本章里面,我們將要繼續(xù)使用我們?cè)诘?章已經(jīng)構(gòu)筑好的類Cattle和Bull。
?
筆者在這里暫時(shí)違反一下不修改已經(jīng)生效的代碼規(guī)則改寫了一下Cattle和Bull類,在里面追加了一些類方法,用于創(chuàng)建Cattle系列的對(duì)象。
?
筆者也改寫了Cattle的頭文件用來(lái)實(shí)現(xiàn)方法的私有化。
?
面向?qū)ο蟮某绦蛴幸粋€(gè)很大的特色就是動(dòng)態(tài)性,但是由于某種原因我們?cè)谠O(shè)計(jì)超類的時(shí)候,也許會(huì)考慮把某個(gè)方法設(shè)定成為靜態(tài)的,這樣就有了諸如 final的概念。在本章我們將要使用動(dòng)態(tài)的方法替換來(lái)實(shí)現(xiàn)這個(gè)功能。我們將要構(gòu)筑一個(gè)新類,名字叫做UnknownBull,我們使用動(dòng)態(tài)方法替換導(dǎo)致 即使UnknownBull重載了Cattle類的saySomething,但是向UnknownBull發(fā)送saySomething的時(shí)候,仍然執(zhí) 行的是Cattle的saySomething。本章程序的執(zhí)行結(jié)果請(qǐng)參照下圖:
?
圖8-1,本章程序的執(zhí)行結(jié)果。
?
本章程序可以點(diǎn)擊 這里 下載。
8.2,實(shí)現(xiàn)步驟
第一步,按照我們?cè)诘?章所述的方法,新建一個(gè)項(xiàng)目,項(xiàng)目的名字叫做07-InitWithAndIvarScope。如果你是第一次看本篇文章,請(qǐng)到 這里 參看第二章的內(nèi)容。
?
第二步,按照我們?cè)诘?章的4.2節(jié)的第二,三,四步所述的方法,把在第4章已經(jīng)使用過(guò)的“Cattle.h”,“Cattle.m”,“Bull.h”還有“Bull.m”, 導(dǎo)入本章的項(xiàng)目里面。
?
第三步,打開“Cattle.h”和“Cattle.m”,分別修改成為下面的代碼并且保存:
#import <Foundation/Foundation.h> @interface Cattle : NSObject { int legsCount; } - (void)saySomething; + (id) cattleWithLegsCountVersionA:(int) count; + (id) cattleWithLegsCountVersionB:(int) count; + (id) cattleWithLegsCountVersionC:(int) count; + (id) cattleWithLegsCountVersionD:(int) count; @end #import "Cattle.h" #import <objc/objc-class.h> @implementation Cattle -(void) saySomething { NSLog(@"Hello, I am a cattle, I have %d legs.", legsCount); } -(void) setLegsCount:(int) count { legsCount = count; } + (id) cattleWithLegsCountVersionA:(int) count { id ret = [[Cattle alloc] init]; //NEVER DO LIKE BELOW //legsCount = count; [ret setLegsCount:count]; return [ret autorelease]; } + (id) cattleWithLegsCountVersionB:(int) count { id ret = [[[Cattle alloc] init] autorelease]; [ret setLegsCount:count]; return ret; } + (id) cattleWithLegsCountVersionC:(int) count { id ret = [[self alloc] init]; [ret setLegsCount:count]; return [ret autorelease]; } + (id) cattleWithLegsCountVersionD:(int) count { id ret = [[self alloc] init]; [ret setLegsCount:count]; if([self class] == [Cattle class]) return [ret autorelease]; SEL sayName = @selector(saySomething); Method unknownSubClassSaySomething = class_getInstanceMethod([self class], sayName); //Change the subclass method is RUDE! Method cattleSaySomething = class_getInstanceMethod([Cattle class], sayName); //method_imp is deprecated since 10.5 unknownSubClassSaySomething->method_imp = cattleSaySomething->method_imp; return [ret autorelease]; } @end??
第四步,打開“Bull.h”和“Bull.m”,分別修改成為下面的代碼并且保存:
#import <Foundation/Foundation.h> #import "Cattle.h" @interface Bull : Cattle { NSString *skinColor; } - (void)saySomething; - (NSString*) getSkinColor; - (void) setSkinColor:(NSString *) color; + (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor; @end #import "Bull.h" @implementation Bull -(void) saySomething { NSLog(@"Hello, I am a %@ bull, I have %d legs.", [self getSkinColor],legsCount); } -(NSString*) getSkinColor { return skinColor; } - (void) setSkinColor:(NSString *) color { skinColor = color; } + (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor { id ret = [self cattleWithLegsCountVersionC:count]; [ret setSkinColor:theColor]; //DO NOT USE autorelease here! return ret; } @end??
第五步,創(chuàng)建一個(gè)新類,名字叫做“UnknownBull”,然后分別打開“UnknownBull.h”和“UnknownBull.m”,分別修改成為下面的代碼并且保存:
#import <Foundation/Foundation.h> #import "Bull.h" @interface UnknownBull : Bull { } -(void)saySomething; @end #import "UnknownBull.h" @implementation UnknownBull -(void)saySomething { NSLog(@"Hello, I am an unknown bull."); } @end
?
第六步,打開“08-Class_Method_And_Private_Method.m” ,修改成為下面的樣子并且保存
#import <Foundation/Foundation.h> #import "Cattle.h" #import "Bull.h" #import "UnknownBull.h" int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; id cattle[5]; cattle[0] = [Cattle cattleWithLegsCountVersionA:4]; cattle[1] = [Bull cattleWithLegsCountVersionB:4]; cattle[2] = [Bull cattleWithLegsCountVersionC:4]; cattle[3] = [Bull bullWithLegsCount:4 bullSkinColor:@"red"]; cattle[4] = [UnknownBull cattleWithLegsCountVersionD:4]; for(int i = 0 ; i < 5 ; i++) { [cattle[i] saySomething]; } [pool drain]; return 0; }??
第七步,選擇屏幕上方菜單里面的“Run”,然后選擇“Console”,打開了Console對(duì)話框之后,選擇對(duì)話框上部中央的“Build and Go”,如果不出什么意外的話,那么應(yīng)該出現(xiàn)入圖8-1所示的結(jié)果。如果出現(xiàn)了什么意外導(dǎo)致錯(cuò)誤的話,那么請(qǐng)仔細(xì)檢查一下你的代碼。如果經(jīng)過(guò)仔細(xì)檢查發(fā)現(xiàn) 還是不能執(zhí)行的話,可以到 這里 下載筆者為同學(xué)們準(zhǔn)備的代碼。 如果筆者的代碼還是不能執(zhí)行的話,請(qǐng)告知筆者。
8.2,方法的私有化
在講述方法私有化之前,我們首先要提到一個(gè)Objective-C里面的一個(gè)概念,動(dòng)態(tài)類型和靜態(tài)類型。
所謂的動(dòng)態(tài)類型,就是使用id來(lái)定義一個(gè)對(duì)象,比如說(shuō)
id cattle = [[Cattle alloc] init];?
所謂的靜態(tài)類型,就是使用已知變量的的類型來(lái)定義對(duì)象,比如說(shuō)
Cattle cattle = [[Cattle alloc] init];?
動(dòng)態(tài)類型和靜態(tài)類型各有好處,動(dòng)態(tài)類型實(shí)現(xiàn)了多態(tài)性,使用靜態(tài)類型的時(shí)候編譯器會(huì)為你檢查一下也許會(huì)出現(xiàn)危險(xiǎn)的地方,比如說(shuō)向一個(gè)靜態(tài)類型的對(duì)象發(fā)送一個(gè)它沒有定義的消息等等。
?
好的,我們現(xiàn)在打開“cattle.h”,大家可以發(fā)現(xiàn),和以前的版本相比,我們的“cattle.h”少了一個(gè)方法的定義,那就是-(void) setLegsCount:(int) count;。筆者在本章的范例程序里面實(shí)現(xiàn)私有方法的手段比較簡(jiǎn)單,直接把-(void) setLegsCount:(int) count從“cattle.h”給刪除掉了。
?
大家打開““cattle.m”,可以看到里面-(void) setLegsCount:(int) count是有實(shí)現(xiàn)部分的。實(shí)現(xiàn)部分和過(guò)去的版本沒有任何區(qū)別的。
?
我們本章里面講述的實(shí)現(xiàn)方法私有化的手段,就是從頭文件當(dāng)中不寫方法的聲明。這樣做會(huì)導(dǎo)致如下幾個(gè)現(xiàn)象
?
1,在類的實(shí)現(xiàn)文件.m里面,你可以向平常一樣使用[self setLegsCount:4] 來(lái)發(fā)送消息,但是確省設(shè)定的編譯器會(huì)很不禮貌的給你一個(gè)警告。
?
2,你可以向Cattle以及從Cattle繼承的類的靜態(tài)對(duì)象發(fā)送setLegsCount:4的消息,但是同樣,確省設(shè)定的編譯器會(huì)很不禮貌的給你一個(gè)警告。
?
3,你可以向Cattle以及從Cattle繼承的類的動(dòng)態(tài)對(duì)象發(fā)送setLegsCount:4的消息,編譯器不會(huì)向你發(fā)送任何警告的。
說(shuō)到這里,同學(xué)們也許會(huì)覺得這一節(jié)的方法私有化有一點(diǎn)奇怪,因?yàn)樵谏厦娴牡诙l里面,不能阻止對(duì)對(duì)象的私有方法進(jìn)行調(diào)用。令我們更為惱火的是,居然在我們自己的類的實(shí)現(xiàn)文件里面需要調(diào)用的時(shí)候產(chǎn)生諸如第一條的警告!
?
讓我們冷靜一下。
?
我們說(shuō),在面向?qū)ο蟮某绦蚶锩妫话愣灶惖氖褂谜咧魂P(guān)心接口,不關(guān)心實(shí)現(xiàn)的。當(dāng)我們類的實(shí)現(xiàn)部分的某個(gè)方法,在頭文件里面沒有定義的話,那么由于 我們的類的使用者只是看頭文件,所以他不應(yīng)該是用我們定義的所謂的私有方法的。這一點(diǎn),對(duì)于其他的語(yǔ)言來(lái)說(shuō)也是一樣的,其他的語(yǔ)言的私有方法和變量,如果 我們把它們改為public,或者我們不修改頭文件,使用指針也可以強(qiáng)行的訪問(wèn)到私有的變量和方法的,從這個(gè)角度上來(lái)說(shuō),私有化的方法和變量也只不過(guò)是一 個(gè)擺設(shè)而已,沒有人可以阻止我們?nèi)ピL問(wèn)他們,探求埋藏在里面的奧秘。所謂的私有化只不過(guò)是一個(gè)潛規(guī)則而已,在正常的時(shí)候,我們大家都會(huì)遵守這個(gè)潛規(guī)則的。 但是被逼無(wú)奈走投無(wú)路的時(shí)候我們也許會(huì)除了訪問(wèn)私有的東西無(wú)可選擇。但是也不能過(guò)分,我們顯然不可以把訪問(wèn)私有變量和函數(shù)當(dāng)作一種樂趣。
?
說(shuō)到這里,我想大家應(yīng)該可以理解這種私有化方法的定義了。它只不過(guò)是一種信號(hào),告訴類的使用者,“這是一個(gè)私有的函數(shù),請(qǐng)不要使用它,否則后果自 負(fù)” 。我們?cè)诳吹絼e人的代碼的時(shí)候看到了這種寫法的時(shí)候,或者別人看到我們的代碼的時(shí)候,大家都需要做到相互理解對(duì)方的隱藏私有部分的意圖。還是還是這句話, 在大多數(shù)時(shí)候,請(qǐng)不要破壞潛規(guī)則。
8.3, 類方法
我們現(xiàn)在轉(zhuǎn)到本章最重要的主題,類方法。我們將要首先關(guān)注一下類方法的聲明,現(xiàn)在請(qǐng)同學(xué)們打開"Cattle.h"文件,可以發(fā)現(xiàn)下面的代碼:
+ (id) cattleWithLegsCountVersionA:(int) count; + (id) cattleWithLegsCountVersionB:(int) count; + (id) cattleWithLegsCountVersionC:(int) count; + (id) cattleWithLegsCountVersionD:(int) count;?
類方法和實(shí)例方法在聲明上的唯一的區(qū)別就是,以加號(hào)+為開始,其余的部分是完全一致的。 筆者在這里定義了4個(gè)不同版本的類方法,從功能上來(lái)說(shuō)都是用來(lái)返回Cattle類或者其子類的對(duì)象的,其中 cattleWithLegsCountVersionA到C是我們這一節(jié)講解的重點(diǎn)。
?
讓我們首先打開“Cattle.m” ,關(guān)注一下下面的代碼:
+ (id) cattleWithLegsCountVersionA:(int) count { id ret = [[Cattle alloc] init]; //NEVER DO LIKE BELOW //legsCount = count; [ret setLegsCount:count]; return [ret autorelease]; } + (id) cattleWithLegsCountVersionB:(int) count { id ret = [[[Cattle alloc] init] autorelease]; [ret setLegsCount:count]; return ret; }?
我們需要使用類方法創(chuàng)建對(duì)象,所以在第3行,我們使用了我們比較熟悉的對(duì)象的創(chuàng)建的方法創(chuàng)建了一個(gè)對(duì)象。大家注意一下第5行,由于類方法是和對(duì)象是 脫離的所以我們是無(wú)法在類方法里面使用實(shí)例變量的。第6行,由于我們創(chuàng)建了對(duì)象ret,所以我們可以向ret發(fā)送setLegsCount:這個(gè)消息,我 們通過(guò)這個(gè)消息,設(shè)定了Cattle的legsCount實(shí)例變量。在第7行,我們遇到了一個(gè)新的朋友,autorelease。我們?cè)陬惙椒ɡ锩鎰?chuàng)建了 一個(gè)對(duì)象,當(dāng)我們返回了這個(gè)對(duì)象之后,類方法也隨之結(jié)束,類方法結(jié)束就意味著在我們寫的類方法里面,我們失去了對(duì)這個(gè)對(duì)象的參照,也就永遠(yuǎn)無(wú)法在類方法里 面控制這個(gè)對(duì)象了。在Objective-C里面有一個(gè)規(guī)則,就是誰(shuí)創(chuàng)建的對(duì)象,那么誰(shuí)就有負(fù)責(zé)管理這個(gè)對(duì)象的責(zé)任,類方法結(jié)束之后,除非和類的使用者商 量好了讓類的使用者釋放內(nèi)存,否則我們無(wú)法直接的控制這個(gè)過(guò)程。
?
記憶力好的同學(xué)應(yīng)該可以回憶起來(lái),筆者曾經(jīng)在第二章提到過(guò)一種延遲釋放內(nèi)存的技術(shù),這個(gè)就是autorelease。關(guān)于autorelease以 及其他的內(nèi)存管理方法,我們將在下一章放到一起講解。到這里大家記住,使用類方法的潛規(guī)則是你要使用類方法操作對(duì)象,當(dāng)你需要使用類方法創(chuàng)建一個(gè)對(duì)象的時(shí) 候,那么請(qǐng)?jiān)陬惙椒ɡ锩婕由蟖utorelease。
?
我們來(lái)看看cattleWithLegsCountVersionB的實(shí)現(xiàn)部分的代碼,和cattleWithLegsCountVersionA 唯一區(qū)別就是我們?cè)趧?chuàng)建的時(shí)候就直接的加上了autorelease。這樣符合創(chuàng)建對(duì)象的時(shí)候“一口氣”的把所有需要的方法都寫到一起的習(xí)慣,采取什么方 式取決于個(gè)人喜好。
?
我們?cè)俅蜷_“08-Class_Method_And_Private_Method.m”,參看下面的代碼
cattle[0] = [Cattle cattleWithLegsCountVersionA:4]; cattle[1] = [Bull cattleWithLegsCountVersionB:4];??
我們?cè)诨仡^看看本章程序的執(zhí)行結(jié)果,心細(xì)的同學(xué)也許發(fā)現(xiàn)了一個(gè)很嚴(yán)重的問(wèn)題,我們?cè)诘?行代碼里面想要返回一個(gè)Bull的對(duì)象,但是輸出的時(shí)候卻變 成了Cattle,原因就是我們?cè)赾attleWithLegsCountVersionB里面創(chuàng)建對(duì)象的時(shí)候,使用了id ret = [[[Cattle alloc] init] autorelease]。由于Bull里面沒有重寫cattleWithLegsCountVersionB,所以除非我們重寫 cattleWithLegsCountVersionB否則我們向Bull發(fā)送cattleWithLegsCountVersionB這個(gè)類方法的時(shí) 候,只能得到一個(gè)Cattle的對(duì)象。我們可以要求我們的子類的設(shè)計(jì)者在他們的子類當(dāng)中重寫cattleWithLegsCountVersionB,但 是這樣明顯非常笨拙,失去了動(dòng)態(tài)的特性。我們當(dāng)然有辦法解決這個(gè)問(wèn)題,現(xiàn)在請(qǐng)大家回到“Cattle.m”,參照下列代碼:
+ (id) cattleWithLegsCountVersionC:(int) count { id ret = [[self alloc] init]; [ret setLegsCount:count]; return [ret autorelease]; }??
我們的解決方案就在第3行,我們不是用靜態(tài)的Cattle,而是使用self。說(shuō)到這里也許大家有些糊涂了,在其他的語(yǔ)言當(dāng)中和self比較類似的 是this指針,但是在Objective-C里面self和this有些不大一樣,在類函數(shù)里面的self實(shí)際上就是這個(gè)類本身。大家可以打開 debugger觀察一下,self的地址就是Bull的Class的地址。所以程序執(zhí)行到上面的代碼的第3行的時(shí)候,實(shí)際上就等同于id ret = [[[Bull class] alloc] init];
?
我們可以在類方法里面使用self,我們可否通過(guò)使用self->legsCount來(lái)訪問(wèn)實(shí)例變量呢?答案是不可以,因?yàn)樵谶@個(gè)時(shí)候?qū)ο鬀]有被創(chuàng)建也就是說(shuō),沒有為legsCount分配內(nèi)存,所以無(wú)法訪問(wèn)legsCount。
?
由于Bull類在程序被調(diào)入內(nèi)存的時(shí)候就已經(jīng)初始化好了,Bull類里面的實(shí)例函數(shù)應(yīng)該被放到了代碼段,所以從理論上來(lái)說(shuō),我們可以通過(guò)使用 [self setLegsCount:count]來(lái)調(diào)用實(shí)例方法的,但是不幸的是Objective-C沒有允許我們這樣做,我們?cè)陬惙椒ㄖ惺褂胹elf來(lái)作為消 息的接收者的時(shí)候,消息總是被翻譯成為類方法,如果發(fā)送實(shí)例方法的消息的話,會(huì)在執(zhí)行的時(shí)候找不到從而產(chǎn)生異常。這樣做是有一定的道理的,因?yàn)橐话愣裕?實(shí)例方法里面難免要使用實(shí)例變量,在類方法當(dāng)中允許使用實(shí)例方法,實(shí)際上也就允許使用實(shí)例變量。
?
關(guān)于self 大家需要記住下面的規(guī)則:1,實(shí)例方法里面的self,是對(duì)象的首地址。
2,類方法里面的self,是Class.
盡管在同一個(gè)類里面的使用self,但是self卻有著不同的解讀。在類方法里面的self,可以翻譯成class self;在實(shí)例方法里面的self,應(yīng)該被翻譯成為object self。在類方法里面的self和實(shí)例方法里面的self有著本質(zhì)上的不同,盡管他們的名字都叫self。
?
請(qǐng)同學(xué)們?cè)俅位氐綀D8-1,可以發(fā)現(xiàn)通過(guò)使用神奇的self,我們動(dòng)態(tài)的創(chuàng)建了Bull類的對(duì)象。但是等一下,我們的程序并不完美,因?yàn)锽ull類 的skinColor并沒有得到初始化,所以導(dǎo)致了null的出現(xiàn)。我們?cè)谠O(shè)計(jì)Cattle類也就是Bull的超類的時(shí)候,明顯我們無(wú)法預(yù)測(cè)到Bull類 的特征。消除這種問(wèn)題,我們可以在得到了Bull對(duì)象之后使用setSkinColor:來(lái)設(shè)定顏色,當(dāng)然我們也可以直接寫一個(gè)Bull類的方法,來(lái)封裝 這個(gè)操作,請(qǐng)同學(xué)們打開“Bull.h”:
?
我們追加了一個(gè)類方法, bullWithLegsCount:bullSkinColor:用于創(chuàng)建Bull對(duì)象,請(qǐng)同學(xué)們打開“Bull.m”:
?
+ (id) bullWithLegsCount:(int) count bullSkinColor:(NSString*) theColor { id ret = [self cattleWithLegsCountVersionC:count]; [ret setSkinColor:theColor]; //DO NOT USE autorelease here! return ret; }??
上面這一段代碼相信大家都可以看明白,筆者就不在這里贅述了。但是筆者需要強(qiáng)調(diào)一點(diǎn),在這里我們不需要調(diào)用autorelease的,因?yàn)槲覀儧]有在這里創(chuàng)建任何對(duì)象。
?
經(jīng)過(guò)了這個(gè)改造,通過(guò)在“08-Class_Method_And_Private_Method.m”里面我們使用
?
?
使得我們的代碼終于正常了,請(qǐng)參照?qǐng)D8-1的第4行輸出。
8.4,使用動(dòng)態(tài)方法替換實(shí)現(xiàn)final功能
首先請(qǐng)同學(xué)們打開“Cattle.m”,參照下面的代碼片斷:
+ (id) cattleWithLegsCountVersionD:(int) count { id ret = [[self alloc] init]; [ret setLegsCount:count]; if([self class] == [Cattle class]) return [ret autorelease]; SEL sayName = @selector(saySomething); Method unknownSubClassSaySomething = class_getInstanceMethod([self class], sayName); //Change the subclass method is RUDE! Method cattleSaySomething = class_getInstanceMethod([Cattle class], sayName); //method_imp is deprecated since 10.5 unknownSubClassSaySomething->method_imp = cattleSaySomething->method_imp; return [ret autorelease]; } @end?
在cattleWithLegsCountVersionD里面,我們將要通過(guò)使用動(dòng)態(tài)的方法替換技術(shù)來(lái)實(shí)現(xiàn)final方法。
第3,4行代碼,是用于創(chuàng)建Cattle或者從Cattle類繼承的對(duì)象,并且設(shè)定實(shí)例變量legsCount。
第6,7行代碼,是用來(lái)判斷調(diào)用這個(gè)類方法的self是不是cattle,如果是cattle的話,那么就直接返回,因?yàn)槲覀円谶@個(gè)方法里面把子 類的saySomething替換成為Cattle的saySomething,如果類是Cattle的話,那么很明顯,我們不需要做什么事情的。
第9行代碼是老朋友了,我們需要得到方法的SEL。
?
第10行和第12行,我們需要通過(guò)Objective-C的一個(gè)底層函數(shù),class_getInstanceMethod來(lái)取得方法的數(shù)據(jù)結(jié)構(gòu) Method。讓我們把鼠標(biāo)移動(dòng)到Method關(guān)鍵字上面,點(diǎn)擊鼠標(biāo)右鍵盤,選擇“Jump to definition”,我們可以看到在文件“objc-class.h”里面的Method的定義。Method實(shí)際上是類方法在Class里面的數(shù)據(jù) 結(jié)構(gòu),系統(tǒng)會(huì)使用Method的信息來(lái)構(gòu)筑Class的信息。在Method類型的聲明里面,我們看到了下面的代碼
typedef struct objc_method *Method; struct objc_method { SEL method_name; char *method_types; IMP method_imp; };??
其中SEL和IMP我們已經(jīng)很熟悉了, method_types是方法的類型信息,Objective-C使用一些預(yù)定義的宏來(lái)表示方法的類型,然后把這些信息放到 method_types里面。
?
需要強(qiáng)調(diào)的是,蘋果在10.5之后就降級(jí)了很多Objective-C 底層的函數(shù),并且在64位的應(yīng)用當(dāng)中使得這些函數(shù)失效,筆者對(duì)剝奪了眾多程序員的自由而感到遺憾。
?
第14行的代碼,我們把子類的函數(shù)指針的地址替換成為Cattle類的saySomething,這樣無(wú)論子類是否重寫saySomething, 執(zhí)行的時(shí)候由于runtime需要找到方法的入口地址,但是這個(gè)地址總是被我們替換為Cattle的saySomething,所以子類通過(guò) cattleWithLegsCountVersionD取得對(duì)象之后,總是調(diào)用的Cattle的saySomething,也就實(shí)現(xiàn)了final。當(dāng) 然,這種方法有些粗魯,我們強(qiáng)行的不顧后果的替換了子類的重寫。
?
重要 本節(jié)提到的final的實(shí)現(xiàn)方法,沒有任何蘋果官方的文檔建議這樣做,純屬筆者自創(chuàng)僅供大家參考,如果使用風(fēng)險(xiǎn)自擔(dān)。?
替換的結(jié)果,就是雖然我們?cè)凇?8-Class_Method_And_Private_Method.m”里面的cattle[4]l里面使用 UnknownBull是圖返回UnknownBull對(duì)象,我們也確實(shí)得到了UnknownBull對(duì)象,但是不同的是,我們?cè)?cattleWithLegsCountVersionD里面貍貓換太子,把UnknownBull的saySomething變成了Cattle的 saySomething。
?
讓我們回到圖8-1,我們發(fā)現(xiàn)最后一行的輸出為Cattle的saySomething。
?
關(guān)于final的實(shí)現(xiàn)方式,我們當(dāng)然可以使用一個(gè)文明的方法來(lái)告知子類的使用者,我們不想讓某個(gè)方法被重寫。我們只需要定義一個(gè)宏
?
類的使用者看到這個(gè)FINAL之后,筆者相信在絕大多數(shù)時(shí)候,他會(huì)很配合你不會(huì)重寫帶FINAL定義的方法的。
8.5,本章總結(jié)
我們?cè)诒菊吕锩嬷v述了方法私有化,類方法的定義和使用,動(dòng)態(tài)方法替換等技術(shù)手段,也給大家強(qiáng)調(diào)和澄清了self的概念。
更重要的是,筆者向大家介紹了一些潛規(guī)則,希望大家可以遵守。
非常感謝大家這些天對(duì)我的鼓勵(lì)以及支持!
?
來(lái)源: http://www.cnblogs.com/yaski/archive/2009/04/29/1444035.html
?
?
?
前置加號(hào)(+)的方法為類方法,這類方法是可以直接用類名來(lái)調(diào)用的,它的作用主要是創(chuàng)建一個(gè)實(shí)例。
前置減號(hào)(-)的方法為實(shí)例方法,必須使用類的實(shí)例才可以調(diào)用的。
?
?
?
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
