Add profiler hooks to views
Summary: public The profiler currently only hooks into bridge modules, extend it so we also log method calls on views. Reviewed By: jspahrsummers Differential Revision: D2755213 fb-gh-sync-id: e8ff224eec08898340d05e104772ff1626538bd5
This commit is contained in:
parent
acf977a481
commit
230c180a12
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue