Skip to content

Instantly share code, notes, and snippets.

@saagarjha
Last active December 25, 2023 18:06
Show Gist options
  • Save saagarjha/ed701e3369639410b5d5303612964557 to your computer and use it in GitHub Desktop.
Save saagarjha/ed701e3369639410b5d5303612964557 to your computer and use it in GitHub Desktop.
Type-safe, RAII swizzler for Objective-C++
// Example usage:
// Swizzler<NSString *, NSDateFormatter *, NSDate *> NSDateFormatter_stringFromDate_ {
// NSDateFormatter.class, @selector(stringFromDate:), [&](auto self, auto date) {
// if ([NSCalendar.currentCalendar components:NSCalendarUnitWeekday fromDate:date].weekday == 4) {
// return @"It Is Wednesday My Dudes";
// } else {
// return NSDateFormatter_stringFromDate_(self, date);
// }
// }
// };
#import <objc/runtime.h>
#import <stdexcept>
#import <unordered_map>
#import <utility>
template <typename Return, typename _Class = id, typename... Arguments>
class Swizzler {
using implementation_type = Return (*)(_Class, SEL, Arguments...);
static inline auto _side_table = std::unordered_map<IMP, std::pair<bool, implementation_type>>{};
implementation_type _original;
SEL _selector;
IMP _side_table_key;
bool _moved = false;
public:
template <typename Replacement>
requires std::is_invocable_r_v<Return, Replacement, _Class, Arguments...>
Swizzler(Class _class, SEL selector, Replacement replacement) : _selector(selector) {
auto method = class_getInstanceMethod(_class, selector);
if (!method) {
throw std::runtime_error("Could not find method!");
}
auto encoding = method_getTypeEncoding(method);
class_addMethod(_class, selector, imp_implementationWithBlock(^(_Class self, Arguments... arguments) {
return reinterpret_cast<implementation_type>(class_getMethodImplementation(class_getSuperclass(_class), selector))(self, selector, arguments...);
}),
encoding);
__block decltype(_side_table_key) side_table_key;
auto replacementBlock = ^(_Class self, Arguments... arguments) {
auto pair = _side_table[side_table_key];
if (pair.first) {
return replacement(self, arguments...);
} else {
return pair.second(self, selector, arguments...);
}
};
auto _replacement = _side_table_key = side_table_key = imp_implementationWithBlock(replacementBlock);
_original = reinterpret_cast<implementation_type>(class_replaceMethod(_class, selector, _replacement, encoding));
_side_table[_side_table_key] = std::make_pair(true, _original);
}
Swizzler(Swizzler &&other) {
_original = other._original;
_selector = other._selector;
_side_table_key = other._side_table_key;
other._moved = true;
}
~Swizzler() {
if (!_moved) {
_side_table[_side_table_key].first = false;
}
}
Swizzler(const Swizzler &) = delete;
Swizzler &operator=(const Swizzler &) = delete;
auto operator()(_Class self, Arguments... arguments) const {
return _original(self, _selector, arguments...);
}
auto operator()(_Class self, SEL _cmd, Arguments... arguments) const {
return _original(self, _cmd, arguments...);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment