5

我正在尝试强制执行“正式” @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's 方法 ( Sprocket) 的类及其实例返回NOconformsToProtocol?

为什么一个实际上并不符合,但说它确实(Cog)返回YES

如果声明就是假装符合性所需要的一切,那么正式协议的意义何在?

你怎么能在没有多次@selector调用的情况下检查多个 s的完整实现respondsToSelector

@Josh Caswell .. 如果没有diff这两个 .. 我猜您的回复与NSObject我在此期间一直使用的类别的效果相似......</p>

@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

4 回答 4

9

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

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

完整来源:NSObject 的conformsToProtocol:

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

+ (BOOL)instancesRespondToSelector:(SEL)aSelector;
- (BOOL)respondsToSelector:(SEL)aSelector;
于 2013-05-29T18:31:47.903 回答
4

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

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

例如,某人 ( 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 *位。这留作练习:-)

于 2013-05-29T18:34:05.503 回答
2

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

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

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

如果我有一把锤子...

于 2013-05-29T19:13:06.813 回答
0

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

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

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

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

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

于 2018-06-06T19:59:42.070 回答