【问题标题】:Why does "conformsToProtocol" not check for "required" method implementations?为什么“conformsToProtocol”不检查“必需”的方法实现?
【发布时间】:2013-05-25 02:26:24
【问题描述】:

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

正式协议的意义何在?如果声明就是假装符合性所需要的全部内容?

如果没有多次调用respondsToSelector,您如何才能真正检查多个@selectors 的完整实现?

@Josh Caswell.. 如果没有diff这两个...

@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

【问题讨论】:

  • 如果一个类没有实现需要的方法,你会得到一个警告……这是一个编程错误;
  • 那是一些彻头彻尾的时髦代码格式......
  • tl;dr 编译器检查协议一致性。采取零警告/错误策略,您就会知道这些类符合协议。在运行时检查将是多余的,可能很慢,并且运行时间不可预测。
  • @bbum 格式有点做作 - 消除“滚动”,也就是切断我。 @Grady Player 谢谢!我以为我要疯了……但你的强调让我看起来 - 并意识到 - 我已将 -w 插入到我的 Code Runner 编译脚本中。呵呵。

标签: objective-c class methods protocols


【解决方案1】:

遵守协议只是一个“承诺”,你无法知道conformsToProtocol:的接收者是否真的实现了所有需要的方法。使用尖括号语法声明该类符合协议就足够了,并且 conformsToProtocol: 将返回 yes:

讨论
如果一个类采用协议或从另一个采用该协议的类继承,则称该类“符合”协议。通过在接口声明后的尖括号中列出协议来采用协议。

完整来源:NSObject's conformsToProtocol:

协议声明的优势在于您可以在编译时知道一个类是否真的采用了所需的方法。如果没有,将给出警告。我建议不要依赖conformsToProtocol:,而是使用自省。即通过调用 instancesRespondToSelector: / respondsToSelector: 来验证类/对象是否实现了方法:

+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
- (BOOL)respondsToSelector:(SEL)aSelector;

【讨论】:

    【解决方案2】:

    你用的是什么编译器? Xcode/Clang 发出 2 个警告和 1 个错误...

    将协议视为具有会员要求的俱乐部。询问某人是否是俱乐部的成员,可以通过他们拥有会员卡 (NSObject&lt;ReqiredProtocol&gt;) 来证明,应该告诉您该人符合这些要求。但是,没有会员资格并不意味着他们不符合要求。

    例如某人 (Sprocket) 可能满足加入的所有要求,但选择不加入。其他人 (Cog) 可能无法满足要求,但马虎的管理员可能会让他们进入。

    后者是我询问编译器的原因(马虎的管理员;-))。尝试在 Xcode 4.6.3/Clang 4.2 上输入的代码会产生警告和错误(与使用 GCC 4.2 一样):

    1. 警告指出Cog 未能实现所需的方法;
    2. 错误抱怨[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 * 位。这留作练习:-)

    【讨论】:

    • 就是这样。每个类都有一个它说它遵守的协议的列表,conformsToProtocol: 检查的就是这个列表(以及超类的列表)。实际的一致性检查——这些承诺的方法是否已实现——在编译时执行。
    【解决方案3】:

    CRD 是对的;编译器会告诉你实际的一致性,应该听听。如果这被忽略,则运行时没有任何内置方法可以进行仔​​细检查。类在内部维护协议对象的内部列表; conformsToProtocol: 看看就好。

    冒着有人会过来告诉我不要再摆弄 #@(%!^& 运行时的风险,如果你真的需要检查 actual 实现,这是一种方法:

    #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;
    }
    

    如果我有一把锤子……

    【讨论】:

      【解决方案4】:

      所有这些答案都很好。对他们来说,我还要补充一点:调用conformsToProtocol: 几乎总是一个错误。因为它告诉的是类是否说它符合协议,而不是它是否真的提供了具体的方法:

      • 可以通过消除各种警告来创建一个声称符合但不符合的类,如果您假设存在所需的方法,则会导致崩溃。
      • 可以创建一个符合协议但未声明这样做的类,从而导致即使存在委托也不会调用方法。
      • 当协议更改时,它可能会导致编程错误蔓延,因为您的代码会在调用以前需要但现在不再需要的方法之前检查是否符合协议。

      所有这些问题都可能导致意外行为。

      IMO,如果你想知道一个类是否处理一个方法,最安全的方法是明确询问它是否处理该方法 (respondsToSelector:),而不是询问它是否符合恰好发生的协议包含那个方法。

      IMO,conformsToProtocol: 确实应该是 Objective-C 运行时中的一个函数,而不是暴露在 NSObject 上,因为它通常会导致比它解决的问题更多的问题。

      【讨论】:

        猜你喜欢
        • 2016-06-25
        • 1970-01-01
        • 2019-10-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-26
        • 1970-01-01
        • 2011-12-23
        相关资源
        最近更新 更多