
Add Pattern Matching and Parameterized Methods to Objective-C
More and more articles on the topic "add functional bones to buns in your favorite imperative programming language." Here is a recent example for Java .
Objective-C recently added blocks to help with closures. But I want something more. For example comparison with the image ( Pattern Matching ) and parameterized methods.
Exclusively Just For Fun, we will try to add them to the language without patching the compiler and dancing with the preprocessor, only using the language itself.
What came of it?
All of the following exclusively example illustrating the idea and using it in your projects, you can at your own peril and risk.
Comparison with the image allows you to write something in the similarity: First, try to make a comparison with the image. So, we create the NSObject extension and declare the type of block in which we will wrap additional implementations.
Now you need to replace the implementation of the selector in order to be able to choose the necessary implementation depending on the actual parameter passed. The method substitution method ( Method Swizzling ) will help with this . Each method is a type structure
And the actual implementation of the NSObject extension:
All the magic is in the [-NSObject method: withParameter: useBlock:] method. For the passed selector, we create a PMImplementation object that stores a pointer to the implementation. Then we replace it with the "swizzledMethod:" method. The trick is that we can find out which selector was actually called through the _cmd parameter, which is implicitly passed when any selector is called. Now, when calling swizzledMethod, [-PMImplementation forObject: invokeWithParameter:] is called, and there we either find the block on the object and execute it, or use the default implementation.
The [-NSObject impls] method adds a dictionary in the runtime for self that stores PMImplementation objects for different selectors.
Now, for the sake of everything, it was all about to start - for example, a class for finding the factorial:
It works as expected:
To parameterize the methods, we write a small wrapper class, in which we redefine the “isEqual:” method and a convenient macro.
Now the implementation can be chosen depending on the type of argument.
We try:
What else could be done? At least working with methods that have more than one argument, but this would greatly increase the article.
As a result, we obtained implementations of image matching and parameterized methods (though with creepy syntax) in Objective-C.
Obvious minuses - only objects can be used as parameters and “images”.
I hope this article was at least useful to someone and after reading there was a desire to learn more about the magic of Objective-C runtime. If so, then addictive reading matter for the night:
Objective-C Runtime Programming Guide
Objective-C Runtime Reference
Objective-C recently added blocks to help with closures. But I want something more. For example comparison with the image ( Pattern Matching ) and parameterized methods.
Exclusively Just For Fun, we will try to add them to the language without patching the compiler and dancing with the preprocessor, only using the language itself.
What came of it?
All of the following exclusively example illustrating the idea and using it in your projects, you can at your own peril and risk.
Comparison with the image allows you to write something in the similarity: First, try to make a comparison with the image. So, we create the NSObject extension and declare the type of block in which we will wrap additional implementations.
factorial( 0 ) -> 1
factorial( 1 ) -> 1
factorial( n ) -> n * factorial( n - 1 )
typedef id (^ PatternMatchingBlock) (id obj);
@interface NSObject (PatternMatching)
- (void) method: (SEL) sel_ withParameter: (id) object_ useBlock: (PatternMatchingBlock) block_;
@end
Now you need to replace the implementation of the selector in order to be able to choose the necessary implementation depending on the actual parameter passed. The method substitution method ( Method Swizzling ) will help with this . Each method is a type structure
objc_method
from which we are interested in the method_imp
type field IMP
. IMP
Is a pointer to the C-function of the method implementation. The meaning of method substitution is to replace these pointers in 2 methods. We create a class that will store in itself a pointer to the initial implementation of the method and a dictionary, the keys of which will be objects of “images”, and the values are blocks of implementations:@interface PMImplementation: NSObject
@property (nonatomic, retain) NSMutableDictionary * impls;
@property (nonatomic, retain) NSValue * defaultImpl;
+ (id) implementationWithDefaultImpl: (IMP) impl_;
- (id) forObject: (id) object_ invokeWithParameter: (id) parameter_;
@end
static char * PMimplsKey = nil;
@implementation PMImplementation
@synthesize impls = _impls;
@synthesize defaultImpl = _default_impl;
- (void) dealloc {
[_impls release];
[_default_impl release];
[super dealloc];
}
- (id) initWithDefaultImpl: (IMP) impl_ {
if (! (self = [super init]))
return nil;
self.defaultImpl = [NSValue valueWithPointer: impl_];
self.impls = [NSMutableDictionary dictionary];
return self;
}
+ (id) implementationWithDefaultImpl: (IMP) impl_ {
return [[[self alloc] initWithDefaultImpl: impl_] autorelease];
}
- (id) forObject: (id) object_ invokeWithParameter: (id) parameter_ {
for (id key_ in [self.impls allKeys])
if ([key_ isEqual: parameter_]) {
PatternMatchingBlock block_ = [self.impls objectForKey: key_ ];
return block_ (parameter_);
}
IMP impl_ = [self.defaultImpl pointerValue];
return impl_ (object_, _cmd, parameter_);
}
@end
And the actual implementation of the NSObject extension:
@implementation NSObject (PatternMatching)
- (NSMutableDictionary *) impls {
NSMutableDictionary * impls_ = objc_getAssociatedObject (self, & PMImplsKey);
if (! impls_) {
impls_ = [NSMutableDictionary dictionary];
objc_setAssociatedObject (self, & PMImplsKey, impls_
, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return impls_;
}
- (void) method: (SEL) sel_ withParameter: (id) object_
useBlock: (PatternMatchingBlock) block_ {
NSString * selector_key_ = NSStringFromSelector (sel_);
PMImplementation * impl_ = [self.impls objectForKey: selector_key_];
if (! impl_) {
Method default_ = class_getInstanceMethod ([self class], sel_);
IMP default_impl_ = method_getImplementation (default_);
impl_ = [PMImplementation implementationWithDefaultImpl: default_impl_];
[self.impls setObject: impl_ forKey: selector_key_];
Method swizzed_method_ = class_getInstanceMethod ([self class]
, @ selector (swizzledMethod :));
method_setImplementation (default_, method_getImplementation (swizzed_method_));
}
[impl_.impls setObject: block_ forKey: object_];
}
- (id) swizzledMethod: (id) obj_ {
PMImplementation * impl_ = [self.impls objectForKey: NSStringFromSelector (_cmd)];
return [impl_ forObject: self invokeWithParameter: obj_];
}
@end
All the magic is in the [-NSObject method: withParameter: useBlock:] method. For the passed selector, we create a PMImplementation object that stores a pointer to the implementation. Then we replace it with the "swizzledMethod:" method. The trick is that we can find out which selector was actually called through the _cmd parameter, which is implicitly passed when any selector is called. Now, when calling swizzledMethod, [-PMImplementation forObject: invokeWithParameter:] is called, and there we either find the block on the object and execute it, or use the default implementation.
The [-NSObject impls] method adds a dictionary in the runtime for self that stores PMImplementation objects for different selectors.
Now, for the sake of everything, it was all about to start - for example, a class for finding the factorial:
@interface Factorial: NSObject
- (NSDecimalNumber *) factorial :( NSDecimalNumber *) number_;
@end
@implementation Factorial
- (id) init {
if (! (self = [super init]))
return nil;
NSDecimalNumber * zero_ = [NSDecimalNumber numberWithInteger: 0];
[self method: @selector (factorial :) withParameter: zero_ useBlock: ^ (id obj _) {
return (id) [NSDecimalNumber numberWithInteger: 1];
}];
NSDecimalNumber * one_ = [NSDecimalNumber numberWithInteger: 1];
[self method: @selector (factorial :) withParameter: one_ useBlock: ^ (id obj _) {
return (id) [NSDecimalNumber numberWithInteger: 1];
}];
return self;
}
- (NSDecimalNumber *) factorial: (NSDecimalNumber *) number_ {
return [number_ decimalNumberByMultiplyingBy:
[self factorial: [number_ decimalNumberBySubtracting:
[NSDecimalNumber numberWithInteger: 1]]]];
}
It works as expected:
Factorial * factorial_ = [[Factorial new] autorelease];
NSNumber * number_ = [NSDecimalNumber numberWithInteger: 10];
NSLog (@ "factorial% @ =% @", number_, [factorial_ factorial: number_]);
factorial 10 = 3628800
To parameterize the methods, we write a small wrapper class, in which we redefine the “isEqual:” method and a convenient macro.
#define PMCLASS (x) [[[PMClass alloc] initWith: [x class]] autorealese]
@interface PMClass: NSObject
@property (nonatomic, retain) Class class;
- (id) initWith :( Class) class_;
@end
@implementation PMClass
@synthesize class = _class;
- (void) dealloc {
[_class release];
[super dealloc];
}
- (id) initWith: (Class) class_ {
if (! (self = [super init]))
return nil;
self.class = class_;
return self;
}
- (BOOL) isEqual: (id) object_ {
return [self.class isEqual: [object_ class]];
}
- (id) copyWithZone: (NSZone *) zone_ {
return [[PMClass alloc] initWith: self.class];
}
@end
Now the implementation can be chosen depending on the type of argument.
@interface Test: NSObject
- (void) test: (id) obj_;
@end
@implementation Test
- (id) init {
if (! (self = [super init]))
return nil;
[self method: @selector (test :) withParameter: PMCLASS (NSNull) useBlock: ^ (id obj _) {
NSLog (@ "implementation for Null:% @", [obj_ class]);
return (id) nil;
}];
return self;
}
- (void) test: (id) obj_ {
NSLog (@ "default impl for Object:% @", [obj_ class]);
}
@end
We try:
Test * test_ = [[Test new] autorealese];
[test_ test: @ "String"];
[test_ test: [NSNull null]];
default impl for Object: NSCFString
implementation for Null: NSNull
What else could be done? At least working with methods that have more than one argument, but this would greatly increase the article.
As a result, we obtained implementations of image matching and parameterized methods (though with creepy syntax) in Objective-C.
Obvious minuses - only objects can be used as parameters and “images”.
I hope this article was at least useful to someone and after reading there was a desire to learn more about the magic of Objective-C runtime. If so, then addictive reading matter for the night:
Objective-C Runtime Programming Guide
Objective-C Runtime Reference