1.方法
Objective-C中的方法有兩種:
1.1 實(shí)例方法
以-
開(kāi)頭的方法是實(shí)例方法。它屬于類的某一個(gè)或某幾個(gè)實(shí)例對(duì)象国瓮,即類對(duì)象必須實(shí)例化后才可以使用的方法灭必,將消息發(fā)送給實(shí)例對(duì)象:
// Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
@interface Deck : NSObject
@property(nonatomic) int cardNum;
// 實(shí)例方法
- (Card *)randomDrawCard;
+ (NSString *)CardKinds;
@end
實(shí)例方法中可以使用該類的所有實(shí)例變量:
// Deck.m
#import "Card.h"
@implementation Deck
- (Card *)drawCardFromTop
{
// 實(shí)例變量
_cardNum--;
//TODO.....
}
+ (NSString *)CardKinds
{
NSLog("Cards are divided into four kinds: spades, diamonds, clubs and hearts");
}
@end
1.2 類方法
以+
開(kāi)頭的方法是類方法。Objc中的類方法類似Java中的static
靜態(tài)方法乃摹,它是屬于類本身的方法厂财,不屬于類的某一個(gè)實(shí)例對(duì)象,所以不需要實(shí)例化類峡懈,用類名即可使用璃饱,是將消息發(fā)送給類:
// Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
@interface Deck : NSObject
- (Card *)randomDrawCard;
// 類方法
+ (NSString *)CardKinds;
@end
類方法不能使用任何實(shí)例變量:
// Deck.m
#import "Card.h"
@implementation Deck
- (Card *)drawCardFromTop
{
// 實(shí)例變量
_cardNum--;
//TODO.....
}
// 不能使用該類的實(shí)例變量_carNum
+ (NSString *)CardKinds
{
NSLog("Cards are divided into four kinds: spades, diamonds, clubs and hearts");
}
@end
所以我們使用類方法一般有兩種情況:
- 創(chuàng)建一些事物,比如特殊格式的字符串等肪康。
- 作為工具方法荚恶,比如返回常數(shù)等。
1.3 類方法和實(shí)例方法認(rèn)知的誤區(qū)
類方法常駐內(nèi)存磷支,所以比實(shí)例方法效率高谒撼。
事實(shí)上,在加載時(shí)機(jī)和占用內(nèi)存上雾狈,類方法和實(shí)例方法是一樣的廓潜,在類第一次被使用時(shí)加載方法,所以在效率上沒(méi)有什么區(qū)別。類方法分配在堆上辩蛋,實(shí)例方法分配在棧上呻畸。
事實(shí)上,所有的方法都不可能分配在堆棧區(qū)悼院,方法作為二進(jìn)制代碼是存儲(chǔ)在內(nèi)存的程序代碼區(qū)伤为,這個(gè)內(nèi)存區(qū)域是不可寫(xiě)的。請(qǐng)查看我這篇筆記中的相關(guān)概念Objective-C中的Block据途。
1.4 總結(jié)
實(shí)例方法和類方法有大多數(shù)的共性绞愚,比如都可以有一個(gè)或多個(gè)參數(shù)、都可以繼承基類的方法颖医、相同的聲明規(guī)范等位衩。唯一不同的就是類方法不能使用實(shí)例變量,所以導(dǎo)致它只適用于一些特殊的情況熔萧。
2.Category
如果我們想給一個(gè)已存在的蚂四、很復(fù)雜的類添加一個(gè)新的方法,應(yīng)該怎么做哪痰?
想翻源碼添加遂赠?騷年,你太天真晌杰,你如果看不到源碼呢跷睦。即便我們可以看到源碼,如果我們新增的邏輯也很復(fù)雜肋演,這樣就會(huì)擴(kuò)大原始設(shè)計(jì)的規(guī)模抑诸,有可能會(huì)打亂整個(gè)設(shè)計(jì)的結(jié)構(gòu)。
Category就是Objective-C提供的為我們解決這一問(wèn)題的方法爹殊。它可以讓我們動(dòng)態(tài)的在已經(jīng)存在的類中添加新的行為蜕乡,即方法。對(duì)類進(jìn)行擴(kuò)展時(shí)不需要訪問(wèn)其源碼梗夸,也不需要?jiǎng)?chuàng)建子類层玲。
2.1 使用方法
Category的實(shí)現(xiàn)很簡(jiǎn)單,我們舉例說(shuō)明反症。
// Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
@interface Deck : NSObject
- (Card *)randomDrawCard;
@end
這是類Deck的聲明文件辛块,其中包含一個(gè)實(shí)例方法randomDrawCard
,如果我們想在不修改原始類铅碍、不增加子類的情況下润绵,為該類增加一個(gè)drawCardFromTop
方法,只需要定義兩個(gè)文件Deck+DrawCardFromTop.h
和Deck+DrawCardFromTop.m
胞谈,在聲明文件和實(shí)現(xiàn)文件中用()
把Category的名稱括起來(lái)即可尘盼,聲明文件如下:
// Deck+DrawCardFromTop.h
#import "Deck.h"
#import "Card.h"
@interface Deck(DrawCardFromTop)
- (Card *)drawCardFromTop;
@end
實(shí)現(xiàn)文件如下:
// Deck+DrawCardFromTop.m
#import "Deck+DrawCardFromTop.h"
#import "Card.h"
@implementation Deck(DrawCardFromTop)
- (Card *)drawCardFromTop
{
//TODO.....
}
@end
DrawCardFromTop
是Category的名稱憨愉。這里一般使用約定俗成的習(xí)慣,將聲明文件和實(shí)現(xiàn)文件統(tǒng)一采用"原類名+Category名"的方式命名卿捎。
使用也非常簡(jiǎn)單配紫,引入Category的聲明文件,然后正常調(diào)用即可:
// main.m
#import "Deck+DrawCardFromTop.h"
#import "Card.h"
int main(int argc, char * argv[])
{
Deck *deck = [[Deck alloc] init];
Card *card = [deck drawCardFromTop];
return 0;
}
2.2 使用場(chǎng)景
- 需求變更在整個(gè)開(kāi)發(fā)周期是司空見(jiàn)慣的事情娇澎,那么我們可能就需要對(duì)某個(gè)或某幾個(gè)類中添加新的方法以滿足需求笨蚁。
- 我們?cè)趫F(tuán)隊(duì)協(xié)作開(kāi)發(fā)時(shí)候睹晒,經(jīng)常需要多個(gè)人來(lái)實(shí)現(xiàn)一個(gè)類中的不同方法趟庄,在這種情況下采用Category是一個(gè)較好的選擇。
- 當(dāng)一些基礎(chǔ)類庫(kù)滿足不了我們的需求時(shí)伪很,我們希望能擴(kuò)展基礎(chǔ)類庫(kù)戚啥,這時(shí)就需要Category。
2.3 需要注意的問(wèn)題
- Category可以訪問(wèn)原始類的實(shí)例變量锉试,但不能添加變量猫十,如果想添加變量,可以考慮通過(guò)繼承創(chuàng)建子類呆盖。
- Category可以重載原始類的方法拖云,但不推薦這么做,這么做的后果是你再也不能訪問(wèn)原來(lái)的方法应又。如果確實(shí)要重載宙项,正確的選擇是創(chuàng)建子類。
- 和普通接口有所區(qū)別的是株扛,在分類的實(shí)現(xiàn)文件中可以不必實(shí)現(xiàn)所有聲明的方法尤筐,只要你不去調(diào)用它。
2.4 總結(jié)
掌握并用好Category可以充分利用Objective-C的動(dòng)態(tài)特性洞就,編寫(xiě)出靈活簡(jiǎn)潔的代碼盆繁。
3.Protocol
簡(jiǎn)單來(lái)說(shuō),Protocol不屬于任何一個(gè)類旬蟋,它只是一個(gè)方法列表油昂,任何類都可以對(duì)其中聲明的方法進(jìn)行實(shí)現(xiàn)。這種設(shè)計(jì)模式一般稱為代理模式(delegation)倾贰。你可以通過(guò)Protocol定義各種行為秕狰,在不同的場(chǎng)景采用不同的實(shí)現(xiàn)方式。在iOS和OS X開(kāi)發(fā)中躁染,Apple采用了大量的代理模式來(lái)實(shí)現(xiàn)MVC中View和Controller的解耦鸣哀。
3.1 使用方法
Protocol有兩種聲明的方式:
- 在單獨(dú)的聲明文件(.h文件)中聲明。
- 在某個(gè)類的聲明的文件中聲明吞彤。
以上兩種方式視具體情況而定我衬,但是在代碼規(guī)范上都是一致的:
// HandleDeckDelegate.h
@protocol HandleDeckDelegate <NSObject>
@required
- (void)ShuffleDeck;
@optional
- (void)CuttingDeck;
@end
上述代碼中有兩個(gè)關(guān)鍵字叹放,@required
和@optional
,表示如果要實(shí)現(xiàn)這個(gè)協(xié)議挠羔,那么ShuffleDeck
方法是必須要實(shí)現(xiàn)的傲武,CuttingDeck
則是可選的,如果不注明囊拜,那么方法默認(rèn)是@required
的兔港,必須實(shí)現(xiàn)。
那么如何實(shí)現(xiàn)這個(gè)Protocol呢范舀,很簡(jiǎn)單合是,創(chuàng)建一個(gè)普通的Objective-C類,如果Protocol使用單獨(dú)的.h文件聲明锭环,那么在該類的.h聲明文件中引入包含Protocol的.h文件聪全,如果Protocol是聲明在一個(gè)相關(guān)類中,那么就需要引入該類的.h文件辅辩。之后聲明采用這個(gè)Protocol即可:
// Deck.h
#import <Foundation/Foundation.h>
#import "Card.h"
#import "HandleDeckDelegate.h"
@interface Deck : NSObject<HandleDeckDelegate>
- (Card *)randomDrawCard;
@end
用尖括號(hào)(<...>)括起來(lái)的HandleDeckDelegate
就是我們創(chuàng)建的Protocol难礼。如果要采用多個(gè)Protocol,可以在尖括號(hào)內(nèi)引入多個(gè)Protocol名稱玫锋,并用逗號(hào)隔開(kāi)即可蛾茉。例如<HandleDeckDelegate,xxxDelegate>
。
// Deck.m
#import "Card.h"
@implementation Deck
- (Card *)drawCardFromTop
{
//TODO.....
}
- (void)ShuffleDeck
{
//TODO.....
}
@end
由于CuttingDeck
方法是可選的撩鹿,所以我們只實(shí)現(xiàn)了ShuffleDeck
谦炬。
3.2 使用場(chǎng)景
- Objective-C里的Protocol和Java語(yǔ)言中的接口很類似,如果一些類之間沒(méi)有繼承關(guān)系三痰,但是又具備某些相同的行為吧寺,則可以使用Protocol來(lái)描述它們的關(guān)系。
- 不同的類散劫,可以遵守同一個(gè)Protocol稚机,在不同的場(chǎng)景下注入不同的實(shí)例,實(shí)現(xiàn)不同的功能获搏。
3.3 需要注意的問(wèn)題
- 根據(jù)約定赖条,框架中后綴為Delegate的都是Protocol,例如
UIApplicationDelegate
常熙,UIWebViewDelegate
等纬乍。 - Protocol本身是可以繼承的,比如:
@protocol A
-(void)methodA;
@end
@protocol B <A>
-(void)methodB;
@end
如果你要實(shí)現(xiàn)B裸卫,那么methodA和methodB都需要實(shí)現(xiàn)仿贬。
- Protocol是與任何類都無(wú)關(guān)的,任何類都可以實(shí)現(xiàn)定義好的Protocol墓贿,如果我們想知道某個(gè)類是否實(shí)現(xiàn)了某個(gè)Protocol茧泪,那么我們可以用
conformsToProtocol
方法進(jìn)行判斷:
[obj conformsToProtocol:@protocol(ProcessDataDelegate)]
3.4 總結(jié)
Protocol最常用的就是委托代理模式蜓氨,Cocoa框架中大量采用了這種模式實(shí)現(xiàn)數(shù)據(jù)和UI的分離。例如UIView產(chǎn)生的所有事件队伟,都是通過(guò)委托的方式交給Controller完成穴吹。