From d5cff7e687af4bb553b18ae5b28746e99f13bc5d Mon Sep 17 00:00:00 2001 From: Anton Bukov Date: Sun, 29 May 2016 10:41:30 +0300 Subject: [PATCH] Add ability to call original getter/setter implementation from injected method --- DeluxeInjection.podspec | 2 +- DeluxeInjection/Classes/DIDefaults.m | 11 ++- DeluxeInjection/Classes/DIDeluxeInjection.h | 34 ++++---- DeluxeInjection/Classes/DIDeluxeInjection.m | 79 ++++++++++++------- .../DeluxeInjection.xcodeproj/project.pbxproj | 12 +-- 5 files changed, 75 insertions(+), 63 deletions(-) diff --git a/DeluxeInjection.podspec b/DeluxeInjection.podspec index 2f8d772..be9ee21 100644 --- a/DeluxeInjection.podspec +++ b/DeluxeInjection.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DeluxeInjection" - s.version = "0.2.0" + s.version = "0.3.0" s.summary = "Simplest Objective-C Dependency Injection (DI:syringe:) implementation ever" s.description = <<-DESC diff --git a/DeluxeInjection/Classes/DIDefaults.m b/DeluxeInjection/Classes/DIDefaults.m index 578d514..495b0ea 100644 --- a/DeluxeInjection/Classes/DIDefaults.m +++ b/DeluxeInjection/Classes/DIDefaults.m @@ -31,17 +31,20 @@ @implementation DeluxeInjection (DIDefaults) + (void)injectDefaultsWithKey:(DIDefaultsKeyBlock)keyBlock forProtocol:(Protocol *)protocol withSync:(BOOL)withSync { [self inject:^NSArray *(Class targetClass, SEL getter, SEL setter, NSString *propertyName, Class propertyClass, NSSet *propertyProtocols) { NSString *key = keyBlock(targetClass, propertyName, propertyClass, propertyProtocols) ?: propertyName; - return @[^id(id target, id *ivar) { + return @[DIGetterMake(^id _Nullable(id _Nonnull target, id _Nullable __autoreleasing * _Nonnull ivar) { if (withSync) { [[NSUserDefaults standardUserDefaults] synchronize]; } return [[NSUserDefaults standardUserDefaults] objectForKey:key]; - }, ^(id target, id *ivar, id newValue) { - [[NSUserDefaults standardUserDefaults] setValue:newValue forKey:key]; + }), DISetterWithOriginalMake(^(id _Nonnull target, id _Nullable __autoreleasing * _Nonnull ivar, id _Nonnull value, void (* _Nullable originalSetter)(id _Nonnull __strong, SEL _Nonnull, id _Nullable __strong)) { + [[NSUserDefaults standardUserDefaults] setObject:value forKey:key]; if (withSync) { [[NSUserDefaults standardUserDefaults] synchronize]; } - }]; + if (originalSetter) { + originalSetter(target, setter, value); + } + })]; } conformingProtocol:protocol]; } diff --git a/DeluxeInjection/Classes/DIDeluxeInjection.h b/DeluxeInjection/Classes/DIDeluxeInjection.h index e1f9d73..bfa7c13 100644 --- a/DeluxeInjection/Classes/DIDeluxeInjection.h +++ b/DeluxeInjection/Classes/DIDeluxeInjection.h @@ -23,6 +23,9 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Block types +typedef id _Nullable (*DIOriginalGetter)(id target, SEL cmd); +typedef void (*DIOriginalSetter)(id target, SEL cmd, id _Nullable value); + /** * Block to be injected instead of property getter * @@ -31,26 +34,19 @@ NS_ASSUME_NONNULL_BEGIN * * @return Injected value or \c [DeluxeInjection \c doNotInject] instance to not inject this property */ -typedef id _Nullable (^DIGetter)(id target, id _Nullable * _Nonnull ivar); +typedef id _Nullable (^DIGetter)(id target, id _Nullable * _Nonnull ivar, DIOriginalGetter _Nullable originalGetter); +typedef id _Nullable (^DIGetterWithoutOriginal)(id target, id _Nullable * _Nonnull ivar); +typedef id _Nullable (^DIGetterWithoutIvar)(id target); /** * Block to be injected instead of property setter * * @param target Receiver of selector * @param ivar Pointer to instance variable - * - * @return Injected value or \c [DeluxeInjection \c doNotInject] instance to not inject this property */ -typedef void (^DISetter)(id target, id _Nullable * _Nonnull ivar, id value); - -/** - * Block to be injected instead of property getter - * - * @param target Receiver of selector - * - * @return Injected value or \c nil - */ -typedef id _Nullable (^DIGetterWithoutIvar)(id target); +typedef void (^DISetter)(id target, id _Nullable * _Nonnull ivar, id value, DIOriginalSetter _Nullable originalSetter); +typedef void (^DISetterWithoutOriginal)(id target, id _Nullable * _Nonnull ivar, id value); +typedef void (^DISetterWithoutIvar)(id target, id value); /** * Block to be injected for property @@ -119,8 +115,10 @@ typedef BOOL (^DIPropertyFilter)(Class targetClass, NSString *propertyName, Clas /** * Helper methods to create DIGetter and DISetter with Xcode autocomplete :) */ -DIGetter DIGetterMake(DIGetter getter); -DISetter DISetterMake(DISetter setter); +DIGetter DIGetterMake(DIGetterWithoutOriginal getter); +DISetter DISetterMake(DISetterWithoutOriginal setter); +DIGetter DIGetterWithOriginalMake(DIGetter getter); +DISetter DISetterWithOriginalMake(DISetter setter); /** * Transforms getter block without \c ivar argument to block with \c ivar argument this way: @@ -155,9 +153,7 @@ id DIGetterSuperCall(id target, Class klass, SEL getter); * * @param target Target to call * @param klass Class of current setter implementation - * @param getter Selector to call - * - * @return Return value be supers setter + * @param setter Selector to call */ void DISetterSuperCall(id target, Class klass, SEL setter, id value); @@ -198,7 +194,7 @@ void DISetterSuperCall(id target, Class klass, SEL setter, id value); * @param setter Class property setter to inject * @param setterBlock Block to be injected into setter */ -+ (void)inject:(Class)klass setter:(SEL)getter setterBlock:(DISetter)setterBlock; ++ (void)inject:(Class)klass setter:(SEL)setter setterBlock:(DISetter)setterBlock; /** * Reject concrete property injection diff --git a/DeluxeInjection/Classes/DIDeluxeInjection.m b/DeluxeInjection/Classes/DIDeluxeInjection.m index e0ad9cf..756df37 100644 --- a/DeluxeInjection/Classes/DIDeluxeInjection.m +++ b/DeluxeInjection/Classes/DIDeluxeInjection.m @@ -115,21 +115,33 @@ static void DIAssociatesRemove(Class class, SEL getter) { // -DIGetter DIGetterMake(DIGetter getter) { +DIGetter DIGetterMake(DIGetterWithoutOriginal getter) { + return DIGetterWithOriginalMake(^id _Nullable(id _Nonnull target, id _Nullable __autoreleasing * _Nonnull ivar, id _Nullable (* _Nullable originalGetter)(id _Nonnull __strong, SEL _Nonnull)) { + return getter(target, ivar); + }); +} + +DISetter DISetterMake(DISetterWithoutOriginal setter) { + return DISetterWithOriginalMake(^(id _Nonnull target, id _Nullable __autoreleasing * _Nonnull ivar, id _Nonnull value, void (* _Nullable originalSetter)(id _Nonnull __strong, SEL _Nonnull, id _Nullable __strong)) { + return setter(target, ivar, value); + }); +} + +DIGetter DIGetterWithOriginalMake(DIGetter getter) { return [getter copy]; } -DISetter DISetterMake(DISetter setter) { +DISetter DISetterWithOriginalMake(DISetter setter) { return [setter copy]; } DIGetter DIGetterIfIvarIsNil(DIGetterWithoutIvar getter) { - return ^id(id target, id *ivar) { + return DIGetterWithOriginalMake(^id _Nullable(id _Nonnull target, id _Nullable __autoreleasing * _Nonnull ivar, id _Nullable (* _Nullable originalGetter)(id _Nonnull __strong, SEL _Nonnull)) { if (*ivar == nil) { *ivar = getter(target); } return *ivar; - }; + }); } id DIGetterSuperCall(id target, Class class, SEL getter) { @@ -224,9 +236,36 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe } } + Method getterMethod = class_getInstanceMethod(class, getter); + if (getterMethod) { + NSAssert(RRMethodGetArgumentsCount(getterMethod) == 0, + @"Getter should not have any arguments"); + NSAssert([RRMethodGetReturnType(getterMethod) isEqualToString:@"@"], + @"DeluxeInjection do not support non-object properties injections"); + } + + Method setterMethod = class_getInstanceMethod(class, setter); + if (setterMethod) { + NSAssert([RRMethodGetReturnType(setterMethod) isEqualToString:@"v"], + @"Setter should return void"); + NSAssert(RRMethodGetArgumentsCount(setterMethod) == 1, + @"Setter should have exactly one argument"); + NSAssert([RRMethodGetArgumentType(setterMethod, 0) isEqualToString:@"@"], + @"DeluxeInjection do not support non-object properties injections"); + } + + DIOriginalGetter originalGetterIMP = (DIOriginalGetter)(DIInjectionsGettersBackupRead(class, getter) ?: method_getImplementation(getterMethod)); + DIOriginalSetter originalSetterIMP = (DIOriginalSetter)(DIInjectionsSettersBackupRead(class, setter) ?: method_getImplementation(setterMethod)); + if (originalGetterIMP == DINothingToRestore) { + originalGetterIMP = nil; + } + if (originalSetterIMP == DINothingToRestore) { + originalSetterIMP = nil; + } + NSString *propertyIvarStr = RRPropertyGetAttribute(property, "V"); Ivar propertyIvar = propertyIvarStr ? class_getInstanceVariable(class, propertyIvarStr.UTF8String) : nil; - + BOOL isAssociated = (propertyIvar == nil); SEL associationKey = NSSelectorFromString([@"DI_" stringByAppendingString:propertyName]); objc_AssociationPolicy associationPolicy = RRPropertyGetAssociationPolicy(property); @@ -237,7 +276,7 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe if (!isAssociated) { newGetterBlock = ^id(id target) { id ivar = object_getIvar(target, propertyIvar); - id result = getterToInject(target, &ivar); + id result = getterToInject(target, &ivar, originalGetterIMP); object_setIvar(target, propertyIvar, ivar); return result; }; @@ -252,7 +291,7 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe } id ivar = ((DIWeakWrapper *)wrapper)->object; BOOL ivarWasNil = (ivar == nil); - id result = getterToInject(target, &ivar); + id result = getterToInject(target, &ivar, originalGetterIMP); if (ivar && ivarWasNil) { DIAssociatesWrite(class, getter, target); } @@ -267,7 +306,7 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe newGetterBlock = ^id(id target) { id ivar = objc_getAssociatedObject(target, associationKey); BOOL ivarWasNil = (ivar == nil); - id result = getterToInject(target, &ivar); + id result = getterToInject(target, &ivar, originalGetterIMP); if (ivar && ivarWasNil) { DIAssociatesWrite(class, getter, target); } @@ -283,7 +322,7 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe if (!isAssociated) { newSetterBlock = ^void(id target, id newValue) { id ivar = object_getIvar(target, propertyIvar); - setterToInject(target, &ivar, newValue); + setterToInject(target, &ivar, newValue, originalSetterIMP); object_setIvar(target, propertyIvar, ivar); }; } @@ -293,7 +332,7 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe DIWeakWrapper *wrapper = objc_getAssociatedObject(target, associationKey) ?: [[DIWeakWrapper alloc] init]; id ivar = wrapper->object; BOOL ivarWasNil = (ivar == nil); - setterToInject(target, &ivar, newValue); + setterToInject(target, &ivar, newValue, originalSetterIMP); if (ivar && ivarWasNil) { DIAssociatesWrite(class, getter, target); } @@ -305,7 +344,7 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe newSetterBlock = ^void(id target, id newValue) { id ivar = objc_getAssociatedObject(target, associationKey); BOOL ivarWasNil = (ivar == nil); - setterToInject(target, &ivar, newValue); + setterToInject(target, &ivar, newValue, originalSetterIMP); if (ivar && ivarWasNil) { DIAssociatesWrite(class, getter, target); } @@ -316,14 +355,6 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe } if (getterToInject) { - Method getterMethod = class_getInstanceMethod(class, getter); - if (getterMethod) { - NSAssert(RRMethodGetArgumentsCount(getterMethod) == 0, - @"Getter should not have any arguments"); - NSAssert([RRMethodGetReturnType(getterMethod) isEqualToString:@"@"], - @"DeluxeInjection do not support non-object properties injections"); - } - IMP newGetterImp = imp_implementationWithBlock(newGetterBlock); const char *getterTypes = method_getTypeEncoding(class_getInstanceMethod(self, @selector(getterExample))); IMP replacedGetterImp = class_replaceMethod(class, getter, newGetterImp, getterTypes); @@ -349,16 +380,6 @@ + (void)inject:(Class)class property:(objc_property_t)property getterBlock:(DIGe } if (newSetterBlock) { - Method setterMethod = class_getInstanceMethod(class, setter); - if (setterMethod) { - NSAssert([RRMethodGetReturnType(setterMethod) isEqualToString:@"v"], - @"Setter should return void"); - NSAssert(RRMethodGetArgumentsCount(setterMethod) == 1, - @"Setter should have exactly one argument"); - NSAssert([RRMethodGetArgumentType(setterMethod, 0) isEqualToString:@"@"], - @"DeluxeInjection do not support non-object properties injections"); - } - IMP newSetterImp = imp_implementationWithBlock(newSetterBlock); const char *setterTypes = method_getTypeEncoding(class_getInstanceMethod(self, @selector(setterExample:))); IMP replacedSetterImp = class_replaceMethod(class, setter, newSetterImp, setterTypes); diff --git a/Example/DeluxeInjection.xcodeproj/project.pbxproj b/Example/DeluxeInjection.xcodeproj/project.pbxproj index f16458a..e0fe587 100644 --- a/Example/DeluxeInjection.xcodeproj/project.pbxproj +++ b/Example/DeluxeInjection.xcodeproj/project.pbxproj @@ -431,6 +431,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; @@ -471,6 +472,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INT_CONVERSION = YES; @@ -532,11 +534,6 @@ baseConfigurationReference = DE9A9F81BBBE43EA4D1527E8 /* Pods-DeluxeInjection_Tests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - "$(DEVELOPER_FRAMEWORKS_DIR)", - ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; GCC_PREPROCESSOR_DEFINITIONS = ( @@ -556,11 +553,6 @@ baseConfigurationReference = 7E0D553882C7D23CCDB521D0 /* Pods-DeluxeInjection_Tests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; - FRAMEWORK_SEARCH_PATHS = ( - "$(SDKROOT)/Developer/Library/Frameworks", - "$(inherited)", - "$(DEVELOPER_FRAMEWORKS_DIR)", - ); GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch"; INFOPLIST_FILE = "Tests/Tests-Info.plist";