我正在尝试强制执行“正式”@protocol
,但无法可靠地测试我的类/实例是否实际执行协议的“必需”方法,而不是简单地“声明”它们符合协议
我窘境的一个完整例子......
#import <Foundation/Foundation.h>
@protocol RequiredProtocol
@required
- (NSString*) mustImplement; @end
@interface Cog : NSObject <RequiredProtocol> @end
@implementation Cog @end
@interface Sprocket : NSObject @end
@implementation Sprocket
- (NSString*) mustImplement
{ return @"I conform, but ObjC doesn't care!"; } @end
int main(int argc, char *argv[]) {
Protocol *required = @protocol(RequiredProtocol);
SEL requiredSEL = @selector(mustImplement);
void (^testProtocolConformance)(NSObject*) = ^(NSObject *x){
NSLog(@"Protocol:%@\n"
"Does %@ class conform:%@ \n"
"Do instances conform:%@ \n"
"Required method's result:\"%@\"",
NSStringFromProtocol ( required ),
NSStringFromClass ( x.class ),
[x.class conformsToProtocol:required] ? @"YES" : @"NO",
[x conformsToProtocol:required] ? @"YES" : @"NO",
[x respondsToSelector:requiredSEL] ? [x mustImplement]
: nil );
};
testProtocolConformance ( Cog.new );
testProtocolConformance ( Sprocket.new );
}
结果:
Protocol:RequiredProtocol
Does Cog class conform:YES
Do instances conform:YES
Required method's result:"(null)"
Protocol:RequiredProtocol
Does Sprocket class conform:NO
Do instances conform:NO
Required method's result:"I conform, but ObjC doesn't care!"
为什么一个类及其实现@protocol
方法(Sprocket
)的实例会将NO
返回conformsToProtocol
?
为什么一个实际上并不符合,但是它做到了(Cog
)返回YES
?
正式协议的重点是什么?如果声明是假装一致性所需的全部内容?
如果没有对@selector
进行MULTIPLE调用,您如何才能实际检查多个respondsToSelector
的完整实施?
@Josh Caswell ..没有diff
这两个......我猜你的回答与我在{I}期间一直在使用的NSObject
类别的效果相似...
@implementation NSObject (ProtocolConformance)
- (BOOL) implementsProtocol:(id)nameOrProtocol {
Protocol *p = [nameOrProtocol isKindOfClass:NSString.class]
? NSProtocolFromString(nameOrProtocol)
: nameOrProtocol; // Arg is string OR protocol
Class klass = self.class;
unsigned int outCount = 0;
struct objc_method_description *methods = NULL;
methods = protocol_copyMethodDescriptionList( p, YES, YES, &outCount);
for (unsigned int i = 0; i < outCount; ++i) {
SEL selector = methods[i].name;
if (![klass instancesRespondToSelector: selector]) {
if (methods) free(methods); methods = NULL; return NO;
}
}
if (methods) free(methods); methods = NULL; return YES;
}
@end
答案 0 :(得分:9)
符合协议只是一个“承诺”,您无法知道 conformsToProtocol:的接收者是否实际实现了所有必需的方法。足以让您使用尖括号语法声明该类符合协议,并且 conformsToProtocol:将返回yes:
讨论
如果一个类采用协议或从另一个采用它的类继承,则称该类符合协议。通过在接口声明后将它们放在尖括号内来采用协议。
完整来源:NSObject's conformsToProtocol:。
协议声明的优势在于,如果类真正采用了所需的方法,则可以在编译时知道 。如果没有,将发出警告。我建议不要依赖 conformsToProtocol:,而是要使用内省。也就是说,验证类/对象是否通过调用 instancesRespondToSelector实现方法: / respondsToSelector::
+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
- (BOOL)respondsToSelector:(SEL)aSelector;
答案 1 :(得分:4)
您使用的是什么编译器? Xcode / Clang发出2个警告和1个错误......
将协议视为具有会员资格要求的俱乐部。询问某人是否是俱乐部会员,由他们证明拥有会员卡(NSObject<ReqiredProtocol>
),应该告诉您一个人符合这些要求。但是,缺乏会员资格并不意味着他们不符合要求。
E.g。某人(Sprocket
)可能符合加入的所有要求,但选择不参加。其他人(Cog
)可能无法满足要求,但是一个草率的管理员可能会让他们进入。
后者是我询问编译器(草率管理员;-))的原因。尝试在Xcode 4.6.3 / Clang 4.2上输入的代码产生警告和错误(使用GCC 4.2时):
Cog
未能实施所需的方法; [x mustImplement]
由于x
不知道具有NSObject
类型所需的方法 - 您需要强制删除它,只需[(id)x mustImplement]
你将已经测试过该方法的存在。总之,如果您知道代码的发起者没有忽略编译器警告,那么您只能依赖conformsToProtocol
- 检查是在编译时完成的。
<强>附录强>
我错过了你问题的最后一句话。如果您希望发现某个类是否符合协议的要求,即使它没有声明它,例如,上面的Sprocket
(或者如果你从忽略编译器警告的民众那里获得代码 - 上面的Cog
作者),那么你可以使用Obj-C运行时的工具来实现。而且您只需要写一次调用repsondsToSelector
...
我只输入以下内容并在您的样本上快速测试。无论如何未经过彻底测试,警告经纪人等等。代码假定为ARC。
#import <objc/runtime.h>
@interface ProtocolChecker : NSObject
+ (BOOL) doesClass:(Class)aClass meetTheRequirementsOf:(Protocol *)aProtocol;
@end
@implementation ProtocolChecker
+ (BOOL) doesClass:(Class)aClass meetTheRequirementsOf:(Protocol *)aProtocol
{
struct objc_method_description *methods;
unsigned int count;
// required instance methods
methods = protocol_copyMethodDescriptionList(aProtocol, YES, YES, &count);
for (unsigned int ix = 0; ix < count; ix++)
{
if (![aClass instancesRespondToSelector:methods[ix].name])
{
free(methods);
return NO;
}
}
free(methods);
// required class methods
methods = protocol_copyMethodDescriptionList(aProtocol, YES, NO, &count);
for (unsigned int ix = 0; ix < count; ix++)
{
if (![aClass respondsToSelector:methods[ix].name])
{
free(methods);
return NO;
}
}
free(methods);
// other protocols
Protocol * __unsafe_unretained *protocols = protocol_copyProtocolList(aProtocol, &count);
for (unsigned int ix = 0; ix < count; ix++)
{
if (![self doesClass:aClass meetTheRequirementsOf:protocols[ix]])
{
free(protocols);
return NO;
}
}
free(protocols);
return YES;
}
@end
您当然希望确切了解其工作原理,尤其是* __unsafe_unretained *
位。这是一个练习: - )
答案 2 :(得分:2)
CRD是对的;编译器会告诉您实际一致性,应该听取它。如果忽略了这一点,则运行时没有任何内置方法可以进行仔细检查。类在内部维护协议对象的内部列表; conformsToProtocol:
只是看着它。
冒着有人要出现并告诉我再次停止摆弄#@(%!^&amp;运行时,如果你真的需要检查实际实现,是你可以这样做的一种方式:
#import <objc/runtime.h>
BOOL classReallyTrulyDoesImplementAllTheRequiredMethodsOfThisProtocol(Class cls, Protocol * prtcl)
{
unsigned int meth_count;
struct objc_method_description * meth_list;
meth_list = protocol_copyMethodDescriptionList(p,
YES /*isRequired*/,
YES /*isInstanceMethod*/,
&meth_count);
/* Check instance methods */
for(int i = 0; i < meth_count; i++ ){
SEL methName = meth_list[i].name;
if( ![class instancesRespondToSelector:methName] ){
/* Missing _any_ required methods means failure */
free(meth_list);
return NO;
}
}
free(meth_list);
meth_list = protocol_copyMethodDescriptionList(p,
YES /*isRequired*/,
NO /*isInstanceMethod*/,
&meth_count);
/* Check class methods, if any */
for(int i = 0; i < meth_count; i++ ){
SEL methName = meth_list[i].name;
if( ![class respondsToSelector:methName] ){
free(meth_list);
return NO;
}
}
free(meth_list);
return YES;
}
如果我有锤子......
答案 3 :(得分:0)
所有这些答案都很好。对他们来说,我还要补充一点:调用conformsToProtocol:
几乎总是一个错误。因为它告诉该类是否符合协议,而不是它是否实际提供了特定的方法:
所有这些问题都可能导致意外行为。
IMO,如果你想知道一个类是否处理一个方法,最安全的方法是明确询问它是否处理该方法(respondsToSelector:
),而不是询问它是否符合一个协议碰巧包含那种方法。
IMO,conformsToProtocol:
应该是Objective-C运行时中的一个函数,而不是在NSObject上公开,因为它通常会导致比它解决的问题更多的问题。