Skip to content

Instantly share code, notes, and snippets.

@steipete
Created August 7, 2019 08:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save steipete/1d308fad786399b58875cd12e4b9bba2 to your computer and use it in GitHub Desktop.
Save steipete/1d308fad786399b58875cd12e4b9bba2 to your computer and use it in GitHub Desktop.
pspdf_swizzleSelectorWithBlock, pspdf_swizzleSelector. Please use your own prefix when you copy these.
// 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);
}
@steipete
Copy link
Author

steipete commented Aug 7, 2019

Things to know about this swizzling approach: https://pspdfkit.com/blog/2019/swizzling-in-swift/

@aoverholtzer
Copy link

aoverholtzer commented Aug 7, 2019

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,...);

@zetasq
Copy link

zetasq commented Dec 2, 2019

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.

@steipete
Copy link
Author

steipete commented Dec 2, 2019

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)

@zetasq
Copy link

zetasq commented Dec 2, 2019

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 :(

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment