Objective-C is a dynamic language. It’s possible to use runtime reflection to inspect and call an objects methods. The most common way to do this is to use respondsToSelector: and the performSelector:…
family of methods. The performSelector:
methods are useful but they do suffer from one potential irritating restriction: you can only call methods with 0 or 1 arguments. A work around for this is to pack all the arguments into a dictionary (or array). This works, but it’s ugly because:
There is another way; NSInvocation
. NSInvocation
is a class that represents a method call. Cocoa uses it for it’s undo system and for message forwarding. Here’s an example that shows how to use NSInvocation
instead of performSelector:
.
EMKTextFormatter
is an abstract base class. It converts an XML file into a NSAttributedString
which can be displayed in an NSTextView
. EMKTextFormatter
is designed to be reusable for many different XML schemas. We use reflection and invocations to provide this flexibility.
@interface EMKTextFormatter : NSObject
-(NSAttributedString *)formattedTextFromXMLFile:(NSURL *)xmlURL;
/*
Formatting methods for elements must have selectors that match this naming scheme:
apply{LOWERCASE_METHOD_NAME_WITH_LEADING_CAPITAL}:range:attribs:
Examples:
-(BOOL)applyQuote:(NSMutableAttributedString *)text range:(NSRange)range attribs:(NSDictionary *)attribs;
-(BOOL)applyQuoteattrib:(NSMutableAttributedString *)text range:(NSRange)range attribs:(NSDictionary *)attribs;
-(BOOL)applyImagecaption:(NSMutableAttributedString *)text range:(NSRange)range attribs:(NSDictionary *)attribs;
Formatting methods should return YES if the formatting was successfully applied otherwise they should return NO.
*/
@end
@implementation EMKTextFormatter
//...
-(BOOL)applyFormattingToElement:(NSString *)elementName text:(NSMutableAttributedString *)text range:(NSRange)range attribs:(NSDictionary *)attribs
{
//Create the selector for the method to call
NSString *elementNameHead = [elementName substringToIndex:1];
NSString *elementNameTail = [elementName substringFromIndex:1];
NSString *formatedElementName = [[elementNameHead uppercaseString] stringByAppendingString:[elementNameTail lowercaseString]];
NSString *selectorName = [NSString stringWithFormat:@"apply%@:range:attribs:", formatedElementName];
SEL formattingSelector = NSSelectorFromString(selectorName);
if (![self respondsToSelector:formattingSelector])
{
NSLog(@"Cannot format element <%@>. %@ does not implement %@", elementName, [self class], selectorName);
return NO;
}
//Create the method signature
//If there was an other method with an identical signature then instead of creating one we could use ???? to
//fetch the existing method signature.
NSString *typeString = [NSString stringWithFormat:@"%s%s%s%s%s%s", @encode(BOOL), //return value
@encode(id), //hidden 'self' argument
@encode(SEL), //hidden '_cmd' argument
@encode(NSMutableAttributedString), //first argument
@encode(NSRange), //second argument
@encode(NSDictionary *)]; //third argument
NSMethodSignature *methodSig = [NSMethodSignature signatureWithObjCTypes:[typeString UTF8String]];
//Create the invocation
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
[invocation setSelector:formattingSelector];
[invocation setArgument:&text atIndex:2];
[invocation setArgument:&range atIndex:3];
[invocation setArgument:&attribs atIndex:4];
//Fire the invocation
[invocation invokeWithTarget:self];
//Get the return value of the invocation
BOOL result = NO;
[invocation getReturnValue:&result];
if (!result)
{
NSLog(@"Error applying formatting to element %@ at range %@, with attribs: %@", elementName, NSStringFromRange(range), attribs);
}
return result;
}
-(BOOL)applyQuote:(NSMutableAttributedString *)formatedText range:(NSRange)range attribs:(NSDictionary *)attribs
{
BOOL result = NO;
//...
//Format the text here
//...
return result;
}
@end
It’s worth noting that we could call invokeWithTarget: using a performSelector:
… method. Also we could improve performance by storing methodSig
as this will be identical for each invocation.