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:
Tadeu Zagallo 2015-12-14 13:07:24 -08:00 committed by facebook-github-bot-1
parent acf977a481
commit 230c180a12
1 changed files with 116 additions and 53 deletions

View File

@ -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