在书The Pragmatic Programmer中,作者建议应验证所有方法输入。这样可以及早发现方法问题,并且可以轻松追踪其来源。
在我的Mac应用程序中,我通过创建Assert
类来完成此操作。这个类有几个类方法。这些方法确定是否满足某些前提条件,如果不满足,则抛出异常。典型的断言可能看起来像这样:
-(void) setWidth: (int) theWidth {
[Assert integer: width isGreaterThanInteger: 0];
width = theWidth;
}
这非常有效,并且大大减少了我花在寻找bug上的时间。但是,我最近注意到一些断言方法作为谓词非常有用。例如,我的integer:isGreaterThanInteger:andLessThanInteger:
和我的stringIsNotEmpty:
方法同样有用。为此,我创建了第二个类Predicate
,我用几个更有用的谓词方法填充了它。所以我从assert方法中获取了逻辑,并将其移到Predicate
中,然后重写了我的Assert
方法,如下所示:
if ![Predicate predicateMethod]
throw exception
这已成为维护噩梦。如果我更改Predicate
中方法名称的名称,我还必须在Assert
中更改它以保持一致。如果我更新Assert
方法的文档,那么我必须对Predicate
方法执行相同的操作。
理想情况下,我希望重构Assert
类,以便在调用任何方法时,它会拦截选择器。然后可以检查Predicate
类以查看它是否响应选择器,如果是,则在Predicate
上调用该方法,并使用传递给Assert
方法的相同参数。如果Predicate
方法返回false,则抛出异常。
有没有办法在Objective-C中执行此操作?
感谢。
答案 0 :(得分:2)
您可以使用-forwardingTargetForSelector:
简单地将方法转发给另一个对象,但如果您想要高级行为(比如检查返回值以查看它是否为假),则可能需要使用-forwardInvocation:
。 (但请注意,文档说这比前一个选项“要贵得多”。)
答案 1 :(得分:1)
如果您使用纯Objective-C,您应该看到“转发”讨论here。它基本上描述了如何做到你想要的,包括示例代码。
如果你正在使用Cocoa,那么你可能不得不使用forwardInvocation:。
答案 2 :(得分:1)
我最终覆盖了resolveClassMethod:
。虽然覆盖forwardInvocation
可能有效(我不得不想办法覆盖类对象的某种方式),resolveClassMethod:
似乎是更简单,更有效的方法。这是我的最终实现结果如下:
#import "Assert.h"
#import "Predicate.h"
#include <objc/objc-runtime.h>
void handlePredicateSelector(id self, SEL _cmd, ...);
@implementation Assert
+(void) failWithMessage: (NSString *) message
{
NSLog(@"%@", message);
[NSException raise:@"ASSERTION FAILURE" format:message];
}
+(void) fail
{
[Assert failWithMessage:@"An unconditional failure has been detected."];
}
+(BOOL) resolveClassMethod: (SEL) selector
{
if ([(id) [Predicate class] respondsToSelector:selector])
{
/*
The meta class fix was taken from here: http://iphonedevelopment.blogspot.com/2008/08/dynamically-adding-class-objects.html
*/
//get the method properties from the Predicate class
Class predicateMetaClass = objc_getMetaClass([[Predicate className] UTF8String]);
Method predicateMethod = class_getClassMethod(predicateMetaClass, selector);
const char *encoding = method_getTypeEncoding(predicateMethod);
Class selfMetaClass = objc_getMetaClass([[self className] UTF8String]);
class_addMethod(selfMetaClass, selector, (IMP) handlePredicateSelector, "B@:?");
return YES;
}
return [super resolveClassMethod:selector];
}
@end
void handlePredicateSelector(id self, SEL _cmd, ...)
{
//get the number of arguments minus the self and _cmd arguments
NSMethodSignature *predicateMethodSignature = [(id) [Predicate class] methodSignatureForSelector:_cmd];
NSUInteger numberOfArguments = [predicateMethodSignature numberOfArguments] - 2;
NSInvocation *predicateInvocation = [NSInvocation invocationWithMethodSignature:predicateMethodSignature];
[predicateInvocation setTarget:[Predicate class]];
[predicateInvocation setSelector:_cmd];
va_list ap;
va_start(ap, _cmd);
for (int i = 0; i < numberOfArguments; i++)
{
void *arg = va_arg(ap, void *);
[predicateInvocation setArgument:&arg atIndex:i+2];
}
va_end(ap);
BOOL returnValue;
[predicateInvocation invoke];
[predicateInvocation getReturnValue:&returnValue];
//determine if the assertion is true
if (!returnValue)
{
[Assert failWithMessage:[NSString stringWithFormat: @"The following assertion failed: %@", NSStringFromSelector(_cmd)]];
}
}
我唯一无法弄清楚的是如何从方法签名中获取类型编码。它似乎没有影响方法的输出,但如果可以,我想解决它。