From ca12eff9a42a70786f7c78bec6f8295abcb1e85f Mon Sep 17 00:00:00 2001 From: Hugo Cuvillier Date: Tue, 25 Feb 2020 10:35:57 -0800 Subject: [PATCH] Store last component class name on view Summary: Only happens when assertions are enabled - should help debug the task assigned. Reviewed By: aCorrado Differential Revision: D20067626 fbshipit-source-id: 3b51e7f22de37fa0976aea36401e7eb7c77607ec --- .../Action/CKComponentDelegateForwarder.mm | 6 ++++- ComponentKit/Core/CKComponent+UIView.h | 2 ++ ComponentKit/Core/CKComponent+UIView.mm | 22 ++++++++++++++++++- .../CKComponentGestureActionsTests.mm | 3 +++ 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/ComponentKit/Core/Action/CKComponentDelegateForwarder.mm b/ComponentKit/Core/Action/CKComponentDelegateForwarder.mm index 057663c6f..e44a7054d 100644 --- a/ComponentKit/Core/Action/CKComponentDelegateForwarder.mm +++ b/ComponentKit/Core/Action/CKComponentDelegateForwarder.mm @@ -76,7 +76,11 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation CKComponent *responder = CKMountedComponentForView(_view); id target = [responder targetForAction:selector withSender:responder]; if (!target) { - CKFailAssert(@"Delegate method is being called on an unmounted component's view: %@ selector:%@", _view, NSStringFromSelector(selector)); + CKFailAssertWithCategory( + CKLastMountedComponentClassNameForView(_view), + @"Delegate method is being called on an unmounted component's view: %@ selector:%@", + _view, + NSStringFromSelector(selector)); return; } [anInvocation invokeWithTarget:target]; diff --git a/ComponentKit/Core/CKComponent+UIView.h b/ComponentKit/Core/CKComponent+UIView.h index 2e3803d09..15e7ed482 100644 --- a/ComponentKit/Core/CKComponent+UIView.h +++ b/ComponentKit/Core/CKComponent+UIView.h @@ -20,6 +20,8 @@ #error This file must be compiled as Obj-C++. If you are importing it, you must change your file extension to .mm. #endif +NSString *CKLastMountedComponentClassNameForView(UIView *view); + /** Strong reference back to the associated component while the component is mounted. */ CKComponent *CKMountedComponentForView(UIView *view); diff --git a/ComponentKit/Core/CKComponent+UIView.mm b/ComponentKit/Core/CKComponent+UIView.mm index 4d8f7bd4a..73d6f57cd 100644 --- a/ComponentKit/Core/CKComponent+UIView.mm +++ b/ComponentKit/Core/CKComponent+UIView.mm @@ -11,8 +11,22 @@ #import "CKComponent+UIView.h" #import +#import +#import -#import "CKComponent.h" +#if CK_ASSERTIONS_ENABLED +static const void *kMountedComponentClassNameKey = nullptr; +#endif + +/** Strong reference back to the associated component while the component is mounted. */ +NSString *CKLastMountedComponentClassNameForView(UIView *view) +{ +#if CK_ASSERTIONS_ENABLED + return CKGetAssociatedObject_MainThreadAffined(view, &kMountedComponentClassNameKey); +#else + return nil; +#endif +} /** Strong reference back to the associated component while the component is mounted. */ CKComponent *CKMountedComponentForView(UIView *view) @@ -24,4 +38,10 @@ void CKSetMountedComponentForView(UIView *view, CKComponent *component) { CKSetMountedObjectForView(view, component); +#if CK_ASSERTIONS_ENABLED + if (component != nil) { + // We want to know which component was last mounted - do not clean this up. + CKSetAssociatedObject_MainThreadAffined(view, &kMountedComponentClassNameKey, component.className); + } +#endif } diff --git a/ComponentKitTests/CKComponentGestureActionsTests.mm b/ComponentKitTests/CKComponentGestureActionsTests.mm index 517489761..fdf24ba21 100644 --- a/ComponentKitTests/CKComponentGestureActionsTests.mm +++ b/ComponentKitTests/CKComponentGestureActionsTests.mm @@ -59,6 +59,7 @@ - (void)testThatTappingAViewSendsComponentAction { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; id mockComponent = [OCMockObject mockForClass:[CKComponent class]]; + [[[mockComponent stub] andReturn:@"MyClass"] className]; CKFakeActionComponent *fakeParentComponent = [CKFakeActionComponent new]; [[[mockComponent stub] andReturn:fakeParentComponent] nextResponder]; [[[mockComponent stub] andReturn:fakeParentComponent] targetForAction:[OCMArg anySelector] withSender:[OCMArg any]]; @@ -106,6 +107,7 @@ - (void)testThatWhenDelegateActionsAreSetTheyProxyToComponent UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; id mockComponent = [OCMockObject mockForClass:[CKComponent class]]; + [[[mockComponent stub] andReturn:@"MyClass"] className]; CKFakeActionComponent *fakeParentComponent = [CKFakeActionComponent new]; [[[mockComponent stub] andReturn:fakeParentComponent] nextResponder]; [[[mockComponent stub] andReturn:fakeParentComponent] targetForAction:[OCMArg anySelector] withSender:[OCMArg any]]; @@ -126,6 +128,7 @@ - (void)testThatWithNoDelegateActionsNoDelegateIsSet { UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]; id mockComponent = [OCMockObject mockForClass:[CKComponent class]]; + [[[mockComponent stub] andReturn:@"MyClass"] className]; CKFakeActionComponent *fakeParentComponent = [CKFakeActionComponent new]; [[[mockComponent stub] andReturn:fakeParentComponent] nextResponder]; [[[mockComponent stub] andReturn:fakeParentComponent] targetForAction:[OCMArg anySelector] withSender:[OCMArg any]];