Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ComponentKit/Core/CKComponentLifecycleManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#import "CKComponent.h"
#import "CKComponentInternal.h"
#import "CKComponentDebugController.h"
#import "CKComponentLayout.h"
#import "CKComponentLifecycleManagerAsynchronousUpdateHandler.h"
#import "CKComponentMemoizer.h"
Expand All @@ -37,6 +38,9 @@
.root = nil,
};

@interface CKComponentLifecycleManager () <CKComponentDebugReflowListener>
@end

@implementation CKComponentLifecycleManager
{
UIView *_mountedView;
Expand All @@ -62,6 +66,7 @@ - (instancetype)initWithComponentProvider:(Class<CKComponentProvider>)componentP
if (self = [super init]) {
_componentProvider = componentProvider;
_sizeRangeProvider = sizeRangeProvider;
[CKComponentDebugController registerReflowListener:self];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though it's a bit of a shame to have CKComponentLifecycleManager aware of debug code I think it's totally worthwhile considering how much it simplifies CKComponentDebugController.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a bit weird, definitely. How about having a separate registry of lifecycle managers (exposed in an internal header) that the debug controller can then access? At least that would get the debug controller code out of CKComponentLifecycleManager.

}
return self;
}
Expand Down Expand Up @@ -220,4 +225,9 @@ - (void)componentScopeHandleWithIdentifier:(int32_t)globalIdentifier
return _state;
}

- (void)didReceiveReflowComponentsRequest
{
[self prepareForUpdateWithModel:_state.model constrainedSize:_state.constrainedSize context:_state.context];
}

@end
19 changes: 14 additions & 5 deletions ComponentKit/Debug/CKComponentDebugController.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
#import <ComponentKit/CKComponentViewConfiguration.h>

@class CKComponent;
@class UIView;

@protocol CKComponentDebugReflowListener
- (void)didReceiveReflowComponentsRequest;
@end

/**
CKComponentDebugController exposes the functionality needed by the lldb helpers to control the debug behavior for
Expand All @@ -27,16 +30,22 @@
/**
Setting the debug mode enables the injection of debug configuration into the component.
*/
+ (void)setDebugMode:(BOOL)debugMode NS_EXTENSION_UNAVAILABLE("Recursively reflows components using -[UIApplication keyWindow]");
+ (void)setDebugMode:(BOOL)debugMode;

/**
Components are an immutable construct. Whenever we make changes to the parameters on which the components depended,
the changes won't be reflected in the component hierarchy until we explicitly cause a reflow/update. A reflow
essentially rebuilds the component hierarchy and mounts it back on the view.
essentially rebuilds the component hierarchy and remounts on the attached view, if any.

This is particularly used in reflowing the component hierarchy when we set the debug mode.
This is automatically triggered when changing debug mode, to ensure that debug views are added or removed.
*/
+ (void)reflowComponents;

/**
Registers an object that will be notified when +reflowComponents is called. The listener is weakly held and will
be messaged on the main thread.
*/
+ (void)reflowComponents NS_EXTENSION_UNAVAILABLE("Recursively reflows components using -[UIApplication keyWindow]");
+ (void)registerReflowListener:(id<CKComponentDebugReflowListener>)listener;

@end

Expand Down
52 changes: 20 additions & 32 deletions ComponentKit/Debug/CKComponentDebugController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

#import <UIKit/UIKit.h>

#import <mutex>

#import "CKComponent.h"
#import "CKComponentAnimation.h"
#import "CKComponentHostingView.h"
Expand Down Expand Up @@ -109,9 +111,6 @@ + (BOOL)debugMode
return context; // no need for a debug view if the component has a view.
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnecessary; mount is only performed on the main thread.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to assert this is called on the main thread? Or do you think that's overkill in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's overkill in this particular case.

static CK::StaticMutex l = CK_MUTEX_INITIALIZER;
CK::StaticMutexLocker lock(l);

// Avoid the static destructor fiasco, use a pointer:
static std::unordered_map<Class, CKComponentViewConfiguration> *debugViewConfigurations =
new std::unordered_map<Class, CKComponentViewConfiguration>();
Expand All @@ -133,46 +132,35 @@ + (BOOL)debugMode

#pragma mark - Synchronous Reflow

static NSHashTable<id<CKComponentDebugReflowListener>> *reflowListeners;
static std::mutex reflowMutex;

+ (void)reflowComponents
{
if (![NSThread isMainThread]) {
dispatch_async(dispatch_get_main_queue(), ^{ [self reflowComponents]; });
} else {
UIWindow *window = [[UIApplication sharedApplication] keyWindow];
CKRecursiveComponentReflow(window);
return;
}
}

+ (void)reflowComponentsForView:(UIView *)view searchUpwards:(BOOL)upwards
{
if (upwards) {
while (view && ![view isKindOfClass:[CKComponentRootView class]]) {
view = view.superview;
}
NSArray<id<CKComponentDebugReflowListener>> *copiedListeners;
{
std::lock_guard<std::mutex> l(reflowMutex);
copiedListeners = [reflowListeners allObjects];
}
if (view) {
CKRecursiveComponentReflow(view);
for (id<CKComponentDebugReflowListener> listener in copiedListeners) {
[listener didReceiveReflowComponentsRequest];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so much better than traversing the view hierarchy to find the component lifecycle manager or explicitly telling component hosting views to layout.

}
}

static void CKRecursiveComponentReflow(UIView *view)
+ (void)registerReflowListener:(id<CKComponentDebugReflowListener>)listener
{
if (view.ck_componentLifecycleManager) {
CKComponentLifecycleManager *lifecycleManager = view.ck_componentLifecycleManager;
CKComponentLifecycleManagerState oldState = [lifecycleManager state];
CKComponentLifecycleManagerState state =
[lifecycleManager prepareForUpdateWithModel:oldState.model
constrainedSize:oldState.constrainedSize
context:oldState.context];
[lifecycleManager updateWithState:state];
} else if ([view.superview isKindOfClass:[CKComponentHostingView class]]) {
CKComponentHostingView *hostingView = (CKComponentHostingView *)view.superview;
[hostingView setNeedsLayout];
} else {
for (UIView *subview in view.subviews) {
CKRecursiveComponentReflow(subview);
}
if (listener == nil) {
return;
}
std::lock_guard<std::mutex> l(reflowMutex);
if (reflowListeners == nil) {
reflowListeners = [NSHashTable weakObjectsHashTable];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. So we weakly hold the reflow listeners to naturally keep the collection bounded.

}
[reflowListeners addObject:listener];
}

@end
12 changes: 11 additions & 1 deletion ComponentKit/HostingView/CKComponentHostingView.mm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#import <ComponentKit/CKMacros.h>

#import "CKComponentAnimation.h"
#import "CKComponentDebugController.h"
#import "CKComponentHostingViewDelegate.h"
#import "CKComponentLayout.h"
#import "CKComponentRootView.h"
Expand All @@ -33,7 +34,7 @@
};
};

@interface CKComponentHostingView () <CKComponentStateListener>
@interface CKComponentHostingView () <CKComponentStateListener, CKComponentDebugReflowListener>
{
Class<CKComponentProvider> _componentProvider;
id<CKComponentSizeRangeProviding> _sizeRangeProvider;
Expand Down Expand Up @@ -75,6 +76,8 @@ - (instancetype)initWithComponentProvider:(Class<CKComponentProvider>)componentP

_componentNeedsUpdate = YES;
_requestedUpdateMode = CKUpdateModeSynchronous;

[CKComponentDebugController registerReflowListener:self];
}
return self;
}
Expand Down Expand Up @@ -150,6 +153,13 @@ - (void)componentScopeHandleWithIdentifier:(CKComponentScopeHandleIdentifier)glo
[self _setNeedsUpdateWithMode:mode];
}

#pragma mark - CKComponentDebugController

- (void)didReceiveReflowComponentsRequest
{
[self _setNeedsUpdateWithMode:CKUpdateModeAsynchronous];
}

#pragma mark - Private

- (void)_setNeedsUpdateWithMode:(CKUpdateMode)mode
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "CKTransactionalComponentDataSourceInternal.h"

#import "CKAssert.h"
#import "CKComponentDebugController.h"
#import "CKComponentScopeRoot.h"
#import "CKTransactionalComponentDataSourceChange.h"
#import "CKTransactionalComponentDataSourceChangesetModification.h"
Expand All @@ -35,7 +36,7 @@ - (instancetype)initWithModification:(id<CKTransactionalComponentDataSourceState

@end

@interface CKTransactionalComponentDataSource () <CKComponentStateListener>
@interface CKTransactionalComponentDataSource () <CKComponentStateListener, CKComponentDebugReflowListener>
{
CKTransactionalComponentDataSourceState *_state;
CKTransactionalComponentDataSourceListenerAnnouncer *_announcer;
Expand All @@ -60,6 +61,7 @@ - (instancetype)initWithConfiguration:(CKTransactionalComponentDataSourceConfigu
_workQueue = dispatch_queue_create("org.componentkit.CKTransactionalComponentDataSource", DISPATCH_QUEUE_SERIAL);
_pendingAsynchronousModifications = [NSMutableArray array];
_workThreadOverride = configuration.workThreadOverride;
[CKComponentDebugController registerReflowListener:self];
}
return self;
}
Expand Down Expand Up @@ -163,6 +165,13 @@ - (void)componentScopeHandleWithIdentifier:(CKComponentScopeHandleIdentifier)glo
}
}

#pragma mark - CKComponentDebugReflowListener

- (void)didReceiveReflowComponentsRequest
{
[self reloadWithMode:CKUpdateModeAsynchronous userInfo:nil];
}

#pragma mark - Internal

- (void)_enqueueModification:(id<CKTransactionalComponentDataSourceStateModifying>)modification
Expand Down