From 99d3dc72d1079d8ee3c8e6d87f7438badd65b954 Mon Sep 17 00:00:00 2001 From: Oliver Letterer Date: Mon, 3 Sep 2012 10:43:03 +0200 Subject: [PATCH] Adds new runtime addition void class_swizzleSelectorWithBlock(Class class, SEL originalSelector, SEL unusedSelector, id block);. --- .../project.pbxproj | 99 +++++++ .../CTBlockDescription.h | 48 ++++ .../CTBlockDescription.m | 74 +++++ .../CTObjectiveCRuntimeAdditions.h | 6 + .../CTObjectiveCRuntimeAdditions.m | 33 +++ .../CTSwizzleBlockImplementation.h | 24 ++ .../CTSwizzleBlockImplementation.m | 253 ++++++++++++++++++ .../CTObjectiveCRuntimeAdditionsTests.m | 60 +++++ .../CTTestSwizzleClass.h | 6 +- .../CTTestSwizzleClass.m | 10 + 10 files changed, 612 insertions(+), 1 deletion(-) create mode 100644 CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTBlockDescription.h create mode 100644 CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTBlockDescription.m create mode 100644 CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTSwizzleBlockImplementation.h create mode 100644 CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTSwizzleBlockImplementation.m diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.xcodeproj/project.pbxproj b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.xcodeproj/project.pbxproj index 55e9efb..152a0d5 100644 --- a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.xcodeproj/project.pbxproj +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.xcodeproj/project.pbxproj @@ -7,6 +7,12 @@ objects = { /* Begin PBXBuildFile section */ + A7620EDE15F359680016F414 /* CTBlockDescription.h in Headers */ = {isa = PBXBuildFile; fileRef = A7620EDC15F359680016F414 /* CTBlockDescription.h */; }; + A7620EDF15F359680016F414 /* CTBlockDescription.m in Sources */ = {isa = PBXBuildFile; fileRef = A7620EDD15F359680016F414 /* CTBlockDescription.m */; }; + A7620EE315F360700016F414 /* CTSwizzleBlockImplementation.h in Headers */ = {isa = PBXBuildFile; fileRef = A7620EE115F360700016F414 /* CTSwizzleBlockImplementation.h */; }; + A7620EE415F360700016F414 /* CTSwizzleBlockImplementation.m in Sources */ = {isa = PBXBuildFile; fileRef = A7620EE215F360700016F414 /* CTSwizzleBlockImplementation.m */; }; + A7620F2915F36CEF0016F414 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A7620F2415F36CDF0016F414 /* libffi.a */; }; + A7620F2B15F394430016F414 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7620F2A15F394430016F414 /* CoreGraphics.framework */; }; A76AC25B154BDACB00B93FEF /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A76AC25A154BDACB00B93FEF /* Foundation.framework */; }; A76AC261154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A76AC260154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions.m */; }; A76AC269154BDACC00B93FEF /* SenTestingKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A76AC268154BDACC00B93FEF /* SenTestingKit.framework */; }; @@ -19,6 +25,27 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ + A7620F2315F36CDF0016F414 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A7620F1B15F36CDF0016F414 /* libffi.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = F6F980BA147386130008F121; + remoteInfo = "libffi iOS"; + }; + A7620F2515F36CDF0016F414 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A7620F1B15F36CDF0016F414 /* libffi.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 6C43CB3D1534E9D100162364; + remoteInfo = "libffi OS X"; + }; + A7620F2715F36CE70016F414 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A7620F1B15F36CDF0016F414 /* libffi.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = F6F980B9147386130008F121; + remoteInfo = "libffi iOS"; + }; A76AC26D154BDACC00B93FEF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = A76AC24E154BDACB00B93FEF /* Project object */; @@ -29,6 +56,12 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + A7620EDC15F359680016F414 /* CTBlockDescription.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTBlockDescription.h; sourceTree = ""; }; + A7620EDD15F359680016F414 /* CTBlockDescription.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTBlockDescription.m; sourceTree = ""; }; + A7620EE115F360700016F414 /* CTSwizzleBlockImplementation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTSwizzleBlockImplementation.h; sourceTree = ""; }; + A7620EE215F360700016F414 /* CTSwizzleBlockImplementation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTSwizzleBlockImplementation.m; sourceTree = ""; }; + A7620F1B15F36CDF0016F414 /* libffi.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = libffi.xcodeproj; path = ../../libffi/libffi.xcodeproj; sourceTree = ""; }; + A7620F2A15F394430016F414 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; A76AC257154BDACB00B93FEF /* libCTObjectiveCRuntimeAdditions.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libCTObjectiveCRuntimeAdditions.a; sourceTree = BUILT_PRODUCTS_DIR; }; A76AC25A154BDACB00B93FEF /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; A76AC25E154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "CTObjectiveCRuntimeAdditions-Prefix.pch"; sourceTree = ""; }; @@ -51,6 +84,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A7620F2915F36CEF0016F414 /* libffi.a in Frameworks */, A76AC25B154BDACB00B93FEF /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -59,6 +93,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A7620F2B15F394430016F414 /* CoreGraphics.framework in Frameworks */, A76AC269154BDACC00B93FEF /* SenTestingKit.framework in Frameworks */, A76AC26C154BDACC00B93FEF /* Foundation.framework in Frameworks */, A76AC26F154BDACC00B93FEF /* libCTObjectiveCRuntimeAdditions.a in Frameworks */, @@ -68,6 +103,23 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + A7620EEF15F363AA0016F414 /* libffi */ = { + isa = PBXGroup; + children = ( + A7620F1B15F36CDF0016F414 /* libffi.xcodeproj */, + ); + name = libffi; + sourceTree = ""; + }; + A7620F1C15F36CDF0016F414 /* Products */ = { + isa = PBXGroup; + children = ( + A7620F2415F36CDF0016F414 /* libffi.a */, + A7620F2615F36CDF0016F414 /* libffi.a */, + ); + name = Products; + sourceTree = ""; + }; A76AC24C154BDACB00B93FEF = { isa = PBXGroup; children = ( @@ -90,6 +142,7 @@ A76AC259154BDACB00B93FEF /* Frameworks */ = { isa = PBXGroup; children = ( + A7620F2A15F394430016F414 /* CoreGraphics.framework */, A76AC25A154BDACB00B93FEF /* Foundation.framework */, A76AC268154BDACC00B93FEF /* SenTestingKit.framework */, ); @@ -99,6 +152,11 @@ A76AC25C154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions */ = { isa = PBXGroup; children = ( + A7620EEF15F363AA0016F414 /* libffi */, + A7620EE115F360700016F414 /* CTSwizzleBlockImplementation.h */, + A7620EE215F360700016F414 /* CTSwizzleBlockImplementation.m */, + A7620EDC15F359680016F414 /* CTBlockDescription.h */, + A7620EDD15F359680016F414 /* CTBlockDescription.m */, A76AC25F154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions.h */, A76AC260154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions.m */, A76AC25D154BDACB00B93FEF /* Supporting Files */, @@ -144,6 +202,8 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + A7620EDE15F359680016F414 /* CTBlockDescription.h in Headers */, + A7620EE315F360700016F414 /* CTSwizzleBlockImplementation.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -161,6 +221,7 @@ buildRules = ( ); dependencies = ( + A7620F2815F36CE70016F414 /* PBXTargetDependency */, ); name = CTObjectiveCRuntimeAdditions; productName = CTObjectiveCRuntimeAdditions; @@ -206,6 +267,12 @@ mainGroup = A76AC24C154BDACB00B93FEF; productRefGroup = A76AC258154BDACB00B93FEF /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = A7620F1C15F36CDF0016F414 /* Products */; + ProjectRef = A7620F1B15F36CDF0016F414 /* libffi.xcodeproj */; + }, + ); projectRoot = ""; targets = ( A76AC256154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions */, @@ -214,6 +281,23 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + A7620F2415F36CDF0016F414 /* libffi.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libffi.a; + remoteRef = A7620F2315F36CDF0016F414 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + A7620F2615F36CDF0016F414 /* libffi.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libffi.a; + remoteRef = A7620F2515F36CDF0016F414 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ A76AC264154BDACC00B93FEF /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -247,6 +331,8 @@ buildActionMask = 2147483647; files = ( A76AC261154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions.m in Sources */, + A7620EDF15F359680016F414 /* CTBlockDescription.m in Sources */, + A7620EE415F360700016F414 /* CTSwizzleBlockImplementation.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -263,6 +349,11 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ + A7620F2815F36CE70016F414 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "libffi iOS"; + targetProxy = A7620F2715F36CE70016F414 /* PBXContainerItemProxy */; + }; A76AC26E154BDACC00B93FEF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = A76AC256154BDACB00B93FEF /* CTObjectiveCRuntimeAdditions */; @@ -305,6 +396,10 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/../libffi/ios/include\"", + ); IPHONEOS_DEPLOYMENT_TARGET = 4.0; SDKROOT = iphoneos; }; @@ -326,6 +421,10 @@ GCC_WARN_ABOUT_RETURN_TYPE = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"$(SRCROOT)/../libffi/ios/include\"", + ); IPHONEOS_DEPLOYMENT_TARGET = 4.0; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTBlockDescription.h b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTBlockDescription.h new file mode 100644 index 0000000..c084bdd --- /dev/null +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTBlockDescription.h @@ -0,0 +1,48 @@ +// +// CTBlockDescription.h +// CTBlockDescription +// +// Created by Oliver Letterer on 01.09.12. +// Copyright (c) 2012 olettere. All rights reserved. +// + +struct CTBlockLiteral { + void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock + int flags; + int reserved; + void (*invoke)(void *, ...); + struct block_descriptor { + unsigned long int reserved; // NULL + unsigned long int size; // sizeof(struct Block_literal_1) + // optional helper functions + void (*copy_helper)(void *dst, void *src); // IFF (1<<25) + void (*dispose_helper)(void *src); // IFF (1<<25) + // required ABI.2010.3.16 + const char *signature; // IFF (1<<30) + } *descriptor; + // imported variables +}; + +enum { + CTBlockDescriptionFlagsHasCopyDispose = (1 << 25), + CTBlockDescriptionFlagsHasCtor = (1 << 26), // helpers have C++ code + CTBlockDescriptionFlagsIsGlobal = (1 << 28), + CTBlockDescriptionFlagsHasStret = (1 << 29), // IFF BLOCK_HAS_SIGNATURE + CTBlockDescriptionFlagsHasSignature = (1 << 30) +}; +typedef int CTBlockDescriptionFlags; + + + +@interface CTBlockDescription : NSObject + +@property (nonatomic, readonly) CTBlockDescriptionFlags flags; +@property (nonatomic, readonly) NSMethodSignature *blockSignature; +@property (nonatomic, readonly) unsigned long int size; +@property (nonatomic, readonly) id block; + +- (id)initWithBlock:(id)block; + +- (BOOL)isCompatibleForBlockSwizzlingWithMethodSignature:(NSMethodSignature *)methodSignature; + +@end diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTBlockDescription.m b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTBlockDescription.m new file mode 100644 index 0000000..280ace4 --- /dev/null +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTBlockDescription.m @@ -0,0 +1,74 @@ +// +// CTBlockDescription.m +// CTBlockDescription +// +// Created by Oliver Letterer on 01.09.12. +// Copyright (c) 2012 olettere. All rights reserved. +// + +#import "CTBlockDescription.h" + +@implementation CTBlockDescription + +- (id)initWithBlock:(id)block +{ + if (self = [super init]) { + _block = block; + + struct CTBlockLiteral *blockRef = (__bridge struct CTBlockLiteral *)block; + _flags = blockRef->flags; + _size = blockRef->descriptor->size; + + if (_flags & CTBlockDescriptionFlagsHasSignature) { + void *signatureLocation = blockRef->descriptor; + signatureLocation += sizeof(unsigned long int); + signatureLocation += sizeof(unsigned long int); + + if (_flags & CTBlockDescriptionFlagsHasCopyDispose) { + signatureLocation += sizeof(void(*)(void *dst, void *src)); + signatureLocation += sizeof(void (*)(void *src)); + } + + const char *signature = (*(const char **)signatureLocation); + _blockSignature = [NSMethodSignature signatureWithObjCTypes:signature]; + } + } + return self; +} + +- (BOOL)isCompatibleForBlockSwizzlingWithMethodSignature:(NSMethodSignature *)methodSignature +{ + if (_blockSignature.numberOfArguments != methodSignature.numberOfArguments + 1) { + return NO; + } + + if (strcmp(_blockSignature.methodReturnType, methodSignature.methodReturnType) != 0) { + return NO; + } + + for (int i = 0; i < methodSignature.numberOfArguments; i++) { + if (i == 1) { + // SEL in method, IMP in block + if (strcmp([methodSignature getArgumentTypeAtIndex:i], ":") != 0) { + return NO; + } + + if (strcmp([_blockSignature getArgumentTypeAtIndex:i + 1], "^?") != 0) { + return NO; + } + } else { + if (strcmp([methodSignature getArgumentTypeAtIndex:i], [_blockSignature getArgumentTypeAtIndex:i + 1]) != 0) { + return NO; + } + } + } + + return YES; +} + +- (NSString *)description +{ + return [NSString stringWithFormat:@"%@: %@", [super description], _blockSignature.description]; +} + +@end diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.h b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.h index 053b4ba..dc3dbb1 100644 --- a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.h +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.h @@ -28,3 +28,9 @@ void class_enumerateMethodList(Class class, CTMethodEnumertor enumerator); @return A subclass of class which passes test. */ Class class_subclassPassingTest(Class class, CTClassTest test); + +/** + @abstract Swizzles originalSelector with block and places original implementation in unusedSelector. + @warning if originalSelector's argument list is (id self, SEL _cmd, ...), then block's argument list must be (id self, IMP originalImplemenation, ...) + */ +void class_swizzleSelectorWithBlock(Class class, SEL originalSelector, SEL unusedSelector, id block); diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.m b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.m index 037b922..0f4f18f 100644 --- a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.m +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions.m @@ -7,6 +7,17 @@ // #import "CTObjectiveCRuntimeAdditions.h" +#import "CTBlockDescription.h" +#import "CTSwizzleBlockImplementation.h" + +static NSMutableArray *CTBlockImplementations; + +__attribute((constructor))void CTObjectiveCRuntimeAdditionsInitialization(void) +{ + @autoreleasepool { + CTBlockImplementations = [NSMutableArray array]; + } +} void class_swizzleSelector(Class class, SEL originalSelector, SEL newSelector) { @@ -110,3 +121,25 @@ Class class_subclassPassingTest(Class class, CTClassTest test) return testPassingClass; } + +void class_swizzleSelectorWithBlock(Class class, SEL originalSelector, SEL unusedSelector, id block) +{ + NSCAssert(block != nil, @"block cannot be nil"); + + Method originalMethod = class_getInstanceMethod(class, originalSelector); + NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:method_getTypeEncoding(originalMethod)]; + + CTBlockDescription *blockDescription = [[CTBlockDescription alloc] initWithBlock:block]; + NSCAssert([blockDescription isCompatibleForBlockSwizzlingWithMethodSignature:methodSignature], @"block is not compatible for swizzling selector %@ of class %@", NSStringFromSelector(originalSelector), NSStringFromClass(class)); + + CTSwizzleBlockImplementation *blockImplementation = [[CTSwizzleBlockImplementation alloc] initWithBlock:block + methodSignature:methodSignature + swizzledSelector:unusedSelector + originalClass:class]; + [CTBlockImplementations addObject:blockImplementation]; + + BOOL success = class_addMethod(class, unusedSelector, blockImplementation.implementation, method_getTypeEncoding(originalMethod)); + NSCAssert(success, @"An implementation for selector %@ of class %@ already exists", NSStringFromSelector(unusedSelector), NSStringFromClass(class)); + + class_swizzleSelector(class, originalSelector, unusedSelector); +} diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTSwizzleBlockImplementation.h b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTSwizzleBlockImplementation.h new file mode 100644 index 0000000..a2a92a6 --- /dev/null +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTSwizzleBlockImplementation.h @@ -0,0 +1,24 @@ +// +// CTSwizzleBlockImplementation.h +// CTObjectiveCRuntimeAdditions +// +// Created by Oliver Letterer on 02.09.12. +// Copyright 2012 ebf. All rights reserved. +// + + + +/** + @abstract <#abstract comment#> + */ +@interface CTSwizzleBlockImplementation : NSObject + +- (id)initWithBlock:(id)block + methodSignature:(NSMethodSignature *)methodSignature + swizzledSelector:(SEL)swizzledSelector + originalClass:(Class)originalClass; + +@property (nonatomic, readonly) id block; +@property (nonatomic, readonly) void *implementation; + +@end diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTSwizzleBlockImplementation.m b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTSwizzleBlockImplementation.m new file mode 100644 index 0000000..ec43515 --- /dev/null +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditions/CTSwizzleBlockImplementation.m @@ -0,0 +1,253 @@ +// +// CTSwizzleBlockImplementation.m +// CTObjectiveCRuntimeAdditions +// +// Created by Oliver Letterer on 02.09.12. +// Copyright 2012 ebf. All rights reserved. +// + +#import "CTSwizzleBlockImplementation.h" +#import "CTBlockDescription.h" +#import +#import + +@interface CTSwizzleBlockImplementation () { +@package + NSMutableArray *_allocations; + void *_methodCIF; + void *_blockCIF; + void *_closure; + id _block; + + SEL _swizzledSelector; + Class _swizzledClass; + + int _numberOfMethodArguments; +} + +- (void *)_allocatePersistentMemoryWithSize:(size_t)size; +- (size_t)_getNumberOfArgumentsInStructWithEncoding:(const char *)structEncoding; +- (const char *)_skipStructNameOfStructWithEncoding:(const char *)structEncoding; +- (ffi_type *)_ffiTypeForObjcTypeWithEncoding:(const char *)typeEncoding; + +@end + +static void CTSwizzleBlockImplementationTrampoline(ffi_cif *cif, void *ret, void **args, void *userdata) { + CTSwizzleBlockImplementation *self = (__bridge CTSwizzleBlockImplementation *)userdata; + struct CTBlockLiteral *block = (__bridge struct CTBlockLiteral *)self->_block; + + void *blockArguments[self->_numberOfMethodArguments + 1]; + + blockArguments[0] = █ + + for (int i = 0; i < self->_numberOfMethodArguments; i++) { + if (i == 1) { + // this is the SEL _cmd + Method originalMethod = class_getInstanceMethod(self->_swizzledClass, self->_swizzledSelector); + IMP originalImplementation = method_getImplementation(originalMethod); + blockArguments[i + 1] = &originalImplementation; + } else { + blockArguments[i + 1] = args[i]; + } + } + + void (*blockInvoke)(void) = (void (*)(void))block->invoke; + ffi_call(self->_blockCIF, blockInvoke, ret, blockArguments); +} + + +/** + workflow: objc_msgSend jumps to _implementation. _implementation sorts function arguments and calls CTSwizzleBlockImplementationTrampoline. CTSwizzleBlockImplementationTrampoline updates function arguments to match required arguments for block and calls invoke of the block. + */ +@implementation CTSwizzleBlockImplementation + +#pragma mark - Initialization + +- (id)initWithBlock:(id)block methodSignature:(NSMethodSignature *)methodSignature swizzledSelector:(SEL)swizzledSelector originalClass:(Class)originalClass +{ + if (self = [super init]) { + _allocations = [NSMutableArray array]; + _block = block; + _swizzledSelector = swizzledSelector; + _swizzledClass = originalClass; + + _closure = ffi_closure_alloc(sizeof(ffi_closure), &_implementation); + _numberOfMethodArguments = (unsigned int)methodSignature.numberOfArguments; + unsigned int numberOfBlockArguments = _numberOfMethodArguments + 1; + + ffi_cif methodCif, blockCif; + + ffi_type **methodArguments = [self _allocatePersistentMemoryWithSize:_numberOfMethodArguments * sizeof(ffi_type *)]; + ffi_type **blockArguments = [self _allocatePersistentMemoryWithSize:numberOfBlockArguments * sizeof(ffi_type *)]; + ffi_type *returnType = [self _ffiTypeForObjcTypeWithEncoding:methodSignature.methodReturnType]; + + methodArguments[0] = methodArguments[1] = &ffi_type_pointer; + blockArguments[0] = blockArguments[1] = blockArguments[2] = &ffi_type_pointer; + + for (unsigned int i = 2; i < _numberOfMethodArguments; i++) { + methodArguments[i] = [self _ffiTypeForObjcTypeWithEncoding:[methodSignature getArgumentTypeAtIndex:i]]; + blockArguments[i + 1] = methodArguments[i]; + } + + ffi_status methodStatus = ffi_prep_cif(&methodCif, FFI_DEFAULT_ABI, _numberOfMethodArguments, returnType, methodArguments); + ffi_status blockStatus = ffi_prep_cif(&blockCif, FFI_DEFAULT_ABI, numberOfBlockArguments, returnType, blockArguments); + + NSAssert(methodStatus == FFI_OK, @"Unable to create function interface for method. %@ %@", [self class], self.block); + NSAssert(blockStatus == FFI_OK, @"Unable to create function interface for block. %@ %@", [self class], self.block); + + _methodCIF = malloc(sizeof(ffi_cif)); + *(ffi_cif *)_methodCIF = methodCif; + + _blockCIF = malloc(sizeof(ffi_cif)); + *(ffi_cif *)_blockCIF = blockCif; + + ffi_status status = ffi_prep_closure_loc(_closure, _methodCIF, CTSwizzleBlockImplementationTrampoline, (__bridge void *)self, _implementation); + + NSAssert(status == FFI_OK, @"Unable to create function closure for block. %@ %@", [self class], self.block); + } + return self; +} + +#pragma mark - Memory management + +- (void)dealloc +{ + if(_closure) { + ffi_closure_free(_closure); + } + if (_methodCIF) { + free(_methodCIF); + } + if (_blockCIF) { + free(_blockCIF); + } +} + +#pragma mark - Private category implementation () + +- (void *)_allocatePersistentMemoryWithSize:(size_t)size +{ + NSMutableData *data = [NSMutableData dataWithLength:size]; + [_allocations addObject:data]; + return data.mutableBytes; +} + +- (size_t)_getNumberOfArgumentsInStructWithEncoding:(const char *)structEncoding +{ + if (*structEncoding != _C_STRUCT_B) { + return -1; + } + + while (*structEncoding != _C_STRUCT_E && *structEncoding++ != '='); // skip "=" + + size_t numberOfArguments = 0; + while (*structEncoding != _C_STRUCT_E) { + structEncoding = NSGetSizeAndAlignment(structEncoding, NULL, NULL); + numberOfArguments++; + } + + return numberOfArguments; +} + +- (const char *)_skipStructNameOfStructWithEncoding:(const char *)structEncoding +{ + if (*structEncoding == _C_STRUCT_B) { + structEncoding++; + } + + if (*structEncoding == _C_UNDEF) { + structEncoding++; + } else if (isalpha(*structEncoding) || *structEncoding == '_') { + while (isalnum(*structEncoding) || *structEncoding == '_') { + structEncoding++; + } + } else { + return structEncoding; + } + + if (*structEncoding == '=') { + structEncoding++; + } + + return structEncoding; +} + +- (ffi_type *)_ffiTypeForObjcTypeWithEncoding:(const char *)typeEncoding +{ + switch (*typeEncoding) { + case _C_ID: + case _C_CLASS: + case _C_SEL: + case _C_ATOM: + case _C_CHARPTR: + case _C_PTR: + return &ffi_type_pointer; break; + case _C_BOOL: + case _C_UCHR: + return &ffi_type_uchar; break; + case _C_CHR: return &ffi_type_schar; break; + case _C_SHT: return &ffi_type_sshort; break; + case _C_USHT: return &ffi_type_ushort; break; + case _C_INT: return &ffi_type_sint; break; + case _C_UINT: return &ffi_type_uint; break; + case _C_LNG: return &ffi_type_slong; break; + case _C_ULNG: return &ffi_type_ulong; break; + case _C_LNG_LNG: return &ffi_type_sint64; break; + case _C_ULNG_LNG: return &ffi_type_uint64; break; + case _C_FLT: return &ffi_type_float; break; + case _C_DBL: return &ffi_type_double; break; + case _C_VOID: return &ffi_type_void; break; + case _C_BFLD: + case _C_ARY_B: { + NSUInteger size, align; + + NSGetSizeAndAlignment(typeEncoding, &size, &align); + + if (size > 0) { + if (size == 1) + return &ffi_type_uchar; + else if (size == 2) + return &ffi_type_ushort; + else if (size <= 4) + return &ffi_type_uint; + else { + ffi_type *type = [self _allocatePersistentMemoryWithSize:sizeof(ffi_type)]; + type->size = size; + type->alignment = align; + type->type = FFI_TYPE_STRUCT; + type->elements = [self _allocatePersistentMemoryWithSize:(size + 1) * sizeof(ffi_type *)]; + for (NSUInteger i = 0; i < size; i++) { + type->elements[i] = &ffi_type_uchar; + } + type->elements[size] = NULL; + return type; + } + break; + } + } case _C_STRUCT_B: { + ffi_type *type = [self _allocatePersistentMemoryWithSize:sizeof(ffi_type)]; + type->size = 0; + type->alignment = 0; + type->type = FFI_TYPE_STRUCT; + type->elements = [self _allocatePersistentMemoryWithSize:([self _getNumberOfArgumentsInStructWithEncoding:typeEncoding] + 1) * sizeof(ffi_type *)]; + + size_t index = 0; + typeEncoding = [self _skipStructNameOfStructWithEncoding:typeEncoding]; + while (*typeEncoding != _C_STRUCT_E) { + type->elements[index] = [self _ffiTypeForObjcTypeWithEncoding:typeEncoding]; + typeEncoding = NSGetSizeAndAlignment(typeEncoding, NULL, NULL); + index++; + } + + return type; + break; + } + default: { + NSAssert(NO, @"Unknown typeEncoding %s", typeEncoding); + return &ffi_type_void; + break; + } + } +} + +@end diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTObjectiveCRuntimeAdditionsTests.m b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTObjectiveCRuntimeAdditionsTests.m index 73e5f3b..4c54668 100644 --- a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTObjectiveCRuntimeAdditionsTests.m +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTObjectiveCRuntimeAdditionsTests.m @@ -10,6 +10,7 @@ #import "CTObjectiveCRuntimeAdditions.h" #import "CTTestSwizzleClass.h" #import "CTTestSwizzleSubclass.h" +#import "CTBlockDescription.h" @implementation CTObjectiveCRuntimeAdditionsTests @@ -27,6 +28,65 @@ - (void)tearDown [super tearDown]; } +- (void)testBlockDescription +{ + BOOL(^testBlock)(BOOL animated, id object) = ^BOOL(BOOL animated, id object) { + return YES; + }; + + CTBlockDescription *blockDescription = [[CTBlockDescription alloc] initWithBlock:testBlock]; + NSMethodSignature *methodSignature = blockDescription.blockSignature; + + STAssertEquals(strcmp(methodSignature.methodReturnType, @encode(BOOL)), 0, @"return type wrong"); + + const char *expectedArguments[] = {@encode(typeof(testBlock)), @encode(BOOL), @encode(id)}; + for (int i = 0; i < blockDescription.blockSignature.numberOfArguments; i++) { + STAssertEquals(strcmp([blockDescription.blockSignature getArgumentTypeAtIndex:i], expectedArguments[i]), 0, @"Argument %d wrong", i); + } +} + +- (void)testBlockSwizzling +{ + CTTestSwizzleClass *testObject = [[CTTestSwizzleClass alloc] init]; + NSString *originalString = [testObject helloWorldStringFromString:@"Oli"]; + + STAssertEqualObjects(originalString, @"Hello World Oli", @"Original helloWorldString wrong"); + + STAssertThrows(class_swizzleSelectorWithBlock([CTTestSwizzleClass class], @selector(helloWorldStringFromString:), @selector(helloWorldStringFromString2:), ^NSString *(NSString *object) { + return nil; + }), @"should not swizzle block with wrong signature"); + + + class_swizzleSelectorWithBlock([CTTestSwizzleClass class], @selector(helloWorldStringFromString:), @selector(helloWorldStringFromString2:), ^NSString *(CTTestSwizzleClass *blockSelf, IMP originalImplementation, NSString *string) { + + STAssertEqualObjects(blockSelf.class, [CTTestSwizzleClass class], @"blockSelf is wrong"); + return [originalImplementation(blockSelf, @selector(helloWorldStringFromString2:), string) stringByAppendingFormat:@" Hooked"]; + }); + + STAssertEqualObjects([testObject helloWorldStringFromString:@"Oli"], @"Hello World Oli Hooked", @"did not swizzle with block"); + + class_swizzleSelectorWithBlock([CTTestSwizzleClass class], @selector(helloWorldStringFromString:), @selector(helloWorldStringFromString3:), ^NSString *(CTTestSwizzleClass *blockSelf, IMP originalImplementation, NSString *string) { + + STAssertTrue([blockSelf isKindOfClass:[CTTestSwizzleClass class]], @"blockSelf is wrong"); + return [originalImplementation(blockSelf, @selector(helloWorldStringFromString3:), string) stringByAppendingFormat:@" Hooked2"]; + }); + + STAssertEqualObjects([testObject helloWorldStringFromString:@"Oli"], @"Hello World Oli Hooked Hooked2", @"did not swizzle with block"); + + + // test structs + STAssertEquals(CGPointMake(2.0f, 2.0f), [testObject pointByAddingPoint:CGPointMake(1.0f, 1.0f)], @"initial point wrong"); + + class_swizzleSelectorWithBlock([CTTestSwizzleClass class], @selector(pointByAddingPoint:), @selector(pointByAddingPoint1:), ^CGPoint(CTTestSwizzleClass *blockSelf, CGPoint(*originalImplementation)(id, SEL, CGPoint), CGPoint point) { + STAssertTrue([blockSelf isKindOfClass:[CTTestSwizzleClass class]], @"blockSelf is wrong"); + + CGPoint originalPoint = originalImplementation(blockSelf, @selector(pointByAddingPoint1:), point); + return CGPointMake(originalPoint.x + 1.0f, originalPoint.y + 1.0f); + }); + + STAssertEquals(CGPointMake(3.0f, 3.0f), [testObject pointByAddingPoint:CGPointMake(1.0f, 1.0f)], @"initial point wrong"); +} + - (void)testMethodSwizzling { CTTestSwizzleClass *testObject = [[CTTestSwizzleClass alloc] init]; diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTTestSwizzleClass.h b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTTestSwizzleClass.h index a7e95f3..43d1185 100644 --- a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTTestSwizzleClass.h +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTTestSwizzleClass.h @@ -6,7 +6,7 @@ // Copyright 2012 ebf. All rights reserved. // - +#import /** @abstract <#abstract comment#> @@ -32,4 +32,8 @@ + (BOOL)passesTest; +- (NSString *)helloWorldStringFromString:(NSString *)string; + +- (CGPoint)pointByAddingPoint:(CGPoint)point; + @end diff --git a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTTestSwizzleClass.m b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTTestSwizzleClass.m index 7bfe6e3..94aefa9 100644 --- a/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTTestSwizzleClass.m +++ b/CTObjectiveCRuntimeAdditions/CTObjectiveCRuntimeAdditionsTests/CTTestSwizzleClass.m @@ -32,6 +32,16 @@ - (id)init #pragma mark - Instance methods +- (CGPoint)pointByAddingPoint:(CGPoint)point +{ + return CGPointMake(point.x + 1.0f, point.y + 1.0f); +} + +- (NSString *)helloWorldStringFromString:(NSString *)string +{ + return [@"Hello World" stringByAppendingFormat:@" %@", string]; +} + - (NSString *)orginalString { return @"foo";