-
-
Save steipete/1d308fad786399b58875cd12e4b9bba2 to your computer and use it in GitHub Desktop.
// http://defagos.github.io/yet_another_article_about_method_swizzling/ (Thank you!!) | |
// Returns the original implementation | |
static _Nullable IMP pspdf_swizzleSelector(Class clazz, SEL selector, IMP newImplementation) { | |
NSCParameterAssert(clazz); | |
NSCParameterAssert(selector); | |
NSCParameterAssert(newImplementation); | |
// If the method does not exist for this class, do nothing. | |
const Method method = class_getInstanceMethod(clazz, selector); | |
if (!method) { | |
PSPDFLogError(@"%@ doesn't exist in %@.", NSStringFromSelector(selector), NSStringFromClass(clazz)); | |
// Cannot swizzle methods which are not implemented by the class or one of its parents. | |
return NULL; | |
} | |
// Make sure the class implements the method. If this is not the case, inject an implementation, only calling 'super'. | |
const char *types = method_getTypeEncoding(method); | |
@synchronized(clazz) { | |
// class_addMethod will simply return NO if the method is already implemented. | |
#if !defined(__arm64__) | |
// Sufficiently large struct | |
typedef struct LargeStruct_ { char dummy[16]; } LargeStruct; | |
NSUInteger retSize = 0; | |
NSGetSizeAndAlignment(types, &retSize, NULL); | |
// Large structs on 32-bit architectures | |
// TODO: This is incorrect for some structs on some architectures. Needs to be hardcoded, this cannot be safely inferred at runtime. | |
// https://twitter.com/gparker/status/1028564412339113984 | |
if (sizeof(void *) == 4 && types[0] == _C_STRUCT_B && retSize != 1 && retSize != 2 && retSize != 4 && retSize != 8) { | |
class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) { | |
struct objc_super super = {self, clazz}; | |
return ((LargeStruct(*)(struct objc_super *, SEL, va_list))objc_msgSendSuper2_stret)(&super, selector, argp); | |
}), types); | |
} | |
// All other cases | |
else { | |
#endif | |
class_addMethod(clazz, selector, imp_implementationWithBlock(^(__unsafe_unretained id self, va_list argp) { | |
struct objc_super super = {self, clazz}; | |
return ((id(*)(struct objc_super *, SEL, va_list))objc_msgSendSuper2)(&super, selector, argp); | |
}), types); | |
#if !defined(__arm64__) | |
} | |
#endif | |
// Swizzling | |
return class_replaceMethod(clazz, selector, newImplementation, types); | |
} | |
} | |
_Nullable IMP pspdf_swizzleSelectorWithBlock(Class clazz, SEL selector, id newImplementationBlock) { | |
const IMP newImplementation = imp_implementationWithBlock(newImplementationBlock); | |
return pspdf_swizzleSelector(clazz, selector, newImplementation); | |
} |
Thanks @steipete!
PS: If you’re trying to use this and it won’t compile, be sure to add these:
#import <objc/runtime.h>
#import <objc/message.h>
OBJC_EXPORT id objc_msgSendSuper2(struct objc_super *super, SEL op, ...);
OBJC_EXPORT void objc_msgSendSuper2_stret(struct objc_super *super, SEL op,...);
Does PSPDF team actually use this snippet for swizzling? On arm64 platform, if the original method returns a large c struct, this implementation will use objc_msgSendSuper2
instead of objc_msgSendSuper2_stret
. There may be more cases we need to consider.
Hi there! We don’t use it for struct returns currently so this is an unused code path. There’s an internal ticket for adding these edge cases - if you have time, would love to see your take on it (you can fork gists)
Although clang's implementation (https://github.com/llvm-mirror/clang/blob/a782b2b93cd493f514a5ed77721b7a361bc91553/lib/CodeGen/CGObjCMac.cpp) may give us guidelines about which objc_msg
function should be used, the casting to variadic function prototype ((id(*)(struct objc_super *, SEL, va_list))
is a trickier issue seems hard to solve (https://mikeash.com/pyblog/objc_msgsends-new-prototype.html). It seems that the designers of objective-c really don't want us to use objc_msg
functions directly :(
Things to know about this swizzling approach: https://pspdfkit.com/blog/2019/swizzling-in-swift/