Skip to content

Instantly share code, notes, and snippets.

@saagarjha
Last active January 1, 2024 04:09
Show Gist options
  • Save saagarjha/7f0ae20e472c2ecfe9521782ce3649f2 to your computer and use it in GitHub Desktop.
Save saagarjha/7f0ae20e472c2ecfe9521782ce3649f2 to your computer and use it in GitHub Desktop.
Fix an Xcode hang caused by FB11645580 due to IDERunDestination registering thousands of duplicate KVO observers
// https://gist.github.com/saagarjha/ed701e3369639410b5d5303612964557
#import "swizzler.h"
#import <Foundation/Foundation.h>
#import <cstddef>
#import <cstdlib>
#import <dlfcn.h>
#import <mach-o/dyld.h>
#import <mutex>
#import <string>
#import <tuple>
#import <unordered_map>
struct TupleHasher {
std::size_t operator()(const std::tuple<NSObject *, NSString *, NSUInteger, id> &tuple) const {
// This sucks but it's good enough
return std::get<0>(tuple).hash ^ std::get<1>(tuple).hash ^ std::get<2>(tuple) ^ reinterpret_cast<uintptr_t>(std::get<3>(tuple));
}
};
static auto loader = []() {
std::string buffer;
uint32_t size = 0;
_NSGetExecutablePath(buffer.data(), &size);
buffer = std::string(size, 0);
_NSGetExecutablePath(buffer.data(), &size);
auto url = [NSURL fileURLWithPath:[NSString stringWithCString:buffer.data() encoding:NSASCIIStringEncoding]];
auto path = [[[url.pathComponents subarrayWithRange:NSMakeRange(0, 4)] componentsJoinedByString:@"/"] stringByAppendingString:@"/SharedFrameworks/DVTFoundation.framework/Versions/A/DVTFoundation"];
dlopen(path.UTF8String, RTLD_LAZY);
unsetenv("DYLD_INSERT_LIBRARIES");
return 0;
}();
static Swizzler<id, NSObject *, NSString *, NSUInteger, id> NSObject_dvt_newObserverForKeyPath_options_withHandlerBlock_ {
NSObject.class, @selector(dvt_newObserverForKeyPath:options:withHandlerBlock:), [](auto self, auto keyPath, auto options, auto handlerBlock) {
if ([keyPath isEqualToString:@"name"]) {
static std::mutex mutex;
static std::unordered_map<std::tuple<NSObject *, NSString *, NSUInteger, id>, id, TupleHasher> observerCache;
auto tuple = std::make_tuple(self, keyPath, options, handlerBlock);
std::lock_guard<std::mutex> lock(mutex);
if (auto location = observerCache.find(tuple); location != observerCache.end()) {
[location->second retain];
return location->second;
} else {
auto result = NSObject_dvt_newObserverForKeyPath_options_withHandlerBlock_(self, keyPath, options, handlerBlock);
observerCache[tuple] = result;
// Intentional over-retain, to prevent this token from ever being deallocated
[result retain];
return result;
}
} else {
return NSObject_dvt_newObserverForKeyPath_options_withHandlerBlock_(self, keyPath, options, handlerBlock);
}
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment