Skip to content
Browse files

Merge branch 'EXTPassthrough'

  • Loading branch information...
2 parents 10e41f7 + fada0e6 commit 8256f34deee582a66ed225a6617e96364b70d40f @jspahrsummers committed Jul 3, 2012
Showing with 301 additions and 0 deletions.
  1. +1 −0 README.md
  2. +14 −0 Tests/EXTPassthroughTest.h
  3. +86 −0 Tests/EXTPassthroughTest.m
  4. +14 −0 extobjc.xcodeproj/project.pbxproj
  5. +185 −0 extobjc/EXTPassthrough.h
  6. +1 −0 extobjc/extobjc.h
View
1 README.md
@@ -19,6 +19,7 @@ libextobjc currently includes the following features:
* **Algebraic data types** generated completely at compile-time, defined using EXTADT.
* EXTBlockTarget, which extends the target-action mechanism with support for blocks.
* EXTTuple, for multiple return values and assignment.
+ * EXTPassthrough, to automatically implement methods that simply invoke the same method on another object.
* Better variadic arguments, with support for packaging the arguments up as an array, using EXTVarargs.
* Aspect-oriented programming, using EXTAspect.
* Block-based coroutines, using EXTCoroutine.
View
14 Tests/EXTPassthroughTest.h
@@ -0,0 +1,14 @@
+//
+// EXTPassthroughTest.h
+// extobjc
+//
+// Created by Justin Spahr-Summers on 2012-07-03.
+// Released into the public domain.
+//
+
+#import <SenTestingKit/SenTestingKit.h>
+#import "EXTPassthrough.h"
+
+@interface EXTPassthroughTest : SenTestCase
+
+@end
View
86 Tests/EXTPassthroughTest.m
@@ -0,0 +1,86 @@
+//
+// EXTPassthroughTest.m
+// extobjc
+//
+// Created by Justin Spahr-Summers on 2012-07-03.
+// Released into the public domain.
+//
+
+#import "EXTPassthroughTest.h"
+
+@interface InnerClass : NSObject
+@property (nonatomic, getter = isEnabled) BOOL enabled;
+
+- (void)voidMethod;
+- (int)methodWithString:(NSString *)str;
+- (int)methodWithString:(NSString *)str number:(NSNumber *)num;
+@end
+
+@interface OuterClass : NSObject
+@property (nonatomic, strong) InnerClass *inner;
+@end
+
+@interface OuterClass (DelegatedMethods)
+@property (nonatomic, getter = isEnabled) BOOL enabled;
+
+- (void)renamedMethod;
+- (int)methodWithString:(NSString *)str;
+- (int)methodWithString:(NSString *)str number:(NSNumber *)num;
+@end
+
+@implementation EXTPassthroughTest
+
+- (void)testPassthroughMethods {
+ OuterClass *outer = [[OuterClass alloc] init];
+ STAssertNotNil(outer, @"");
+
+ [outer renamedMethod];
+ STAssertEquals([outer methodWithString:@"foo"], 3, @"");
+ STAssertEquals([outer methodWithString:@"foobar" number:@5], 11, @"");
+}
+
+- (void)testPassthroughProperty {
+ OuterClass *outer = [[OuterClass alloc] init];
+ STAssertNotNil(outer, @"");
+ STAssertFalse(outer.enabled, @"");
+ STAssertFalse(outer.inner.enabled, @"");
+
+ outer.enabled = YES;
+ STAssertTrue(outer.enabled, @"");
+ STAssertTrue(outer.inner.enabled, @"");
+}
+
+@end
+
+@implementation OuterClass
+@passthrough(OuterClass, renamedMethod, self.inner, voidMethod);
+@passthrough(OuterClass, methodWithString:, self.inner);
+@passthrough(OuterClass, methodWithString:number:, [self inner]);
+@passthrough(OuterClass, isEnabled, self.inner);
+@passthrough(OuterClass, setEnabled:, self.inner);
+
+- (id)init {
+ self = [super init];
+ if (!self)
+ return nil;
+
+ self.inner = [[InnerClass alloc] init];
+ return self;
+}
+
+@end
+
+@implementation InnerClass
+
+- (void)voidMethod {
+}
+
+- (int)methodWithString:(NSString *)str {
+ return [self methodWithString:str number:nil];
+}
+
+- (int)methodWithString:(NSString *)str number:(NSNumber *)num {
+ return (int)[str length] + [num intValue];
+}
+
+@end
View
14 extobjc.xcodeproj/project.pbxproj
@@ -135,6 +135,10 @@
D0CD1BE8158F2A2A0039C845 /* EXTTupleTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D0CD1BE6158F2A2A0039C845 /* EXTTupleTest.m */; };
D0CFB61F128A8F12006DC377 /* EXTConcreteProtocolTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D0C1D917128A57B600E96C0F /* EXTConcreteProtocolTest.m */; };
D0CFB620128A8F12006DC377 /* EXTSwizzleTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D0A8B30D128A4B56004AACE0 /* EXTSwizzleTest.m */; };
+ D0D1F92E15A396CC002E2387 /* EXTPassthrough.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D1F92C15A396CC002E2387 /* EXTPassthrough.h */; };
+ D0D1F92F15A396CC002E2387 /* EXTPassthrough.h in Headers */ = {isa = PBXBuildFile; fileRef = D0D1F92C15A396CC002E2387 /* EXTPassthrough.h */; };
+ D0D1F93415A396F7002E2387 /* EXTPassthroughTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D1F93315A396F7002E2387 /* EXTPassthroughTest.m */; };
+ D0D1F93515A396F7002E2387 /* EXTPassthroughTest.m in Sources */ = {isa = PBXBuildFile; fileRef = D0D1F93315A396F7002E2387 /* EXTPassthroughTest.m */; };
D0E6A0EE159BB43B00FB92FC /* EXTAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E6A0EC159BB43B00FB92FC /* EXTAnnotation.h */; };
D0E6A0EF159BB43B00FB92FC /* EXTAnnotation.h in Headers */ = {isa = PBXBuildFile; fileRef = D0E6A0EC159BB43B00FB92FC /* EXTAnnotation.h */; };
D0E6A0F0159BB43B00FB92FC /* EXTAnnotation.m in Sources */ = {isa = PBXBuildFile; fileRef = D0E6A0ED159BB43B00FB92FC /* EXTAnnotation.m */; };
@@ -304,6 +308,9 @@
D0CD1BE6158F2A2A0039C845 /* EXTTupleTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTTupleTest.m; sourceTree = "<group>"; };
D0CFB60F128A8EEE006DC377 /* iOS Tests.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "iOS Tests.octest"; sourceTree = BUILT_PRODUCTS_DIR; };
D0CFB610128A8EEE006DC377 /* iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "iOS-Info.plist"; sourceTree = "<group>"; };
+ D0D1F92C15A396CC002E2387 /* EXTPassthrough.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTPassthrough.h; sourceTree = "<group>"; };
+ D0D1F93215A396F7002E2387 /* EXTPassthroughTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTPassthroughTest.h; sourceTree = "<group>"; };
+ D0D1F93315A396F7002E2387 /* EXTPassthroughTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTPassthroughTest.m; sourceTree = "<group>"; };
D0E6A0EC159BB43B00FB92FC /* EXTAnnotation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTAnnotation.h; sourceTree = "<group>"; };
D0E6A0ED159BB43B00FB92FC /* EXTAnnotation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = EXTAnnotation.m; sourceTree = "<group>"; };
D0E6A0F2159BB46D00FB92FC /* EXTAnnotationTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EXTAnnotationTest.h; sourceTree = "<group>"; };
@@ -435,6 +442,7 @@
D005F02B15950509007A8A1C /* EXTMultiObject.m */,
D005F02C15950509007A8A1C /* EXTNil.h */,
D005F02D15950509007A8A1C /* EXTNil.m */,
+ D0D1F92C15A396CC002E2387 /* EXTPassthrough.h */,
D005F03015950509007A8A1C /* EXTPrivateMethod.h */,
D005F03115950509007A8A1C /* EXTPrivateMethod.m */,
D005F03215950509007A8A1C /* EXTProtocolCategory.h */,
@@ -591,6 +599,8 @@
D0FE94031596668400F3AE1C /* EXTMultimethodTest.m */,
D002DAF613656CDF005348A5 /* EXTNilTest.h */,
D002DAF713656CDF005348A5 /* EXTNilTest.m */,
+ D0D1F93215A396F7002E2387 /* EXTPassthroughTest.h */,
+ D0D1F93315A396F7002E2387 /* EXTPassthroughTest.m */,
D033765B131E53DB0039ACFD /* EXTPrivateMethodTest.h */,
D033765C131E53DB0039ACFD /* EXTPrivateMethodTest.m */,
D0E7E907128F8DD200FE0263 /* EXTProtocolCategoryTest.h */,
@@ -651,6 +661,7 @@
D0FE93FF1596665D00F3AE1C /* EXTMultimethod.h in Headers */,
D09FB2F7159A41C400A5F6A4 /* EXTSelectorChecking.h in Headers */,
D0E6A0EF159BB43B00FB92FC /* EXTAnnotation.h in Headers */,
+ D0D1F92F15A396CC002E2387 /* EXTPassthrough.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -687,6 +698,7 @@
D0FE93FE1596665D00F3AE1C /* EXTMultimethod.h in Headers */,
D09FB2F6159A41C400A5F6A4 /* EXTSelectorChecking.h in Headers */,
D0E6A0EE159BB43B00FB92FC /* EXTAnnotation.h in Headers */,
+ D0D1F92E15A396CC002E2387 /* EXTPassthrough.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -923,6 +935,7 @@
D0FE94041596668400F3AE1C /* EXTMultimethodTest.m in Sources */,
D09FB2FA159A41D100A5F6A4 /* EXTSelectorCheckingTest.m in Sources */,
D0E6A0F4159BB46D00FB92FC /* EXTAnnotationTest.m in Sources */,
+ D0D1F93415A396F7002E2387 /* EXTPassthroughTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -951,6 +964,7 @@
D0FE94051596668400F3AE1C /* EXTMultimethodTest.m in Sources */,
D09FB2FB159A41D100A5F6A4 /* EXTSelectorCheckingTest.m in Sources */,
D0E6A0F5159BB46D00FB92FC /* EXTAnnotationTest.m in Sources */,
+ D0D1F93515A396F7002E2387 /* EXTPassthroughTest.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
View
185 extobjc/EXTPassthrough.h
@@ -0,0 +1,185 @@
+//
+// EXTPassthrough.h
+// extobjc
+//
+// Created by Justin Spahr-Summers on 2012-07-03.
+// Released into the public domain.
+//
+
+#import <Foundation/Foundation.h>
+#import <objc/runtime.h>
+#import "metamacros.h"
+
+/**
+ * \@passthrough defines \a METHOD on \a CLASS to simply invoke a method on
+ * another object and return the result. The object to message should be an
+ * expression passed as the third argument to the macro, and may refer to \c
+ * self (for instance, to access a property).
+ *
+ * By default, the message sent to the other object uses the same method name
+ * given to the macro. \a METHOD may pass through to a method by a different
+ * name by passing a fourth argument to the macro, which should be the name of
+ * the message to send.
+ *
+ * @code
+
+//
+// OuterClass.h
+//
+@interface OuterClass : NSObject
+@end
+
+@interface OuterClass (PassthroughMethods)
+@property (nonatomic, getter = isEnabled) BOOL enabled;
+
+- (void)renamedMethod;
+- (int)methodWithString:(NSString *)str;
+@end
+
+//
+// OuterClass.m
+//
+@interface InnerClass : NSObject
+@property (nonatomic, getter = isEnabled) BOOL enabled;
+
+- (void)voidMethod;
+- (int)methodWithString:(NSString *)str;
+@end
+
+@interface OuterClass ()
+@property (nonatomic, strong) InnerClass *inner;
+@end
+
+@implementation OuterClass
+@passthrough(OuterClass, renamedMethod, self.inner, voidMethod);
+@passthrough(OuterClass, methodWithString:, self.inner);
+@passthrough(OuterClass, isEnabled, self.inner);
+@passthrough(OuterClass, setEnabled:, self.inner);
+
+- (id)init {
+ self = [super init];
+ if (!self)
+ return nil;
+
+ self.inner = [InnerClass new];
+ return self;
+}
+
+@end
+
+@implementation InnerClass
+...
+@end
+
+ * @endcode
+ *
+ * @note \a METHOD must denote an instance method.
+ *
+ * @note To avoid "incomplete implementation" warnings, passthrough methods and
+ * properties may be declared in a category on \a CLASS, as opposed to the main
+ * \@interface block.
+ */
+#define passthrough(CLASS, METHOD, ...) \
+ class CLASS; \
+ \
+ passthrough_(__COUNTER__, CLASS, METHOD, __VA_ARGS__)
+
+/*** implementation details follow ***/ \
+#define passthrough_(ID, CLASS, METHOD, ...) \
+ static id \
+ (*metamacro_concat(ext_originalMethodSignatureForSelector_, ID)) \
+ (id, SEL, SEL); \
+ \
+ static NSMethodSignature * \
+ metamacro_concat(ext_methodSignatureForSelector_, ID) \
+ (CLASS *self, SEL _cmd, SEL selector) { \
+ if (selector != @selector(METHOD)) \
+ return metamacro_concat(ext_originalMethodSignatureForSelector_, ID)(self, _cmd, selector); \
+ \
+ id inner = metamacro_head(__VA_ARGS__); \
+ SEL innerSelector = passthrough_renamed_method(METHOD, __VA_ARGS__); \
+ return [inner methodSignatureForSelector:innerSelector]; \
+ } \
+ \
+ static BOOL \
+ (*metamacro_concat(ext_originalRespondsToSelector_, ID)) \
+ (id, SEL, SEL); \
+ \
+ static BOOL \
+ metamacro_concat(ext_respondsToSelector_, ID) \
+ (CLASS *self, SEL _cmd, SEL selector) { \
+ if (selector != @selector(METHOD)) \
+ return metamacro_concat(ext_originalRespondsToSelector_, ID)(self, _cmd, selector); \
+ \
+ id inner = metamacro_head(__VA_ARGS__); \
+ SEL innerSelector = passthrough_renamed_method(METHOD, __VA_ARGS__); \
+ return [inner respondsToSelector:innerSelector]; \
+ } \
+ \
+ static void \
+ (*metamacro_concat(ext_originalForwardInvocation_, ID)) \
+ (id, SEL, id); \
+ \
+ static void \
+ metamacro_concat(ext_forwardInvocation_, ID) \
+ (CLASS *self, SEL _cmd, NSInvocation *invocation) { \
+ if (invocation.selector != @selector(METHOD)) { \
+ metamacro_concat(ext_originalForwardInvocation_, ID)(self, _cmd, invocation); \
+ return; \
+ } \
+ \
+ [invocation setTarget:metamacro_head(__VA_ARGS__)]; \
+ [invocation setSelector:passthrough_renamed_method(METHOD, __VA_ARGS__)]; \
+ [invocation invoke]; \
+ } \
+ \
+ __attribute__((constructor)) \
+ static void metamacro_concat(ext_passthrough_injection_, ID) (void) { \
+ Class outerClass = objc_getClass(# CLASS); \
+ \
+ Method methodSignatureForSelector = class_getInstanceMethod(outerClass, @selector(methodSignatureForSelector:)); \
+ Method respondsToSelector = class_getInstanceMethod(outerClass, @selector(respondsToSelector:)); \
+ Method forwardInvocation = class_getInstanceMethod(outerClass, @selector(forwardInvocation:)); \
+ \
+ metamacro_concat(ext_originalMethodSignatureForSelector_, ID) = \
+ (id (*)(id, SEL, SEL))method_getImplementation(methodSignatureForSelector); \
+ \
+ metamacro_concat(ext_originalRespondsToSelector_, ID) = \
+ (BOOL (*)(id, SEL, SEL))method_getImplementation(respondsToSelector); \
+ \
+ metamacro_concat(ext_originalForwardInvocation_, ID) = \
+ (void (*)(id, SEL, id))method_getImplementation(forwardInvocation); \
+ \
+ class_replaceMethod( \
+ outerClass, \
+ @selector(methodSignatureForSelector:), \
+ (IMP)&metamacro_concat(ext_methodSignatureForSelector_, ID), \
+ method_getTypeEncoding(methodSignatureForSelector) \
+ ); \
+ \
+ class_replaceMethod( \
+ outerClass, \
+ @selector(respondsToSelector:), \
+ (IMP)&metamacro_concat(ext_respondsToSelector_, ID), \
+ method_getTypeEncoding(respondsToSelector) \
+ ); \
+ \
+ class_replaceMethod( \
+ outerClass, \
+ @selector(forwardInvocation:), \
+ (IMP)&metamacro_concat(ext_forwardInvocation_, ID), \
+ method_getTypeEncoding(forwardInvocation) \
+ ); \
+ }
+
+#define passthrough_renamed_method(METHOD, ...) \
+ @selector(metamacro_if_eq(1, metamacro_argcount(__VA_ARGS__)) \
+ ( \
+ /* no renaming */ \
+ METHOD \
+ ) \
+ ( \
+ /* the renamed method follows the passthrough target */ \
+ metamacro_at(1, __VA_ARGS__) \
+ ) \
+ )
View
1 extobjc/extobjc.h
@@ -20,6 +20,7 @@
#import "EXTMultimethod.h"
#import "EXTMultiObject.h"
#import "EXTNil.h"
+#import "EXTPassthrough.h"
#import "EXTPrivateMethod.h"
#import "EXTProtocolCategory.h"
#import "EXTSafeCategory.h"

0 comments on commit 8256f34

Please sign in to comment.
Something went wrong with that request. Please try again.