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 <atomic>
#import <memory>
#import <objc/runtime.h>
#import <stdexcept>
#import <utility>
template <typename Return, typename _Class = id, typename... Arguments>
class Swizzler {
using implementation_type = Return (*)(_Class, SEL, Arguments...);
struct Dispatch {
std::atomic_bool active;
std::atomic<implementation_type> original;
};
SEL _selector;
bool _moved = false;
std::shared_ptr<Dispatch> _dispatch = std::make_shared<Dispatch>();
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);
auto superForwarder = imp_implementationWithBlock([_class, selector](_Class self, Arguments... arguments) {
return reinterpret_cast<implementation_type>(
class_getMethodImplementation(class_getSuperclass(_class), selector))(self, selector, arguments...);
});
_dispatch->active.store(true, std::memory_order_release);
_dispatch->original.store(
reinterpret_cast<implementation_type>(class_addMethod(_class, selector, superForwarder, encoding)
? superForwarder
: method_getImplementation(method)),
std::memory_order_release);
auto replacementBlock = [dispatch = _dispatch, replacement, selector](_Class self, Arguments... arguments) {
if (dispatch->active.load(std::memory_order::acquire)) {
return replacement(self, arguments...);
} else {
return dispatch->original.load(std::memory_order::acquire)(self, selector, arguments...);
}
};
auto _replacement = imp_implementationWithBlock(replacementBlock);
_dispatch->original.store(
reinterpret_cast<implementation_type>(class_replaceMethod(_class, selector, _replacement, encoding)),
std::memory_order_release);
}
Swizzler(Swizzler &&other) {
_selector = other._selector;
other._moved = true;
}
~Swizzler() {
if (!_moved) {
_dispatch->active.store(false, std::memory_order_release);
}
}
Swizzler(const Swizzler &) = delete;
Swizzler &operator=(const Swizzler &) = delete;
auto operator()(_Class self, Arguments... arguments) const {
return _dispatch->original.load(std::memory_order_acquire)(self, _selector, arguments...);
}
auto operator()(_Class self, SEL _cmd, Arguments... arguments) const {
return _dispatch->original.load(std::memory_order_acquire)(self, _cmd, arguments...);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment