在Objective-C中创建动态方法 [英] Dynamic method creation in Objective-C

查看:144
本文介绍了在Objective-C中创建动态方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

实用程序员的书中,作者建议所有方法输入都应验证。这样就可以很方便地捕捉到一个方法的问题,并且它们的来源很容易跟踪。



在我的Mac应用程序中,我通过创建一个 Assert 类来实现这一点。这个类有几个类的方法。这些方法确定是否满足某些前提条件,如果不符合条件,则抛出异常。典型的断言可能看起来像这样:

   - (void)setWidth:(int)theWidth {
[Assert integer:width isGreaterThanInteger:0];
width = theWidth;
}

这个效果非常好,大大减少了我花费的时间虫狩猎但是,我注意到最近有一些断言方法作为谓词非常有用。例如,我的 integer:isGreaterThanInteger:andLessThanInteger:和我的 stringIsNotEmpty:方法同样有用。为此,我创建了一个第二类谓词,我填充了我更多的有用的谓词方法。所以我从assert方法中获取逻辑,并将其移动到 Predicate 中,然后重写我的 Assert 方法,如以下:

  if![Predicate predicateMethod] 
throw exception
/ pre>

这变成了维护噩梦。如果我在谓词中更改方法名称的名称,我还必须在 Assert 中更改它们以保持一致。如果我更新 Assert 方法的文档,那么我必须对 Predicate 方法执行相同操作。



理想情况下,我想重建 Assert 类,以便当调用任何方法时,它拦截选择器。然后可以检查 Predicate 类,以查看它是否响应选择器,如果是,则在 Predicate 具有相同的参数传入 Assert 方法。如果 Predicate 方法返回false,那么抛出异常。



有没有办法在Objective -C?



谢谢。

解决方案

code> resolveClassMethod:。虽然覆盖 forwardInvocation 可能已经工作(我不得不找出一些方法来覆盖类对象), resolveClassMethod:似乎是更容易和更有效的方法。这是我的最终执行结果如下所示:

  #importAssert.h
#import谓词。 h
#include< objc / objc-runtime.h>

void handlePredicateSelector(id self,SEL _cmd,...);

@implementation Assert

+(void)failWithMessage:(NSString *)消息
{
NSLog(@%@,message);
[NSException raise:@ASSERTION FAILURE格式:message];
}

+(void)fail
{
[Assert failWithMessage:@检测到无条件的失败。


+(BOOL)resolveClassMethod :( SEL)selector
{
if([(id)[Predicate class] respondToSelector:selector])
{
/ *
元类修复是从这里获取的:http://iphonedevelopment.blogspot.com/2008/08/dynamically-adding-class-objects.html
* /

//从谓词类获取方法属性
类predicateMetaClass = objc_getMetaClass([[Predicate className] UTF8String]);
方法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 @ :?);

返回YES;
}
return [super resolveClassMethod:selector];
}

@end

void handlePredicateSelector(id self,SEL _cmd,...)
{
//获取号码的参数减去self和_cmd参数
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); (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];

//确定断言是否为真
if(!returnValue)
{
[Assert failWithMessage:[NSString stringWithFormat:@以下断言失败:% @,NSStringFromSelector(_cmd)]];
}
}

我唯一无法想象的是如何从方法签名获取类型编码。它似乎并没有影响方法的输出,但是如果可以的话我想修复它。


In the book The Pragmatic Programmer, the authors suggest that all method inputs should be validated. This allows problems with a method to be caught early and their sources traced easily.

In my Mac application, I accomplished this by creating an Assert class. This class has several class methods. These methods determine if some precondition is met, and if it is not, then an exception is thrown. A typical assertion might looks something like this:

-(void) setWidth: (int) theWidth {
    [Assert integer: width isGreaterThanInteger: 0];
    width = theWidth;
}

This works really well, and significantly reduced the amount of time I've spend bug hunting. However, I've noticed lately some of the assertion methods are very useful as predicates. For example, my integer:isGreaterThanInteger:andLessThanInteger: and my stringIsNotEmpty: methods are equally useful. To this end, I created a second class Predicate, which I filled with several of my more useful predicate methods. So I took the logic from the assert methods, and moved it into Predicate, and then rewrote my Assert methods like the following:

if ![Predicate predicateMethod]
    throw exception

This has turned into a maintenance nightmare. If I change the name of a method name in Predicate, I must also change it in Assert to stay consistent. If I update the documentation of an Assert method, then I must do the same to a Predicate method.

Ideally, I would like the reconstruct the Assert class so that when any method is called on it, it intercepts the selector. The Predicate class can then be checked to see if it responds to the selector, and if it does, the method is called on Predicatewith the same arguments that were passed into the Assert method. If the Predicate method returns false, then an exception is thrown.

Is there a way to do this in Objective-C?

Thanks.

解决方案

I ended up overriding resolveClassMethod:. While overriding forwardInvocation might have worked (I would have had to figure out some way to override it for the class object), resolveClassMethod: seems like it's the easier and more efficient method. Here's what my final implementation ended up looking like:

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

The only thing I couldn't really figure out was how to get the type encoding from the method signature. It didn't seem to affect the output of the methods, but I would like to fix it if I can.

这篇关于在Objective-C中创建动态方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆