在有些時(shí)候仔沿,我們希望能夠用一些已經(jīng)封裝好的類,但又希望能夠加點(diǎn)自己想要的功能榆浓,但是很明顯于未,繼承并不適用于一些工具包或者類庫(kù),如NSString陡鹃,如果我們要給NSString加一個(gè)獲得長(zhǎng)度的方法該怎么辦呢烘浦?這時(shí)候就要用到“類別”了。
零.寫(xiě)在前面:類別與繼承
類別可以拓展一個(gè)類并添加額外的方法萍鲸,使得在不修改該類原先代碼的情況下闷叉,拓展或者修改現(xiàn)有類的定義,并且是向下有效的脊阴,會(huì)影響到該類的所有子類握侧。
重寫(xiě)一個(gè)類的方式用繼承還是分類取決于具體情況。
- 假如目標(biāo)類有許多的子類嘿期,我們需要拓展這個(gè)類又不希望影響到原有的代碼品擎,繼承比較好。
- 如果僅僅是拓展方法备徐,分類更好萄传。(不需要涉及到原先的代碼)
2.分類:用來(lái)擴(kuò)展類的方法,不能定義新成員蜜猾,但是可以訪問(wèn)到私有成員
子類:可以通過(guò)覆蓋和定義新方法來(lái)擴(kuò)展父類秀菱,可以新增成員,但是不能訪問(wèn)父類的私有成員蹭睡。
一.類別的創(chuàng)建與實(shí)現(xiàn)
1.創(chuàng)建
首先我們要聲明一下"NSString"的類別衍菱,注意在NSString后面加個(gè)括號(hào),只要保證類別名稱的唯一性肩豁,可以向一個(gè)類中添加任意多的類別:
//NSString.h
#import <Foundation/Foundation.h>
@interface NSString(NumberConvenience)
-(NSNumber *) lengthAsNumber;
@end
//NSString.m
#import "NSString.h"
@implementation NSString (NumberConvenience)
- (NSNumber *) lengthAsNumber
{
unsigned int length = (int) [self length];
return [NSNumber numberWithUnsignedInt: length];
}
@end
2.實(shí)現(xiàn)
在Main.m里面調(diào)用該NSString:
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
[dict setObject: [@"hello" lengthAsNumber] forKey:@"hello"];
[dict setObject: [@"Hoben" lengthAsNumber] forKey:@"Hoben"];
NSLog(@"%@", dict);
可以看到脊串,我們可以直接在NSString類后面添加lengthAsNumber方法。
3.局限性
- 無(wú)法向類中添加新的實(shí)例變量蓖救,即類別沒(méi)有位置容納實(shí)例變量洪规。
- 當(dāng)類別中的方法與現(xiàn)有的方法重名的時(shí)候,類別會(huì)具有更高的優(yōu)先級(jí)循捺,也就是說(shuō)斩例,我們創(chuàng)建的類別方法會(huì)完全取代初始方法,從而無(wú)法再使用初始方法从橘。(解決:在類別方法名中加一個(gè)前綴念赶,確保不發(fā)生名稱沖突础钠。)
二.類別的分散實(shí)現(xiàn)
我們可以將類別用于分散上面,即:將類的實(shí)現(xiàn)分散到多個(gè)不同文件或多個(gè)不同框架中叉谜,為什么要這樣做旗吁?舉個(gè)例子,NSString是Foundation框架中的一個(gè)類停局,包含了許多面向數(shù)據(jù)的類很钓,但APPKit也有一個(gè)NSString的類別,稱為NSStringDrawing董栽,該類別允許我們向字符串對(duì)象發(fā)送繪圖信息码倦。Cocoa的設(shè)計(jì)人員使用類別將數(shù)據(jù)功能放在Foundation中實(shí)現(xiàn),二將繪圖功能放在APPKit實(shí)現(xiàn)锭碳。作為編程人員袁稽,我們只需處理NSString即可,通常不用關(guān)心特定的方法來(lái)自何處擒抛。
1.寫(xiě)在前面的注意事項(xiàng)
分類可以擴(kuò)展類的方法推汽,但是不能新創(chuàng)建類的對(duì)象,也就是說(shuō)歧沪,用了分類之后歹撒,一切要使用的對(duì)象只能在父類中定義。(在本次實(shí)驗(yàn)中我遇到了@synthesize not allowed in a category's implementation的報(bào)錯(cuò)诊胞,所以特地提出來(lái)說(shuō)一下)
2.實(shí)現(xiàn)
這次的實(shí)驗(yàn)我們將CategoryThing分為Thing1栈妆、Thing2、Thing3來(lái)實(shí)現(xiàn)厢钧。
//CategoryThing.h
#import <Foundation/Foundation.h>
@interface CategoryThing : NSObject
@property (nonatomic, assign) int thing1;
@property (nonatomic, assign) int thing2;
@property (nonatomic, assign) int thing3;
@end
//CategoryThing.m
#import "CategoryThing.h"
@implementation CategoryThing
- (NSString *)description
{
return [NSString stringWithFormat:@"%d %d %d",
thing1,
thing2,
thing3];
}
@end
再用不同的類別寫(xiě)CategoryThing的get和set方法:
#import "CategoryThing.h"
@interface CategoryThing (Thing1)
- (void) setThing1: (int) thing1;
- (int) thing1;
@end
...//還有implementation
最后在main.m文件實(shí)現(xiàn):
CategoryThing *thing = [[CategoryThing alloc] init];
[thing setThing1: 3];
[thing setThing2: 5];
[thing setThing3: 7];
三.委托
protocol-協(xié)議,就是使用了這個(gè)協(xié)議后就要按照這個(gè)協(xié)議來(lái)辦事嬉橙,協(xié)議要求實(shí)現(xiàn)的方法就一定要實(shí)現(xiàn)早直。
delegate-委托,顧名思義就是委托別人辦事市框,就是當(dāng)一件事情發(fā)生后霞扬,自己不處理,讓別人來(lái)處理枫振。
當(dāng)一個(gè)A view 里面包含了B view
B view需要修改A view界面喻圃,那么這個(gè)時(shí)候就需要用到委托了。
需要幾個(gè)步驟:
1)首先定一個(gè)協(xié)議
2)A view實(shí)現(xiàn)協(xié)議中的方法
3)B view設(shè)置一個(gè)委托變量
4)把B view的委托變量設(shè)置成A view粪滤,意思就是斧拍,B view委托A view辦事情。
5)事件發(fā)生后杖小,用委托變量調(diào)用A view中的協(xié)議方法肆汹。
委托是一種對(duì)象愚墓,另一個(gè)類的對(duì)象會(huì)要求委托對(duì)象執(zhí)行它的某些操作。例如昂勉,當(dāng)APPKit類的NSApplication啟動(dòng)時(shí)浪册,他會(huì)詢問(wèn)其委托對(duì)象是否應(yīng)該打開(kāi)一個(gè)無(wú)標(biāo)題窗口。NSWindow類的對(duì)象詢問(wèn)它們自己的委托對(duì)象是否應(yīng)該允許關(guān)閉某個(gè)窗口岗照。
更常用的是村象,編寫(xiě)委托對(duì)象并將其提供給其他一些對(duì)象,通常是提供給Cocoa生成的對(duì)象攒至。通過(guò)實(shí)現(xiàn)特定的方法厚者,可以控制Cocoa中的對(duì)象的行為。
Cocoa中的滾動(dòng)列表是由APPKit類的NSTableView處理的嗓袱。當(dāng)tableView對(duì)象準(zhǔn)備好執(zhí)行某些操作(例如選擇用戶剛剛點(diǎn)擊的行)時(shí)籍救,它詢問(wèn)其委托對(duì)象是否選擇此行。tableView對(duì)象給其委托對(duì)象發(fā)送一條消息:
- (BOOL) tableView: (NSTableView *) tableView
shouldSelectRow: (int) row;
委托方法可以查看tableView對(duì)象和行并確定是否應(yīng)該選擇該行渠抹。如果表中包含了不該選擇的行蝙昙,則委托對(duì)象可能禁用那些不被選擇的行。
現(xiàn)在以iTunesFinder項(xiàng)目為例梧却,告訴網(wǎng)絡(luò)服務(wù)瀏覽器你期待的服務(wù)并為其提供一個(gè)委托對(duì)象奇颠。然后,該瀏覽器對(duì)象將向委托對(duì)象發(fā)送消息放航,告知其發(fā)現(xiàn)新服務(wù)的時(shí)間烈拒。具體代碼如注釋所示。
NSNetServiceBrowser *browser;
browser = [[NSNetServiceBrowser alloc] init];
ITunesFinder *finder;
finder = [[ITunesFinder alloc] init];
//告訴網(wǎng)絡(luò)服務(wù)瀏覽器广鳍,使用ITunesFinder類的對(duì)象作為雙親委托
[browser setDelegate: finder];
//"_daap._tcp"告訴網(wǎng)絡(luò)服務(wù)瀏覽器使用TCP網(wǎng)絡(luò)協(xié)議去搜索DAAP(數(shù)字音頻訪問(wèn)協(xié)議)
//類型的服務(wù)荆几。該語(yǔ)句可以找出由iTunes發(fā)布的庫(kù)。
//域local.表示只在本地網(wǎng)絡(luò)中搜索該服務(wù)赊时。
[browser searchForServicesOfType: @"_daap._tcp"
inDomain: @"local."];
NSLog(@"begun browsing");
//run循環(huán)會(huì)一直處于阻塞狀態(tài)(不執(zhí)行任何處理)吨铸,直到某些有趣的事情發(fā)生為止。
//在本例中祖秒,有趣的事情是指網(wǎng)絡(luò)服務(wù)瀏覽器發(fā)現(xiàn)了新的iTunes共享诞吱。
[[NSRunLoop currentRunLoop] run];
在ITunesFinder類中,我們并不需要在@interface中聲明方法竭缝。要成為一個(gè)委托對(duì)象房维,我們只需實(shí)現(xiàn)已經(jīng)打算調(diào)用的方法。
//ITunesFinder.h
#import <Foundation/Foundation.h>
//這里還是要聲明一下該類是NSNetServiceBrowser的委托抬纸,否則會(huì)有warning
@interface ITunesFinder : NSObject <NSNetServiceBrowserDelegate>
@end
//ITunesFinder.m
@implementation ITunesFinder
-(void) netServiceBrowser: (NSNetServiceBrowser *) b
didFindService:(nonnull NSNetService *)service
moreComing:(BOOL)moreComing
{
//當(dāng)NSNetServiceBrowser發(fā)現(xiàn)新服務(wù)時(shí)咙俩,他給委托對(duì)象發(fā)送netServiceBrowser:didFindService:moreComing:消息
//瀏覽器被作為第一個(gè)參數(shù)(與main()函數(shù)中Browser變量的值相同)傳遞
//如果有多個(gè)服務(wù)瀏覽器在同時(shí)進(jìn)行搜索,可以利用參數(shù)檢查計(jì)算出哪個(gè)瀏覽器發(fā)現(xiàn)了新服務(wù)松却。
//NSNetService描述了被發(fā)現(xiàn)的服務(wù)(如ITunes共享)
//moreComing用于指示一批通知是否已經(jīng)完成暴浦。
[service resolveWithTimeout: 10];
NSLog(@"found one! Name is %@", [service name]);
}
-(void) netServiceBrowser: (NSNetServiceBrowser *) b
didRemoveService:(nonnull NSNetService *) service
moreComing:(BOOL)moreComing
{
//在某個(gè)服務(wù)不可再用的時(shí)候被調(diào)用溅话。
[service resolveWithTimeout: 10];
NSLog(@"lost one! Name is %@", [service name]);
}
@end
四.委托和類別
被發(fā)送給委托對(duì)象的方法可以聲明為一個(gè)NSObject的類別,如NSNetService委托方法的部分聲明如下:
#import <Foundation/Foundation.h>
@interface NSObject(NSNetServiceBrowserDelegateMethods)
- (void) netServiceBrowserWillSearch:
(NSNetServiceBrowser *) browser;
- (void) netServiceBrowser: (NSNetServiceBrowser *) aNetServiceBrowser
didFindService: (nonnull NSNetService *)service
moreComing: (BOOL)moreComing;
- (void) netServiceBrowserDidStopSearch:
(NSNetServiceBrowser *)browser;
- (void) netServiceBrowser: (NSNetServiceBrowser *) browser
didRemoveService: (nonnull NSNetService *)service
moreComing: (BOOL)moreComing;
@end
通過(guò)將這些方法聲明為NSObject的類別歌焦,NSNetServiceBrowser的實(shí)現(xiàn)可以將這些消息之一發(fā)送給任何對(duì)象飞几,無(wú)論這些對(duì)象實(shí)際上屬于哪個(gè)類。這也意味著独撇,只要對(duì)象實(shí)現(xiàn)了委托方法屑墨,任何類的對(duì)象都可以成為委托對(duì)象。
創(chuàng)建一個(gè)NSObject的類別成為“創(chuàng)建一個(gè)非正式協(xié)議”纷铣,即實(shí)現(xiàn)自己可能希望實(shí)現(xiàn)的方法卵史,使用它們更好地完成工作。之后如果想使用該方法搜立,則在main.m中綁定委托對(duì)象以躯,再在委托對(duì)象所在的類實(shí)現(xiàn)該方法,即可委托完成啄踊。
五.響應(yīng)選擇器
當(dāng)NSNetServiceBrowser試圖發(fā)送一個(gè)對(duì)象無(wú)法理解的消息的時(shí)候忧设,就會(huì)發(fā)生OC的運(yùn)行時(shí)錯(cuò)誤:selector not recognized。
解決:NSNetServiceBrowser首先檢查對(duì)象颠通,詢問(wèn)其能否響應(yīng)該選擇器址晕。如果對(duì)象能夠響應(yīng)該選擇器,NSNetServiceBrowser則給他發(fā)送消息顿锰。
如:
if ([browser respondsToSelector:
@selector(netServiceBrowser:didFindService:moreComing:)])
如果該委托對(duì)象能夠響應(yīng)給定的消息谨垃,則瀏覽器向該對(duì)象發(fā)送此消息。否則硼控,瀏覽器將暫時(shí)忽略該委托對(duì)象刘陶,繼續(xù)正常運(yùn)行。
六.協(xié)議
正式協(xié)議是一個(gè)命名的方法列表牢撼。但與非正式協(xié)議不同的是易核,正式協(xié)議要求顯式地采用協(xié)議。即在類的@interface聲明中列出協(xié)議的名稱浪默。采用協(xié)議意味著承諾實(shí)現(xiàn)該協(xié)議的所有方法。否則缀匕,編譯器將會(huì)生成警告纳决。
1.聲明協(xié)議
我們聲明一個(gè)協(xié)議叫做NSCopying,如果采用該協(xié)議乡小,則對(duì)象將知道如何復(fù)制自己:
@protocol NSCopying
- (id) copyWithZone: (NSZone *) zone;
@end
這和interface有點(diǎn)像阔加,但區(qū)別在于:@protocol告訴編譯器:“下面將是一個(gè)新的正式協(xié)議”,@protocol之后是協(xié)議名稱满钟,協(xié)議名稱必須唯一胜榔。
2.復(fù)制
copy消息通知對(duì)象創(chuàng)建一個(gè)全新的對(duì)象胳喷,并使新對(duì)象與接收copy消息的原對(duì)象一樣。下面進(jìn)行Car的復(fù)制實(shí)驗(yàn):
//Engine.h
@interface Engine : NSObject <NSCopying>
//Engine.m
- (id) copyWithZone:(NSZone *)zone
{
Engine *engineCopy;
//首先通過(guò)[self class]獲得self對(duì)象所屬的類
//然后通過(guò)allocWithZone分配內(nèi)存并創(chuàng)建一個(gè)該類的新對(duì)象
//最后給新對(duì)象發(fā)送init消息使其初始化
engineCopy = [[[self class] allocWithZone: zone] init];
return engineCopy;
}
//Tire.h
@interface Tire : NSObject <NSCopying>
//Tire.m
- (id) copyWithZone:(NSZone *)zone
{
Tire *tireCopy;
tireCopy = [[[self class]
allocWithZone: zone]
initPressure: _pressure andTreadDepth:_treadDepth];
return tireCopy;
}
實(shí)現(xiàn)繼承的類的時(shí)候夭织,無(wú)需聲明<NSCopying>吭露,可以直接調(diào)用父類的方法。
//AllWeatherRadial.m
- (id) copyWithZone:(NSZone *)zone
{
AllWeatherRadial *tireCopy;
tireCopy = [super copyWithZone: zone];
[tireCopy setRainHandling: _rainHandling];
[tireCopy setSnowHandling: _snowHandling];
return tireCopy;
}
復(fù)制Car本身:
- (id) copyWithZone:(NSZone *)zone
{
Car *carCopy;
carCopy = [[[self class] allocWithZone: zone] init];
carCopy.name = self.name;
Engine *engineCopy = [engine copy];
carCopy.engine = engineCopy;
for (int i = 0; i < 4; i++) {
Tire *tireCopy;
tireCopy = [[self tireAtIndex: i] copy];
[carCopy setTire: tireCopy atIndex: i];
}
return carCopy;
}
在main.m這樣調(diào)用:
Car *carCopy = [car copy];
[carCopy print];
即可復(fù)制之前創(chuàng)建好的Car類實(shí)例尊惰。
3.協(xié)議與復(fù)制類型
我們可以在使用的數(shù)據(jù)類型中為實(shí)例變量和方法參數(shù)指定協(xié)議名稱讲竿,這樣就可以給OC的編譯器提供更多一點(diǎn)的信息,從而有助于檢查代碼中的錯(cuò)誤弄屡,如:
- (void) setObjectValue: (id<NSCopying>) obj;
編譯器將會(huì)直到期望的任意類型的對(duì)象题禀,只要其遵守該協(xié)議。