Objective-C中的动态方法创建

时间:2011-08-25 04:24:34

标签: objective-c dynamic

在书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中执行此操作?

感谢。

3 个答案:

答案 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)]];
    }
}

我唯一无法弄清楚的是如何从方法签名中获取类型编码。它似乎没有影响方法的输出,但如果可以,我想解决它。