为什么“conformsToProtocol”不检查“必需”方法实现?

时间:2013-05-29 17:36:34

标签: objective-c class methods protocols

我正在尝试强制执行“正式”@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

4 个答案:

答案 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时):

  1. 警告声明Cog未能实施所需的方法;
  2. 错误抱怨[x mustImplement]由于x不知道具有NSObject类型所需的方法 - 您需要强制删除它,只需[(id)x mustImplement]你将已经测试过该方法的存在。
  3. 总之,如果您知道代码的发起者没有忽略编译器警告,那么您只能依赖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上公开,因为它通常会导致比它解决的问题更多的问题。