Skip to content

Commit

Permalink
Add ability to call original getter/setter implementation from inject…
Browse files Browse the repository at this point in the history
…ed method
  • Loading branch information
k06a committed May 29, 2016
1 parent 6b43173 commit d5cff7e
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 63 deletions.
2 changes: 1 addition & 1 deletion DeluxeInjection.podspec
Original file line number Diff line number Diff line change
@@ -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
Expand Down
11 changes: 7 additions & 4 deletions DeluxeInjection/Classes/DIDefaults.m
Original file line number Diff line number Diff line change
Expand Up @@ -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<Protocol *> *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];
}

Expand Down
34 changes: 15 additions & 19 deletions DeluxeInjection/Classes/DIDeluxeInjection.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down
79 changes: 50 additions & 29 deletions DeluxeInjection/Classes/DIDeluxeInjection.m
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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;
};
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
};
}
Expand All @@ -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);
}
Expand All @@ -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);
}
Expand All @@ -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);
Expand All @@ -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);
Expand Down
12 changes: 2 additions & 10 deletions Example/DeluxeInjection.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 = (
Expand All @@ -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";
Expand Down

0 comments on commit d5cff7e

Please sign in to comment.