Creating a dynamic method in Objective-C

In the book "Pragmatic Programmer," the authors suggest that all input to the method should be verified. This allows problems with the method to be caught at an early stage, and their sources are easily traceable.

In my Mac application, I achieved this by creating an Assert class. This class has several class methods. These methods determine if any precondition is met, and if not, an exception is thrown. A typical statement might look something like this:

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

This works very well and significantly reduces the time I spent searching for errors. However, recently I noticed that some of the assertion methods are very useful as predicates. For example, my methods integer:isGreaterThanInteger:andLessThanInteger: and my stringIsNotEmpty: equally useful. To this end, I created a second Predicate class, which I populated with several of my more useful predicate methods. So I took the logic from the assert methods and moved it to Predicate , and then rewrote my Assert methods as follows:

 if ![Predicate predicateMethod] throw exception 

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

Ideally, I would like to restore the Assert class so that when a method is called on it, it intercepts the selector. Then the Predicate class can be checked to see if it will respond to the selector, and if so, the method is called on Predicate with the same arguments that were passed to the Assert method. If the Predicate method returns false, an exception is thrown.

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

Thanks.

+2
source share
3 answers

I ended up redefining resolveClassMethod: Although overriding forwardInvocation might work (I would have to somehow override it for the class object), resolveClassMethod: seems simpler and more efficient. Here my final implementation looked like this:

 #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 could not understand was how to get the type encoding from the method signature. This did not affect the output of the methods, but I would like to fix it if I can.

+1
source

You can use -forwardingTargetForSelector: to simply forward the method to another object, but if you need advanced behavior (for example, checking the return value for see if it is false), you may need to use -forwardInvocation: (However, note that the documentation states that it is "much more expensive" than the previous option.)

+2
source

If you are using pure Objective-C, you should see the โ€œForwardingโ€ discussion here . It basically describes how to do exactly what you want, including sample code.

If you are using Cocoa, you may need instead of forwardInvocation:.

+1
source

Source: https://habr.com/ru/post/896362/


All Articles