Runtime--Protocols

Working with Protocols

定義

類的接口用來聲明其自身相關(guān)的methods和properties咖为;相反易猫,協(xié)議用于聲明獨立于任何特定類的方法和屬性著隆。
基本定義語法:聲明實例方法狡相、類方法、屬性

@protocol ProtocolName
// list of methods and properties
@end

協(xié)議可以繼承其它協(xié)議褪迟;NSObject提供了很多常用的協(xié)議冗恨,因此常見的定義方式

@protocol MyProtocol <NSObject>
// list of methods and properties
@end

用法

下面舉例說明用法答憔。例如想要繪制如下圖的餅圖

Paste_Image.png

為了使得pie chart view的代碼能夠重復(fù)使用,將決定展示內(nèi)容的代碼放到另一個對象中--Datasource掀抹。這樣可以達(dá)到只要更換數(shù)據(jù)源對象就可以展示不同的內(nèi)容攀唯。
比如說餅圖分幾部分、各部分占得比例渴丸、各部分的標(biāo)題的協(xié)議如下

@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
@end

Datasource

協(xié)議定義好了侯嘀,此view還需要一個property來記錄datasource對象,這個對象可能是任何類的實例谱轨,因此聲明為id類型戒幔;對于view來說只知道此對象遵守XYZPieChartViewDataSource協(xié)議。Objective-C使用尖括號來表示遵守此協(xié)議土童。

@interface XYZPieChartView : UIView
@property (weak) id <XYZPieChartViewDataSource> dataSource;
...
@end

注意:
1诗茎、此處的weak來修飾datasource是為了避免循環(huán)引用
2献汗、datasource對象需要聲明遵守協(xié)議敢订,否則編譯器發(fā)布警告
3、對于view來說datasource的類型不重要罢吃,重要是遵守并實現(xiàn)了協(xié)議

必選楚午、可選協(xié)議

1、通過編譯器指令@optional指定跟在后邊的方法均為可選尿招;
2矾柜、通過編譯器指令@required指定跟在后邊的方法均為必選;
3就谜、不聲明則默認(rèn)必選怪蔑;
例如

@protocol XYZPieChartViewDataSource
- (NSUInteger)numberOfSegments;
- (CGFloat)sizeOfSegmentAtIndex:(NSUInteger)segmentIndex;
@optional
- (NSString *)titleForSegmentAtIndex:(NSUInteger)segmentIndex;
- (BOOL)shouldExplodeSegmentAtIndex:(NSUInteger)segmentIndex;
@required
- (UIColor *)colorForSegmentAtIndex:(NSUInteger)segmentIndex;
@end

表示聲明了3個必選方法和2個可選方法。
對于可選方法丧荐,要在runtime期間通過respondsToSelector:方法檢測對象是否已實現(xiàn)缆瓣;例如

//局部對象變量自動初始化為nil
NSString *thisSegmentTitle;

if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
   thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
}

協(xié)議繼承

像類繼承一樣,協(xié)議也可以繼承虹统。例如最好使得自定義協(xié)議繼承NSObject協(xié)議(NSObject接口分離出來組成的獨立的協(xié)議)弓坞。由于MyProtocol繼承NSObject protocol,所有凡是遵守MyProtocol的對象都將實現(xiàn)NSObject protocol中聲明的方法窟却,然后你并不擔(dān)心這些方法的實現(xiàn)昼丑,因為使用的絕大多數(shù)類都繼承NSObject,已經(jīng)默認(rèn)實現(xiàn)過了夸赫。協(xié)議定義變成這樣

@protocol MyProtocol <NSObject>
// list of methods and properties
@end
In this example, any object that adopts MyProtocol also effectively adopts all the methods declared in the NSObject protocol.

遵守協(xié)議

通過尖括號來聲明一個類遵守協(xié)議,例如

@interface MyClass : NSObject <MyProtocol, AnotherProtocol, YetAnotherProtocol>
...
@end

提示:
1咖城、如果一個類遵守的協(xié)議過多茬腿,不便于維護(hù)呼奢,建議分割成獨立的類去實現(xiàn)。
2切平、如果未實現(xiàn)協(xié)議要求必須實現(xiàn)的方法握础,編譯器將發(fā)出警告。
3悴品、實現(xiàn)協(xié)議方法時禀综,方法名稱、參數(shù)類型必須和協(xié)議聲明的保持一致苔严。

協(xié)議屏蔽類的實現(xiàn)

協(xié)議除了讓不同的類按照指定的方式完成一定的任務(wù)之外定枷,還將功能抽離出來提高代碼復(fù)用率。協(xié)議的標(biāo)識的是相互協(xié)作的類之間沒有層次關(guān)系届氢,這樣使得不相干的類可以使用相同的功能欠窒。比如說NSArray和NSDictionary都遵守NSCoding協(xié)議,使得其本身能方便的進(jìn)行本地存儲退子;遵守NSFastEnumeration協(xié)議岖妄,使得能夠快速枚舉元素。

協(xié)議除了上述作用之外寂祥,還能屏蔽類的實現(xiàn)荐虐。當(dāng)不知道某個對象的所屬類時即id或者需要隱藏的時候,協(xié)議可以起到很好的作用丸凭。例如缚俏,開發(fā)者框架中可能沒有發(fā)布類的接口聲明,由于class未知贮乳,不能夠直接創(chuàng)建實例對象

id utility = [frameworkObject anonymousUtility];

為了anonymousUtility對象能夠使用忧换,SDK發(fā)不了一個協(xié)議來暴露其部分方法,盡管對class一無所知向拆,但是還能勉強能夠使用了

id <XYZFrameworkUtility> utility = [frameworkObject anonymousUtility];

此時utility變量可以使用XYZFrameworkUtility中聲明的方法亚茬。
參考文獻(xiàn):Working with Protocols

Runtime--Protocols

查詢協(xié)議相關(guān)信息

首先先來聲明一個協(xié)議DataDelegate,ViewController類遵守浓恳,然后結(jié)合例子說明接口

@protocol DataDelegate <NSObject>

@property(nonatomic,copy)NSString *name;

-(void)ivarMethod;

@end

1刹缝、獲取所有runtime時的協(xié)議

/* 獲取所有協(xié)議名稱 */
unsigned int protocol_count = 0;
__unsafe_unretained Protocol **protocol_list = objc_copyProtocolList(&protocol_count);
    
for (int i = 0; i < protocol_count ; i ++)
{
   Protocol *protocol = protocol_list[i];
   const char *name_class = protocol_getName(protocol);//名稱
   NSLog(@"index:%d name_class:%@",i,[NSString stringWithUTF8String:name_class]);
   
}
free(protocol_list);

//輸出結(jié)果:過多僅做部分展示
index:0 name_class:SBSRemoteAlertClientHandle
index:1 name_class:_UIContentContainerInternal
index:2 name_class:NSFetchRequestResult
index:3 name_class:CAMLWriterDelegate
index:4 name_class:CABehaviorDelegate
index:5 name_class:GEOTransitSystem
index:6 name_class:GEOMapItemPhoto
index:7 name_class:UIInputViewAnimationHost
index:8 name_class:UIAdaptivePresentationControllerDelegate
index:9 name_class:_UISharingPublicController
index:10 name_class:NSISEngineDelegate
index:11 name_class:UIPopoverPresentationControllerDelegate

2、根據(jù)名稱獲取協(xié)議

/* 獲取協(xié)議 */
Protocol *protocol = objc_getProtocol("DataDelegate");

3颈将、獲取名稱

/* 獲取名稱 */
const char *pro_name_char = protocol_getName(protocol);
NSString *protocolName = [NSString stringWithUTF8String:pro_name_char];
NSLog(@"protocolName:%@",protocolName);

//輸出結(jié)果
protocolName:DataDelegate

4梢夯、獲取方法描述

/* 獲取方法描述 */
struct objc_method_description protocol_method_description =  protocol_getMethodDescription(protocol, @selector(ivarMethod), YES, YES);
NSLog(@"name:%@ type:%@",NSStringFromSelector(protocol_method_description.name),[NSString stringWithUTF8String:protocol_method_description.types]);

//輸出結(jié)果:空返回、無參數(shù)
name:ivarMethod type:v16@0:8
/* 獲取方法列表描述 */
unsigned int methodCount = 0;
struct objc_method_description *method_description_list = protocol_copyMethodDescriptionList(protocol, YES, YES, &methodCount);

for (int i = 0; i < methodCount ; i ++)
{
   struct objc_method_description description = method_description_list[i];
   NSLog(@"index:%d name:%@ type:%@",i,NSStringFromSelector(description.name),[NSString stringWithUTF8String:description.types]);

}
free(method_description_list);

//輸出結(jié)果
index:0 name:ivarMethod type:v16@0:8

index:1 name:name type:@16@0:8
index:2 name:setName: type:v24@0:8@16

5晴圾、獲取property

/* 獲取property */
objc_property_t property = protocol_getProperty(protocol, "name", YES, YES);
const char *propeytyName = property_getName(property);
NSLog(@"propeytyName:%@",[NSString stringWithUTF8String:propeytyName]);

//輸出結(jié)果
propeytyName:name
/* 獲取property列表 */
unsigned int propertyCount = 0;
objc_property_t *property_list = protocol_copyPropertyList(protocol, &propertyCount);
for (int i = 0; i < propertyCount ; i ++)
{
   objc_property_t property = property_list[i];
   const char *propeytyName = property_getName(property);
   NSLog(@"index:%d propeytyName:%@",i,[NSString stringWithUTF8String:propeytyName]);
   
}
free(property_list);

//輸出結(jié)果
index:0 propeytyName:name

6颂砸、獲取繼承的協(xié)議列表

/* 獲取繼承的協(xié)議 */
unsigned int protocol_count = 0;
__unsafe_unretained Protocol **protocol_list = protocol_copyProtocolList(protocol, &protocol_count);

for (int i = 0; i < protocol_count ; i ++)
{
   Protocol *protocol = protocol_list[i];
   const char *name_class = protocol_getName(protocol);//名稱
   NSLog(@"index:%d name_class:%@",i,[NSString stringWithUTF8String:name_class]);

}
free(protocol_list);

//輸出結(jié)果
index:0 name_class:NSObject

7、判斷兩個協(xié)議是否相等

/* 是否相等*/
BOOL isEqual = protocol_isEqual(protocol, @protocol(DataDelegate));
NSLog(@"是否相等:%d",isEqual);

//輸出結(jié)果
是否相等:1

8、判斷協(xié)議A是否遵守(繼承)協(xié)議B

/* 是否遵守 */
BOOL isconform = protocol_conformsToProtocol(protocol, @protocol(NSObject));
NSLog(@"是否遵守:%d",isconform);

//輸出結(jié)果
是否遵守:1

創(chuàng)建新的協(xié)議

OBJC_EXPORT Protocol *objc_allocateProtocol(const char *name) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);
OBJC_EXPORT void objc_registerProtocol(Protocol *proto) 
    OBJC_AVAILABLE(10.7, 4.3, 9.0, 1.0);

說明
1人乓、新的協(xié)議只有被register才能使用
2勤篮、新的協(xié)議可以添加Method、Property色罚、遵守的Protocol
3碰缔、所有的添加操作都必須在objc_allocateProtocol和objc_registerProtocol直接進(jìn)行,也就是說在協(xié)議的構(gòu)建期間進(jìn)行
在上述代碼的基礎(chǔ)上

/* 創(chuàng)建新的協(xié)議 */
Protocol *newProtocol = objc_allocateProtocol("DataDelegate2");
    
/* 添加property */
objc_property_attribute_t attr_T = {"T","@\"NSString\""};//@encod
objc_property_attribute_t attr_N = {"N",""};//原子性
objc_property_attribute_t attr_C = {"C",""};//copy
objc_property_attribute_t attr_V = {"V","_age"};//實例變量
objc_property_attribute_t attrs[] = {attr_T,attr_N,attr_C,attr_V};
protocol_addProperty(newProtocol, "age", attrs, 4, YES, YES);
    
/* 添加遵守的協(xié)議 */
protocol_addProtocol(newProtocol, @protocol(NSObject));
    
/* 添加方法方法 */
protocol_addMethodDescription(newProtocol, @selector(ivarMethod2), "v@:", YES, YES);
    
/* 注冊新協(xié)議 */
objc_registerProtocol(newProtocol);

//獲取property列表
unsigned int propertyCount2 = 0;
objc_property_t *property_list2 = protocol_copyPropertyList(newProtocol, &propertyCount2);
for (int i = 0; i < propertyCount2 ; i ++)
{
   objc_property_t property = property_list2[i];
   const char *propeytyName = property_getName(property);
   NSLog(@"2 index:%d propeytyName:%@",i,[NSString stringWithUTF8String:propeytyName]);
   
}
free(property_list2);
    
//獲取方法列表
unsigned int methodCount2 = 0;
struct objc_method_description *method_description_list2 = protocol_copyMethodDescriptionList(newProtocol, YES, YES, &methodCount2);
    
for (int i = 0; i < methodCount2 ; i ++)
{
   struct objc_method_description description = method_description_list2[i];
   NSLog(@"2 index:%d name:%@ type:%@",i,NSStringFromSelector(description.name),[NSString stringWithUTF8String:description.types]);
   
}
free(method_description_list2);
    
//獲取新協(xié)議遵守的協(xié)議
unsigned int protocol_count2 = 0;
__unsafe_unretained Protocol **protocol_list2 = protocol_copyProtocolList(newProtocol, &protocol_count2);
    
for (int i = 0; i < protocol_count2 ; i ++)
{
   Protocol *protocol = protocol_list2[i];
   const char *name_class = protocol_getName(protocol);//名稱
   NSLog(@"2 index:%d name_class:%@",i,[NSString stringWithUTF8String:name_class]);
   
}
free(protocol_list2);

//輸出結(jié)果
2 index:0 propeytyName:age//property

2 index:0 name:ivarMethod2 type:v@://Method

2 index:0 name_class:NSObject//protocol

協(xié)議添加完成后是需要指定類使用的

/* 檢測類是否遵守指定的協(xié)議 */
BOOL conform = class_conformsToProtocol([ViewController class], protocol);
NSLog(@"conform:%d",conform);
    
/* 添加新的協(xié)議 */
class_addProtocol([ViewController class], newProtocol);
    
/* 獲取類遵守的協(xié)議列表 */
unsigned int classProtocolCount = 0;
__unsafe_unretained Protocol **classProtocolList =  class_copyProtocolList([ViewController class], &classProtocolCount);
for (int i = 0; i < classProtocolCount ; i ++)
{
   Protocol *protocol = classProtocolList[i];
   const char *name_class = protocol_getName(protocol);//名稱
   NSLog(@"class index:%d name_class:%@",i,[NSString stringWithUTF8String:name_class]);
   
}
free(classProtocolList);

//輸出結(jié)果
conform:1 //遵守
class index:0 name_class:DataDelegate2 //添加完成
class index:1 name_class:DataDelegate //本來就遵

參考文獻(xiàn):Objective-C Runtime戳护、Type Encodings

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末金抡,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子腌且,更是在濱河造成了極大的恐慌梗肝,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件切蟋,死亡現(xiàn)場離奇詭異统捶,居然都是意外死亡,警方通過查閱死者的電腦和手機柄粹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門喘鸟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人驻右,你說我怎么就攤上這事什黑。” “怎么了堪夭?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵愕把,是天一觀的道長。 經(jīng)常有香客問我森爽,道長恨豁,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任爬迟,我火速辦了婚禮橘蜜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘付呕。我一直安慰自己藻雪,他們只是感情好证杭,可當(dāng)我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布鸭廷。 她就那樣靜靜地躺著偷卧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪姆钉。 梳的紋絲不亂的頭發(fā)上说订,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天抄瓦,我揣著相機與錄音,去河邊找鬼克蚂。 笑死闺鲸,一個胖子當(dāng)著我的面吹牛筋讨,可吹牛的內(nèi)容都是我干的埃叭。 我是一名探鬼主播,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼悉罕,長吁一口氣:“原來是場噩夢啊……” “哼赤屋!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起壁袄,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤类早,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嗜逻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涩僻,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年栈顷,在試婚紗的時候發(fā)現(xiàn)自己被綠了逆日。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡萄凤,死狀恐怖室抽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情靡努,我是刑警寧澤坪圾,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站惑朦,受9級特大地震影響兽泄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜漾月,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一病梢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧栅屏,春花似錦飘千、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至哥纫,卻和暖如春霉旗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工厌秒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留读拆,地道東北人。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓鸵闪,卻偏偏與公主長得像檐晕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蚌讼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,851評論 2 361

推薦閱讀更多精彩內(nèi)容