RenderCore/View/ComponentViewReuseUtilities.mm (157 lines of code) (raw):
/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#import "ComponentViewReuseUtilities.h"
#import <objc/runtime.h>
#import <unordered_map>
#import <RenderCore/RCAssert.h>
#import <RenderCore/RCAssociatedObject.h>
#import "CKComponentViewClass.h"
using namespace CK::Component;
static char const kViewReuseInfoKey = ' ';
@interface CKComponentViewReuseInfo : NSObject
- (instancetype)initWithView:(UIView *)view
didEnterReusePoolBlock:(void (^)(UIView *))didEnterReusePoolBlock
willLeaveReusePoolBlock:(void (^)(UIView *))willLeaveReusePoolBlock;
- (void)registerChildViewInfo:(CKComponentViewReuseInfo *)info;
- (void)didHide:(CK::Component::MountAnalyticsContext *)mountAnalyticsContext;
- (void)willUnhide:(CK::Component::MountAnalyticsContext *)mountAnalyticsContext;
- (void)ancestorDidHide;
- (void)ancestorWillUnhide;
@end
void ViewReuseUtilities::mountingInRootView(UIView *rootView) noexcept
{
// If we already mounted in this root view, it will already have a reuse info struct.
if (RCGetAssociatedObject_MainThreadAffined(rootView, &kViewReuseInfoKey)) {
return;
}
CKComponentViewReuseInfo *info = [[CKComponentViewReuseInfo alloc] initWithView:rootView
didEnterReusePoolBlock:nil
willLeaveReusePoolBlock:nil];
RCSetAssociatedObject_MainThreadAffined(rootView, &kViewReuseInfoKey, info);
}
void ViewReuseUtilities::createdView(UIView *view, const CKComponentViewClass &viewClass, UIView *parent) noexcept
{
RCCAssertNil(RCGetAssociatedObject_MainThreadAffined(view, &kViewReuseInfoKey),
@"Didn't expect reuse info on just-created view %@", view);
CKComponentViewReuseInfo *info = [[CKComponentViewReuseInfo alloc] initWithView:view
didEnterReusePoolBlock:viewClass.didEnterReusePool
willLeaveReusePoolBlock:viewClass.willLeaveReusePool];
RCSetAssociatedObject_MainThreadAffined(view, &kViewReuseInfoKey, info);
CKComponentViewReuseInfo *parentInfo = RCGetAssociatedObject_MainThreadAffined(parent, &kViewReuseInfoKey);
RCCAssertNotNil(parentInfo, @"Expected parentInfo but found none on %@", parent);
[parentInfo registerChildViewInfo:info];
}
void ViewReuseUtilities::mountingInChildContext(UIView *view, UIView *parent) noexcept
{
// If this view was created by the components infrastructure, or if we've
// mounted in it before, it will already have a reuse info struct.
if (RCGetAssociatedObject_MainThreadAffined(view, &kViewReuseInfoKey)) {
return;
}
CKComponentViewReuseInfo *info = [[CKComponentViewReuseInfo alloc] initWithView:view
didEnterReusePoolBlock:nil
willLeaveReusePoolBlock:nil];
RCSetAssociatedObject_MainThreadAffined(view, &kViewReuseInfoKey, info);
CKComponentViewReuseInfo *parentInfo = RCGetAssociatedObject_MainThreadAffined(parent, &kViewReuseInfoKey);
RCCAssertNotNil(parentInfo, @"Expected parentInfo but found none on %@", parent);
[parentInfo registerChildViewInfo:info];
}
void ViewReuseUtilities::didHide(UIView *view, CK::Component::MountAnalyticsContext *mountAnalyticsContext) noexcept
{
CKComponentViewReuseInfo *info = RCGetAssociatedObject_MainThreadAffined(view, &kViewReuseInfoKey);
RCCAssertNotNil(info, @"Expect to find reuse info on all components-managed views but found none on %@", view);
[info didHide:mountAnalyticsContext];
}
void ViewReuseUtilities::willUnhide(UIView *view, CK::Component::MountAnalyticsContext *mountAnalyticsContext) noexcept
{
CKComponentViewReuseInfo *info = RCGetAssociatedObject_MainThreadAffined(view, &kViewReuseInfoKey);
RCCAssertNotNil(info, @"Expect to find reuse info on all components-managed views but found none on %@", view);
[info willUnhide:mountAnalyticsContext];
}
@implementation CKComponentViewReuseInfo
{
// Weak to prevent a retain cycle since the view holds the info strongly via associated objects
UIView *__weak _view;
void (^_didEnterReusePoolBlock)(UIView *);
void (^_willLeaveReusePoolBlock)(UIView *);
NSMutableArray *_childViewInfos;
BOOL _hidden;
BOOL _ancestorHidden;
}
- (instancetype)initWithView:(UIView *)view
didEnterReusePoolBlock:(void (^)(UIView *))didEnterReusePoolBlock
willLeaveReusePoolBlock:(void (^)(UIView *))willLeaveReusePoolBlock
{
if (self = [super init]) {
_view = view;
_didEnterReusePoolBlock = didEnterReusePoolBlock;
_willLeaveReusePoolBlock = willLeaveReusePoolBlock;
}
return self;
}
- (void)registerChildViewInfo:(CKComponentViewReuseInfo *)info
{
if (_childViewInfos == nil) {
_childViewInfos = [[NSMutableArray alloc] init];
}
[_childViewInfos addObject:info];
}
- (void)didHide:(CK::Component::MountAnalyticsContext *)mountAnalyticsContext
{
if (_hidden) {
return;
}
if (_ancestorHidden == NO && _didEnterReusePoolBlock) {
_didEnterReusePoolBlock(_view);
}
_hidden = YES;
for (CKComponentViewReuseInfo *descendantInfo in _childViewInfos) {
[descendantInfo ancestorDidHide];
}
if (auto mac = mountAnalyticsContext) {
mac->viewHides++;
}
}
- (void)willUnhide:(CK::Component::MountAnalyticsContext *)mountAnalyticsContext
{
if (!_hidden) {
return;
}
if (_ancestorHidden == NO && _willLeaveReusePoolBlock) {
_willLeaveReusePoolBlock(_view);
}
_hidden = NO;
for (CKComponentViewReuseInfo *descendantInfo in _childViewInfos) {
[descendantInfo ancestorWillUnhide];
}
if (auto mac = mountAnalyticsContext) {
mac->viewUnhides++;
}
}
- (void)ancestorDidHide
{
if (_ancestorHidden) {
return;
}
if (_hidden == NO && _didEnterReusePoolBlock) {
_didEnterReusePoolBlock(_view);
}
_ancestorHidden = YES;
if (_hidden) {
// Since this view is itself already hidden, no need to notify children. They already have _ancestorHidden = YES.
return;
}
for (CKComponentViewReuseInfo *descendantInfo in _childViewInfos) {
[descendantInfo ancestorDidHide];
}
}
- (void)ancestorWillUnhide
{
if (!_ancestorHidden) {
return;
}
if (_hidden == NO && _willLeaveReusePoolBlock) {
_willLeaveReusePoolBlock(_view);
}
_ancestorHidden = NO;
if (_hidden) {
// If this view is itself still hidden, the unhiding of an ancestor changes nothing for children.
return;
}
for (CKComponentViewReuseInfo *descendantInfo in _childViewInfos) {
[descendantInfo ancestorWillUnhide];
}
}
@end