Skip to content

Instantly share code, notes, and snippets.

@rickytan
Created July 11, 2023 07:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rickytan/4bb9107f67e2d2975ea660dc9a78061b to your computer and use it in GitHub Desktop.
Save rickytan/4bb9107f67e2d2975ea660dc9a78061b to your computer and use it in GitHub Desktop.
Fix iOS 16 crash on [UIKeyboardTaskQueue promoteDeferredTaskIfIdle]
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
@interface UIDeferredTasksWrapper : NSProxy
- (instancetype)initWithArray:(NSMutableArray *)array;
@end
@implementation UIDeferredTasksWrapper
{
NSMutableArray <id> * _tasks;
}
- (instancetype)initWithArray:(NSMutableArray *)array {
if (self) {
_tasks = array;
}
return self;
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [_tasks methodSignatureForSelector:sel];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
static dispatch_once_t onceToken;
static dispatch_queue_t taskQueue = nil;
dispatch_once(&onceToken, ^{
taskQueue = dispatch_queue_create("com.apple.keyboardTaskQueue", DISPATCH_QUEUE_SERIAL);
});
dispatch_sync(taskQueue, ^{
[invocation invokeWithTarget:_tasks];
});
}
@end
static IMP origin_keyboard_task_queue_init = NULL;
static id new_keyboard_task_queue_init(id self, SEL _cmd) {
NSString *ivarName = @"_deferredTasks";
id(*init)(id, SEL) = (id(*)(id, SEL))origin_keyboard_task_queue_init;
id instance = init(self, _cmd);
id member = [instance valueForKey:ivarName];
if ([member isKindOfClass:NSMutableArray.class]) {
[instance setValue:[[UIDeferredTasksWrapper alloc] initWithArray:member]
forKey:ivarName];
}
return instance;
}
void __FIX_iOS_16_KEYBOARD_TASK_QUEUE_CRASH__(void) {
float system = [UIDevice currentDevice].systemVersion.floatValue;
if (16.0 <= system && system < 17.0) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class cls = NSClassFromString(@"UIKeyboardTaskQueue");
if (cls) {
/// swizzle
/// @code
/// -[UIKeyboardTaskQueue init]
/// @endcode
/// then replace member @c _deferredTasks with thread safe proxy
Method init_m = class_getInstanceMethod(cls, @selector(init));
origin_keyboard_task_queue_init = method_setImplementation(init_m, (IMP)new_keyboard_task_queue_init);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment