Runtime--Protocols

Working with Protocols

定义

类的接口用来声明其自身相关的methods和properties;相反,协议用于声明独立于任何特定类的方法和属性。
基本定义语法:声明实例方法、类方法、属性

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

协议可以继承其它协议;NSObject提供了很多常用的协议,因此常见的定义方式

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

用法

下面举例说明用法。例如想要绘制如下图的饼图

Paste_Image.png

为了使得pie chart view的代码能够重复使用,将决定展示内容的代码放到另一个对象中--Datasource。这样可以达到只要更换数据源对象就可以展示不同的内容。
比如说饼图分几部分、各部分占得比例、各部分的标题的协议如下

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

Datasource

协议定义好了,此view还需要一个property来记录datasource对象,这个对象可能是任何类的实例,因此声明为id类型;对于view来说只知道此对象遵守XYZPieChartViewDataSource协议。Objective-C使用尖括号来表示遵守此协议。

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

注意:
1、此处的weak来修饰datasource是为了避免循环引用
2、datasource对象需要声明遵守协议,否则编译器发布警告
3、对于view来说datasource的类型不重要,重要是遵守并实现了协议

必选、可选协议

1、通过编译器指令@optional指定跟在后边的方法均为可选;
2、通过编译器指令@required指定跟在后边的方法均为必选;
3、不声明则默认必选;
例如

@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:方法检测对象是否已实现;例如

//局部对象变量自动初始化为nil
NSString *thisSegmentTitle;

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

协议继承

像类继承一样,协议也可以继承。例如最好使得自定义协议继承NSObject协议(NSObject接口分离出来组成的独立的协议)。由于MyProtocol继承NSObject protocol,所有凡是遵守MyProtocol的对象都将实现NSObject protocol中声明的方法,然后你并不担心这些方法的实现,因为使用的绝大多数类都继承NSObject,已经默认实现过了。协议定义变成这样

@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.

遵守协议

通过尖括号来声明一个类遵守协议,例如

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

提示:
1、如果一个类遵守的协议过多,不便于维护,建议分割成独立的类去实现。
2、如果未实现协议要求必须实现的方法,编译器将发出警告。
3、实现协议方法时,方法名称、参数类型必须和协议声明的保持一致。

协议屏蔽类的实现

协议除了让不同的类按照指定的方式完成一定的任务之外,还将功能抽离出来提高代码复用率。协议的标识的是相互协作的类之间没有层次关系,这样使得不相干的类可以使用相同的功能。比如说NSArray和NSDictionary都遵守NSCoding协议,使得其本身能方便的进行本地存储;遵守NSFastEnumeration协议,使得能够快速枚举元素。

协议除了上述作用之外,还能屏蔽类的实现。当不知道某个对象的所属类时即id或者需要隐藏的时候,协议可以起到很好的作用。例如,开发者框架中可能没有发布类的接口声明,由于class未知,不能够直接创建实例对象

id utility = [frameworkObject anonymousUtility];

为了anonymousUtility对象能够使用,SDK发不了一个协议来暴露其部分方法,尽管对class一无所知,但是还能勉强能够使用了

id <XYZFrameworkUtility> utility = [frameworkObject anonymousUtility];

此时utility变量可以使用XYZFrameworkUtility中声明的方法。
参考文献:Working with Protocols

Runtime--Protocols

查询协议相关信息

首先先来声明一个协议DataDelegate,ViewController类遵守,然后结合例子说明接口

@protocol DataDelegate <NSObject>

@property(nonatomic,copy)NSString *name;

-(void)ivarMethod;

@end

1、获取所有runtime时的协议

/* 获取所有协议名称 */
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);

//输出结果:过多仅做部分展示
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、根据名称获取协议

/* 获取协议 */
Protocol *protocol = objc_getProtocol("DataDelegate");

3、获取名称

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

//输出结果
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]);

//输出结果:空返回、无参数
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);

//输出结果
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]);

//输出结果
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);

//输出结果
index:0 propeytyName:name

6、获取继承的协议列表

/* 获取继承的协议 */
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);

//输出结果
index:0 name_class:NSObject

7、判断两个协议是否相等

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

//输出结果
是否相等:1

8、判断协议A是否遵守(继承)协议B

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

//输出结果
是否遵守:1

创建新的协议

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、新的协议只有被register才能使用
2、新的协议可以添加Method、Property、遵守的Protocol
3、所有的添加操作都必须在objc_allocateProtocol和objc_registerProtocol直接进行,也就是说在协议的构建期间进行
在上述代码的基础上

/* 创建新的协议 */
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);
    
/* 添加遵守的协议 */
protocol_addProtocol(newProtocol, @protocol(NSObject));
    
/* 添加方法方法 */
protocol_addMethodDescription(newProtocol, @selector(ivarMethod2), "v@:", YES, YES);
    
/* 注册新协议 */
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);
    
//获取新协议遵守的协议
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);

//输出结果
2 index:0 propeytyName:age//property

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

2 index:0 name_class:NSObject//protocol

协议添加完成后是需要指定类使用的

/* 检测类是否遵守指定的协议 */
BOOL conform = class_conformsToProtocol([ViewController class], protocol);
NSLog(@"conform:%d",conform);
    
/* 添加新的协议 */
class_addProtocol([ViewController class], newProtocol);
    
/* 获取类遵守的协议列表 */
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);

//输出结果
conform:1 //遵守
class index:0 name_class:DataDelegate2 //添加完成
class index:1 name_class:DataDelegate //本来就遵

参考文献:Objective-C RuntimeType Encodings

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容