diff --git a/React/Profiler/RCTProfile.m b/React/Profiler/RCTProfile.m index cac086c05..ab050a286 100644 --- a/React/Profiler/RCTProfile.m +++ b/React/Profiler/RCTProfile.m @@ -20,10 +20,12 @@ #import "RCTAssert.h" #import "RCTBridge.h" +#import "RCTComponentData.h" #import "RCTDefines.h" #import "RCTLog.h" #import "RCTModuleData.h" #import "RCTUtils.h" +#import "RCTUIManager.h" NSString *const RCTProfileDidStartProfiling = @"RCTProfileDidStartProfiling"; NSString *const RCTProfileDidEndProfiling = @"RCTProfileDidEndProfiling"; @@ -196,7 +198,12 @@ IMP RCTProfileGetImplementation(id obj, SEL cmd) RCT_EXTERN void RCTProfileTrampolineStart(id, SEL); void RCTProfileTrampolineStart(id self, SEL cmd) { - RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"-[%s %s]", class_getName([self class]), sel_getName(cmd)], nil); + /** + * This call might be during dealloc, so we shouldn't retain the object in the + * block. + */ + Class klass = [self class]; + RCT_PROFILE_BEGIN_EVENT(0, [NSString stringWithFormat:@"-[%s %s]", class_getName(klass), sel_getName(cmd)], nil); } RCT_EXTERN void RCTProfileTrampolineEnd(void); @@ -205,6 +212,85 @@ void RCTProfileTrampolineEnd(void) RCT_PROFILE_END_EVENT(0, @"objc_call,modules,auto", nil); } +static void RCTProfileHookInstance(id instance) +{ + Class moduleClass = object_getClass(instance); + + /** + * We swizzle the instance -class method to return the original class, but + * object_getClass will return the actual class. + * + * If they are different, it means that the object is returning the original + * class, but it's actual class is the proxy subclass we created. + */ + if ([instance class] != moduleClass) { + return; + } + + Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); + + if (!proxyClass) { + proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass)); + if (proxyClass) { + object_setClass(instance, proxyClass); + } + return; + } + + unsigned int methodCount; + Method *methods = class_copyMethodList(moduleClass, &methodCount); + for (NSUInteger i = 0; i < methodCount; i++) { + Method method = methods[i]; + SEL selector = method_getName(method); + + /** + * Bail out on struct returns (except arm64) - we don't use it enough + * to justify writing a stret version + */ +#ifdef __arm64__ + BOOL returnsStruct = NO; +#else + const char *typeEncoding = method_getTypeEncoding(method); + // bail out on structs and unions (since they might contain structs) + BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '('; +#endif + + /** + * Avoid hooking into NSObject methods, methods generated by React Native + * and special methods that start `.` (e.g. .cxx_destruct) + */ + if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] || sel_getName(selector)[0] == '.' || returnsStruct) { + continue; + } + + const char *types = method_getTypeEncoding(method); + class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types); + } + free(methods); + + class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:"); + + for (Class cls in @[proxyClass, object_getClass(proxyClass)]) { + Method oldImp = class_getInstanceMethod(cls, @selector(class)); + class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp)); + } + + objc_registerClassPair(proxyClass); + object_setClass(instance, proxyClass); +} + +static UIView *(*originalCreateView)(RCTComponentData *, SEL, NSNumber *, NSDictionary *); + +RCT_EXTERN UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag, NSDictionary *props); +UIView *RCTProfileCreateView(RCTComponentData *self, SEL _cmd, NSNumber *tag, NSDictionary *props) +{ + UIView *view = originalCreateView(self, _cmd, tag, props); + + RCTProfileHookInstance(view); + + return view; +} + void RCTProfileHookModules(RCTBridge *bridge) { #pragma clang diagnostic push @@ -216,55 +302,29 @@ void RCTProfileHookModules(RCTBridge *bridge) for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { [bridge dispatchBlock:^{ - Class moduleClass = moduleData.moduleClass; - Class proxyClass = objc_allocateClassPair(moduleClass, RCTProfileProxyClassName(moduleClass), 0); - - if (!proxyClass) { - proxyClass = objc_getClass(RCTProfileProxyClassName(moduleClass)); - if (proxyClass) { - object_setClass(moduleData.instance, proxyClass); - } - return; - } - - unsigned int methodCount; - Method *methods = class_copyMethodList(moduleClass, &methodCount); - for (NSUInteger i = 0; i < methodCount; i++) { - Method method = methods[i]; - SEL selector = method_getName(method); - - /** - * Bail out on struct returns (except arm64) - we don't use it enough - * to justify writing a stret version - */ -#ifdef __arm64__ - BOOL returnsStruct = NO; -#else - const char *typeEncoding = method_getTypeEncoding(method); - // bail out on structs and unions (since they might contain structs) - BOOL returnsStruct = typeEncoding[0] == '{' || typeEncoding[0] == '('; -#endif - - if ([NSStringFromSelector(selector) hasPrefix:@"rct"] || [NSObject instancesRespondToSelector:selector] || returnsStruct) { - continue; - } - const char *types = method_getTypeEncoding(method); - - class_addMethod(proxyClass, selector, (IMP)RCTProfileTrampoline, types); - } - free(methods); - - class_replaceMethod(object_getClass(proxyClass), @selector(initialize), imp_implementationWithBlock(^{}), "v@:"); - - for (Class cls in @[proxyClass, object_getClass(proxyClass)]) { - Method oldImp = class_getInstanceMethod(cls, @selector(class)); - class_replaceMethod(cls, @selector(class), imp_implementationWithBlock(^{ return moduleClass; }), method_getTypeEncoding(oldImp)); - } - - objc_registerClassPair(proxyClass); - object_setClass(moduleData.instance, proxyClass); + RCTProfileHookInstance(moduleData.instance); } queue:moduleData.methodQueue]; } + + dispatch_async(dispatch_get_main_queue(), ^{ + for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) { + RCTProfileHookInstance([bridge.uiManager viewForReactTag:view]); + } + + Method createView = class_getInstanceMethod([RCTComponentData class], @selector(createViewWithTag:props:)); + + if (method_getImplementation(createView) != (IMP)RCTProfileCreateView) { + originalCreateView = (typeof(originalCreateView))method_getImplementation(createView); + method_setImplementation(createView, (IMP)RCTProfileCreateView); + } + }); +} + +static void RCTProfileUnhookInstance(id instance) +{ + if ([instance class] != object_getClass(instance)) { + object_setClass(instance, [instance class]); + } } void RCTProfileUnhookModules(RCTBridge *bridge) @@ -272,13 +332,16 @@ void RCTProfileUnhookModules(RCTBridge *bridge) dispatch_group_enter(RCTProfileGetUnhookGroup()); for (RCTModuleData *moduleData in [bridge valueForKey:@"moduleDataByID"]) { - Class proxyClass = object_getClass(moduleData.instance); - if (moduleData.moduleClass != proxyClass) { - object_setClass(moduleData.instance, moduleData.moduleClass); - } + RCTProfileUnhookInstance(moduleData.instance); } - dispatch_group_leave(RCTProfileGetUnhookGroup()); + dispatch_async(dispatch_get_main_queue(), ^{ + for (id view in [bridge.uiManager valueForKey:@"viewRegistry"]) { + RCTProfileUnhookInstance(view); + } + + dispatch_group_leave(RCTProfileGetUnhookGroup()); + }); } #pragma mark - Private ObjC class only used for the vSYNC CADisplayLink target