blob: f4b29a8d3a6ebbbdcbfd423e6f42cd4cf8e77317 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import "WXComponentManager.h"
#import "WXComponent.h"
#import "WXComponent_internal.h"
#import "WXComponent+DataBinding.h"
#import "WXComponentFactory.h"
#import "WXDefine.h"
#import "NSArray+Weex.h"
#import "WXSDKInstance.h"
#import "WXAssert.h"
#import "WXUtility.h"
#import "WXMonitor.h"
#import "WXScrollerProtocol.h"
#import "WXSDKManager.h"
#import "WXSDKError.h"
#import "WXInvocationConfig.h"
#import "WXHandlerFactory.h"
#import "WXValidateProtocol.h"
#import "WXPrerenderManager.h"
#import "WXSDKInstance_performance.h"
#import "WXRootView.h"
#import "WXComponent+Layout.h"
#import "WXCoreBridge.h"
#import "WXComponent_performance.h"
#import "WXAnalyzerCenter.h"
#import "WXDisplayLinkManager.h"
static NSThread *WXComponentThread;
/* Task scheduled on component thread and triggered every N display link ticks.
If display link is stopped, the tasks are also suspended. */
@interface WXComponentThreadTickTask : NSObject
@property (nonatomic, assign) NSUInteger displayTickCount; // Triggered every N display link ticks
@property (nonatomic, copy) dispatch_block_t block;
@end
@implementation WXComponentThreadTickTask
+ (instancetype)taskWithBlock:(dispatch_block_t)block tickCount:(NSUInteger)tickCount
{
WXComponentThreadTickTask* task = [[WXComponentThreadTickTask alloc] init];
task.displayTickCount = tickCount;
task.block = block;
return task;
}
@end
#define WXAssertComponentExist(component) WXAssert(component, @"component not exists")
#define MAX_DROP_FRAME_FOR_BATCH 200
@interface WXComponentManager () <WXDisplayLinkClient>
@end
@implementation WXComponentManager
{
__weak WXSDKInstance *_weexInstance;
BOOL _isValid;
BOOL _stopRunning;
NSUInteger _noTaskTickCount;
// access only on component thread
NSMapTable<NSString *, WXComponent *> *_indexDict;
NSMutableArray<dispatch_block_t> *_uiTaskQueue;
NSMutableDictionary *_uiPrerenderTaskQueue;
NSUInteger _displayTick;
NSMutableArray<WXComponentThreadTickTask*> *_displayTaskQueue;
// vdom depth and component count statistics
NSUInteger _maxVdomDepth;
NSUInteger _maxVdomDepthReported;
NSUInteger _maxComponentCount;
NSUInteger _maxComponentCountReported;
WXComponent *_rootComponent;
NSMutableArray *_fixedComponents;
pthread_mutex_t _propertyMutex;
pthread_mutexattr_t _propertMutexAttr;
NSUInteger _syncUITaskCount;
}
+ (instancetype)sharedManager
{
static id _sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
- (instancetype)initWithWeexInstance:(id)weexInstance
{
if (self = [self init]) {
_weexInstance = weexInstance;
_syncUITaskCount = 0;
_indexDict = [NSMapTable strongToWeakObjectsMapTable];
_fixedComponents = [NSMutableArray wx_mutableArrayUsingWeakReferences];
_uiTaskQueue = [NSMutableArray array];
_displayTaskQueue = [NSMutableArray array];
_isValid = YES;
pthread_mutexattr_init(&_propertMutexAttr);
pthread_mutexattr_settype(&_propertMutexAttr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&_propertyMutex, &_propertMutexAttr);
WXPerformBlockOnComponentThread(^{
// We should ensure that [WXDisplayLinkManager sharedInstance] is only invoked in component thread.
[self _addVdomAndComponentCountTask];
[self _startDisplayLink];
});
}
return self;
}
- (instancetype)init
{
if (self == [super init]) {
_suspend = NO;
}
return self;
}
- (void)dealloc
{
[NSMutableArray wx_releaseArray:_fixedComponents];
pthread_mutex_destroy(&_propertyMutex);
pthread_mutexattr_destroy(&_propertMutexAttr);
}
#pragma mark Thread Management
+ (NSThread *)componentThread
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WXComponentThread = [[NSThread alloc] initWithTarget:[self sharedManager] selector:@selector(_runLoopThread) object:nil];
[WXComponentThread setName:WX_COMPONENT_THREAD_NAME];
[WXComponentThread setQualityOfService:[[NSThread mainThread] qualityOfService]];
[WXComponentThread start];
});
return WXComponentThread;
}
- (void)_runLoopThread
{
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!_stopRunning) {
@autoreleasepool {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
}
+ (void)_performBlockOnComponentThread:(void (^)(void))block
{
if([NSThread currentThread] == [self componentThread]){
block();
} else {
[self performSelector:@selector(_performBlockOnComponentThread:)
onThread:WXComponentThread
withObject:[block copy]
waitUntilDone:NO];
}
}
+ (void)_performBlockSyncOnComponentThread:(void (^)(void))block
{
if([NSThread currentThread] == [self componentThread]){
block();
} else {
[self performSelector:@selector(_performBlockOnComponentThread:)
onThread:WXComponentThread
withObject:[block copy]
waitUntilDone:YES];
}
}
- (void)startComponentTasks
{
[self _awakeDisplayLink];
}
- (void)rootViewFrameDidChange:(CGRect)frame
{
WXAssertComponentThread();
CGSize size = _weexInstance.frame.size;
[WXCoreBridge setDefaultDimensionIntoRoot:_weexInstance.instanceId
width:size.width height:size.height
isWidthWrapContent:size.width == 0.0f isHeightWrapContent:size.height == 0.0f];
[_rootComponent setNeedsLayout];
[self startComponentTasks];
}
- (void)_addUITask:(void (^)(void))block
{
if(!_uiPrerenderTaskQueue){
_uiPrerenderTaskQueue = [NSMutableDictionary new];
}
if(self.weexInstance.needPrerender){
NSMutableArray<dispatch_block_t> *tasks = [_uiPrerenderTaskQueue objectForKey:[WXPrerenderManager getTaskKeyFromUrl:self.weexInstance.scriptURL.absoluteString]];
if(!tasks){
tasks = [NSMutableArray new];
}
[tasks addObject:block];
[_uiPrerenderTaskQueue setObject:tasks forKey:[WXPrerenderManager getTaskKeyFromUrl:self.weexInstance.scriptURL.absoluteString]];
}else{
[_uiTaskQueue addObject:block];
}
}
- (void)executePrerenderUITask:(NSString *)url
{
NSMutableArray *tasks = [_uiPrerenderTaskQueue objectForKey:[WXPrerenderManager getTaskKeyFromUrl:self.weexInstance.scriptURL.absoluteString]];
for (id block in tasks) {
[_uiTaskQueue addObject:block];
}
tasks = [NSMutableArray new];
[_uiPrerenderTaskQueue setObject:tasks forKey:[WXPrerenderManager getTaskKeyFromUrl:self.weexInstance.scriptURL.absoluteString]];
}
#pragma mark Component Tree Building
- (void)createBody:(NSString*)ref
type:(NSString*)type
styles:(NSDictionary*)styles
attributes:(NSDictionary*)attributes
events:(NSArray*)events
renderObject:(void*)renderObject
{
WXAssertComponentThread();
WXAssertParam(ref);
WXAssertParam(type);
WXAssertParam(renderObject);
WXAssert(_rootComponent == nil, @"Create body is invoked twice.");
_rootComponent = [self _buildComponent:ref type:type supercomponent:nil styles:styles attributes:attributes events:events renderObject:renderObject];
CGSize size = _weexInstance.frame.size;
[WXCoreBridge setDefaultDimensionIntoRoot:_weexInstance.instanceId
width:size.width height:size.height
isWidthWrapContent:size.width == 0.0f isHeightWrapContent:size.height == 0.0f];
__weak typeof(self) weakSelf = self;
WX_MONITOR_INSTANCE_PERF_END(WXFirstScreenJSFExecuteTime, self.weexInstance);
[self _addUITask:^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
strongSelf.weexInstance.rootView.wx_component = strongSelf->_rootComponent;
[strongSelf.weexInstance.rootView addSubview:strongSelf->_rootComponent.view];
}];
}
- (void)addComponent:(NSString*)ref
type:(NSString*)type
parentRef:(NSString*)parentRef
styles:(NSDictionary*)styles
attributes:(NSDictionary*)attributes
events:(NSArray*)events
index:(NSInteger)index
renderObject:(void*)renderObject
{
WXAssertComponentThread();
WXAssertParam(ref);
WXAssertParam(type);
WXAssertParam(parentRef);
WXAssertParam(renderObject);
WXComponent *supercomponent = [_indexDict objectForKey:parentRef];
WXAssertComponentExist(supercomponent);
if (!supercomponent) {
WXLogWarning(@"addComponent,superRef from js never exit ! check JS action, supRef:%@", parentRef);
return;
}
if([WXAnalyzerCenter isInteractionLogOpen]){
WXLogDebug(@"wxInteractionAnalyzer: [client][addElementStart]%@,%@,%@",supercomponent.weexInstance.instanceId,type,ref);
}
supercomponent.weexInstance.apmInstance.hasAddView = YES;
WXComponent *component = [self _buildComponent:ref type:type supercomponent:supercomponent styles:styles attributes:attributes events:events renderObject:renderObject];
if (!supercomponent.subcomponents) {
index = 0;
} else {
index = (index == -1 ? supercomponent->_subcomponents.count : index);
}
if (supercomponent.ignoreInteraction) {
component.ignoreInteraction = YES;
} else {
if ([attributes objectForKey:@"ignoreInteraction"]) {
component.ignoreInteraction = [[attributes objectForKey:@"ignoreInteraction"] boolValue];
} else {
if (component->_positionType == WXPositionTypeFixed) {
component.ignoreInteraction = YES;
} else {
component.ignoreInteraction = NO;
}
}
}
#ifdef DEBUG
WXLogDebug(@"flexLayout -> _recursivelyAddComponent : super:(%@,%@):[%f,%f] ,child:(%@,%@):[%f,%f],childClass:%@",
supercomponent.type,
supercomponent.ref,
supercomponent.flexCssNode->getStyleWidth(),
supercomponent.flexCssNode->getStyleHeight(),
component.type,
component.ref,
component.flexCssNode->getStyleWidth(),
component.flexCssNode->getStyleHeight(),
NSStringFromClass([component class])
);
#endif //DEBUG
BOOL inserted = [supercomponent _insertSubcomponent:component atIndex:index];
if (!inserted) {
// component is not inserted, ignore
[component _setRenderObject:nullptr]; // unbind with RenderObject
return;
}
// use _lazyCreateView to forbid component like cell's view creating
if (supercomponent && component && supercomponent->_lazyCreateView) {
component->_lazyCreateView = YES;
}
// update max vdom depth & component count, and will update apm data on next display task.
[self recordMaximumVirtualDom:component];
if ([_indexDict count] > _maxComponentCount) {
_maxComponentCount = [_indexDict count];
}
if (!component->_isTemplate) {
__weak typeof(self) weakSelf = self;
[self _addUITask:^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[supercomponent insertSubview:component atIndex:index];
}];
}
if([WXAnalyzerCenter isInteractionLogOpen]){
WXLogDebug(@"wxInteractionAnalyzer: [client][addElementEnd]%@,%@,%@",supercomponent.weexInstance.instanceId,type,ref);
}
}
- (void)moveComponent:(NSString *)ref toSuper:(NSString *)superRef atIndex:(NSInteger)index
{
WXAssertComponentThread();
WXAssertParam(ref);
WXAssertParam(superRef);
WXComponent *component = [_indexDict objectForKey:ref];
WXComponent *newSupercomponent = [_indexDict objectForKey:superRef];
WXAssertComponentExist(component);
WXAssertComponentExist(newSupercomponent);
[component _moveToSupercomponent:newSupercomponent atIndex:index];
__weak typeof(self) weakSelf = self;
[self _addUITask:^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[component moveToSuperview:newSupercomponent atIndex:index];
}];
}
- (void)removeComponent:(NSString *)ref
{
WXAssertComponentThread();
WXAssertParam(ref);
WXComponent *component = [_indexDict objectForKey:ref];
WXAssertComponentExist(component);
if (!component) {
WXLogWarning(@"removeComponent ref from js never exit ! check JS action, ref :%@",ref);
return;
}
[component _setRenderObject:nullptr]; // unbind with RenderObject
[component _removeFromSupercomponent];
[_indexDict removeObjectForKey:ref];
// remove subcomponents of component from _indexDict and unbind them
NSMutableArray* subcomponents = [[NSMutableArray alloc] init];
[component _collectSubcomponents:subcomponents];
for (WXComponent* c in subcomponents) {
[c _setRenderObject:nullptr];
[_indexDict removeObjectForKey:c.ref];
}
__weak typeof(self) weakSelf = self;
[self _addUITask:^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
if (component.supercomponent) {
[component.supercomponent willRemoveSubview:component];
}
[component removeFromSuperview];
}];
[self _checkFixedSubcomponentToRemove:component];
}
- (void)appendTreeCreateFinish:(NSString*)ref
{
WXAssertComponentThread();
// If appending tree,force layout in case of too much tasks piling up in syncQueue
[self _layoutAndSyncUI];
}
- (void)recordMaximumVirtualDom:(WXComponent*) component
{
WXAssertComponentExist(component);
if(!component){
return;
}
int maxDeep =0;
while (component) {
maxDeep++;
component = component.supercomponent;
}
if (maxDeep > [self weexInstance].performance.maxVdomDeep) {
[self weexInstance].performance.maxVdomDeep = maxDeep;
}
if (maxDeep > _maxVdomDepth) {
_maxVdomDepth = maxDeep;
}
}
- (void)_checkFixedSubcomponentToRemove:(WXComponent *)component
{
for (WXComponent *subcomponent in component.subcomponents) {
if (subcomponent->_positionType == WXPositionTypeFixed) {
[self _addUITask:^{
[subcomponent removeFromSuperview];
}];
}
[self _checkFixedSubcomponentToRemove:subcomponent];
}
}
- (WXComponent *)componentForRef:(NSString *)ref
{
WXAssertComponentThread();
return [_indexDict objectForKey:ref];
}
- (WXComponent *)componentForRoot
{
return _rootComponent;
}
- (NSUInteger)numberOfComponents
{
WXAssertComponentThread();
return _indexDict.count;
}
- (WXComponent *)_buildComponent:(NSString *)ref
type:(NSString*)type
supercomponent:(WXComponent *)supercomponent
styles:(NSDictionary*)styles
attributes:(NSDictionary*)attributes
events:(NSArray*)events
renderObject:(void*)renderObject
{
double buildStartTime = CACurrentMediaTime()*1000;
if (self.weexInstance.needValidate) {
id<WXValidateProtocol> validateHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)];
if (validateHandler) {
WXComponentValidateResult* validateResult;
if ([validateHandler respondsToSelector:@selector(validateWithWXSDKInstance:component:supercomponent:)]) {
validateResult = [validateHandler validateWithWXSDKInstance:self.weexInstance component:type supercomponent:supercomponent];
}
if (validateResult==nil || !validateResult.isSuccess) {
type = validateResult.replacedComponent? validateResult.replacedComponent : @"div";
WXLogError(@"%@",[validateResult.error.userInfo objectForKey:@"errorMsg"]);
}
}
}
WXComponentConfig *config = [WXComponentFactory configWithComponentName:type];
BOOL isTemplate = [config.properties[@"isTemplate"] boolValue] || (supercomponent && supercomponent->_isTemplate);
NSDictionary *bindingStyles = nil;
NSDictionary *bindingAttibutes = nil;
NSDictionary *bindingEvents = nil;
NSDictionary *bindingProps = nil;
if (isTemplate) {
bindingProps = [self _extractBindingProps:&attributes];
bindingStyles = [self _extractBindings:&styles];
bindingAttibutes = [self _extractBindings:&attributes];
bindingEvents = [self _extractBindingEvents:&events];
}
Class clazz = NSClassFromString(config.clazz);
WXComponent *component = [[clazz alloc] init];
if (component) {
if (renderObject) {
[component _setRenderObject:renderObject];
}
component = [component initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:self.weexInstance];
if (isTemplate) {
component->_isTemplate = YES;
[component _storeBindingsWithProps:bindingProps styles:bindingStyles attributes:bindingAttibutes events:bindingEvents];
}
}
WXAssert(component, @"Component build failed for ref:%@, type:%@", ref, type);
[_indexDict setObject:component forKey:component.ref];
[component readyToRender];// notify redyToRender event when init
double diffTime = CACurrentMediaTime()*1000 - buildStartTime;
[self.weexInstance.performance recordComponentCreatePerformance:diffTime forComponent:component];
return component;
}
- (void)addComponent:(WXComponent *)component toIndexDictForRef:(NSString *)ref
{
[_indexDict setObject:component forKey:ref];
}
- (void)removeComponentForRef:(NSString *)ref
{
[_indexDict removeObjectForKey:ref];
}
- (NSDictionary *)_extractBindings:(NSDictionary **)attributesOrStylesPoint
{
NSDictionary *attributesOrStyles = *attributesOrStylesPoint;
if (!attributesOrStyles) {
return nil;
}
NSMutableDictionary *newAttributesOrStyles = [attributesOrStyles mutableCopy];
NSMutableDictionary *bindingAttributesOrStyles = [NSMutableDictionary dictionary];
[attributesOrStyles enumerateKeysAndObjectsUsingBlock:^(id _Nonnull attributeOrStyleName, id _Nonnull attributeOrStyle, BOOL * _Nonnull stop) {
if ([WXBindingMatchIdentify isEqualToString:attributeOrStyleName] // match
|| [WXBindingRepeatIdentify isEqualToString:attributeOrStyleName] // repeat
|| [WXBindingOnceIdentify isEqualToString:attributeOrStyleName] // once
||([attributeOrStyle isKindOfClass:[NSDictionary class]] && attributeOrStyle[WXBindingIdentify])) { // {"attributeOrStyleName": {"@binding":"bindingExpression"}
bindingAttributesOrStyles[attributeOrStyleName] = attributeOrStyle;
[newAttributesOrStyles removeObjectForKey:attributeOrStyleName];
} else if ([attributeOrStyle isKindOfClass:[NSArray class]]) {
// {"attributeOrStyleName":[..., "string", {"@binding":"bindingExpression"}, "string", {"@binding":"bindingExpression"}, ...]
__block BOOL isBinding = NO;
[attributeOrStyle enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:[NSDictionary class]] && obj[WXBindingIdentify]) {
isBinding = YES;
*stop = YES;
}
}];
if (isBinding) {
bindingAttributesOrStyles[attributeOrStyleName] = attributeOrStyle;
[newAttributesOrStyles removeObjectForKey:attributeOrStyleName];
}
}
}];
*attributesOrStylesPoint = newAttributesOrStyles;
return bindingAttributesOrStyles;
}
- (NSDictionary *)_extractBindingEvents:(NSArray **)eventsPoint
{
NSArray *events = *eventsPoint;
if (events == nil) {
return nil;
}
NSMutableArray *newEvents = [events mutableCopy];
NSMutableDictionary *bindingEvents = [NSMutableDictionary dictionary];
[events enumerateObjectsUsingBlock:^(id _Nonnull event, NSUInteger idx, BOOL * _Nonnull stop) {
if ([event isKindOfClass:[NSDictionary class]] && event[@"type"] && event[@"params"]) {
NSString *eventName = event[@"type"];
NSString *bindingParams = event[@"params"];
bindingEvents[eventName] = bindingParams;
newEvents[idx] = eventName;
}
}];
*eventsPoint = newEvents;
return bindingEvents;
}
- (NSDictionary *)_extractBindingProps:(NSDictionary **)attributesPoint
{
NSDictionary *attributes = *attributesPoint;
if (attributes == nil) {
return nil;
}
if (attributes[@"@componentProps"]) {
NSMutableDictionary *newAttributes = [attributes mutableCopy];
[newAttributes removeObjectForKey:@"@componentProps"];
*attributesPoint = newAttributes;
return attributes[@"@componentProps"];
}
return nil;
}
#pragma mark Reset
- (BOOL)isShouldReset:(id )value
{
if([value isKindOfClass:[NSString class]]) {
if(!value || [@"" isEqualToString:value]) {
return YES;
}
}
return NO;
}
- (void)filterStyles:(NSDictionary *)styles normalStyles:(NSMutableDictionary *)normalStyles resetStyles:(NSMutableArray *)resetStyles
{
for (NSString *key in styles) {
id value = [styles objectForKey:key];
if([self isShouldReset:value]) {
[resetStyles addObject:key];
}else{
[normalStyles setObject:styles[key] forKey:key];
}
}
}
- (void)updateStyles:(NSDictionary *)styles forComponent:(NSString *)ref
{
[self handleStyles:styles forComponent:ref isUpdateStyles:YES];
}
- (void)updatePseudoClassStyles:(NSDictionary *)styles forComponent:(NSString *)ref
{
[self handleStyles:styles forComponent:ref isUpdateStyles:NO];
}
- (void)handleStyleOnMainThread:(NSDictionary*)styles forComponent:(WXComponent *)component isUpdateStyles:(BOOL)isUpdateStyles
{
WXAssertParam(styles);
WXAssertParam(component);
WXAssertMainThread();
NSMutableDictionary *normalStyles = [NSMutableDictionary new];
NSMutableArray *resetStyles = [NSMutableArray new];
[self filterStyles:styles normalStyles:normalStyles resetStyles:resetStyles];
[component _updateStylesOnMainThread:normalStyles resetStyles:resetStyles];
[component readyToRender];
NSDictionary* dupStyles = [NSDictionary dictionaryWithDictionary:normalStyles];
WXPerformBlockOnComponentThread(^{
[component _updateStylesOnComponentThread:dupStyles resetStyles:resetStyles isUpdateStyles:isUpdateStyles];
});
}
- (void)handleStyles:(NSDictionary *)styles forComponent:(NSString *)ref isUpdateStyles:(BOOL)isUpdateStyles
{
WXAssertParam(styles);
WXAssertParam(ref);
WXComponent *component = [_indexDict objectForKey:ref];
WXAssertComponentExist(component);
NSMutableDictionary *normalStyles = [NSMutableDictionary new];
NSMutableArray *resetStyles = [NSMutableArray new];
[self filterStyles:styles normalStyles:normalStyles resetStyles:resetStyles];
[component _updateStylesOnComponentThread:normalStyles resetStyles:resetStyles isUpdateStyles:isUpdateStyles];
NSDictionary* dupStyles = [NSDictionary dictionaryWithDictionary:normalStyles];
[self _addUITask:^{
[component _updateStylesOnMainThread:dupStyles resetStyles:resetStyles];
[component readyToRender];
}];
}
- (void)updateAttributes:(NSDictionary *)attributes forComponent:(NSString *)ref
{
WXAssertParam(attributes);
WXAssertParam(ref);
WXComponent *component = [_indexDict objectForKey:ref];
[component _updateAttributesOnComponentThread:attributes];
__weak typeof(self) weakSelf = self;
[self _addUITask:^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
[component _updateAttributesOnMainThread:attributes];
[component readyToRender];
}];
}
- (BOOL)isTransitionNoneOfComponent:(NSString*)ref
{
WXAssertComponentThread();
WXComponent *component = [_indexDict objectForKey:ref];
WXAssertComponentExist(component);
return [component _isTransitionNone];
}
- (BOOL)hasTransitionPropertyInStyles:(NSDictionary*)styles forComponent:(NSString*)ref
{
WXAssertComponentThread();
WXComponent *component = [_indexDict objectForKey:ref];
WXAssertComponentExist(component);
return [component _hasTransitionPropertyInStyles:styles];
}
- (void)layoutComponent:(WXComponent*)component frame:(CGRect)frame isRTL:(BOOL)isRTL innerMainSize:(CGFloat)innerMainSize
{
WXAssertComponentThread();
WXAssertParam(component);
[component _setIsLayoutRTL:isRTL];
if (component == _rootComponent) {
// Synchronize view frame with root component, especially for content wrap mode.
WXPerformBlockOnMainThread(^{
if (!self.weexInstance.isRootViewFrozen &&
(!CGSizeEqualToSize(frame.size, self.weexInstance.frame.size) || !CGSizeEqualToSize(frame.size, self.weexInstance.rootView.frame.size))) {
CGRect rect = self.weexInstance.rootView.frame; // no change of origin
rect.size = frame.size;
self.weexInstance.rootView.frame = rect;
}
});
}
if ([component _isCalculatedFrameChanged:frame]) {
[component _assignCalculatedFrame:frame];
[component _assignInnerContentMainSize:innerMainSize];
[component _frameDidCalculated:YES];
[self _addUITask:^{
[component _layoutDidFinish];
}];
}
else {
CGFloat oldValue = [component _getInnerContentMainSize];
if (oldValue >= 0 && oldValue != innerMainSize) {
[component _assignCalculatedFrame:frame];
[component _assignInnerContentMainSize:innerMainSize];
[component _frameDidCalculated:YES];
[self _addUITask:^{
[component _layoutDidFinish];
}];
}
else {
[component _frameDidCalculated:NO];
}
}
}
- (void)layoutComponent:(WXComponent*)component
{
WXAssertComponentThread();
WXAssertParam(component);
[component _layoutPlatform];
}
- (void)addEvent:(NSString *)event toComponent:(NSString *)ref
{
WXAssertComponentThread();
WXAssertParam(event);
WXAssertParam(ref);
WXComponent *component = [_indexDict objectForKey:ref];
WXAssertComponentExist(component);
[component _addEventOnComponentThread:event];
[self _addUITask:^{
[component _addEventOnMainThread:event];
}];
}
- (void)removeEvent:(NSString *)event fromComponent:(NSString *)ref
{
WXAssertComponentThread();
WXAssertParam(event);
WXAssertParam(ref);
WXComponent *component = [_indexDict objectForKey:ref];
WXAssertComponentExist(component);
[component _removeEventOnComponentThread:event];
[self _addUITask:^{
[component _removeEventOnMainThread:event];
}];
}
- (void)scrollToComponent:(NSString *)ref options:(NSDictionary *)options
{
WXAssertComponentThread();
WXAssertParam(ref);
WXComponent *toComponent = [_indexDict objectForKey:ref];
WXAssertComponentExist(toComponent);
id<WXScrollerProtocol> scrollerComponent = toComponent.ancestorScroller;
if (!scrollerComponent) {
return;
}
CGFloat offset = [[options objectForKey:@"offset"] floatValue];
BOOL animated = YES;
if ([options objectForKey:@"animated"]) {
animated = [[options objectForKey:@"animated"] boolValue];
}
[self _addUITask:^{
[scrollerComponent scrollToComponent:toComponent withOffset:offset animated:animated];
}];
}
#pragma mark Life Cycle
- (void)createFinish
{
WXAssertComponentThread();
WXSDKInstance *instance = self.weexInstance;
[self _addUITask:^{
WX_MONITOR_INSTANCE_PERF_END(WXPTFirstScreenRender, instance);
WX_MONITOR_INSTANCE_PERF_END(WXPTAllRender, instance);
WX_MONITOR_SUCCESS(WXMTJSBridge);
WX_MONITOR_SUCCESS(WXMTNativeRender);
}];
[instance updatePerDicAfterCreateFinish];
}
- (void)updateFinish
{
WXAssertComponentThread();
WXSDKInstance *instance = self.weexInstance;
WXComponent *root = [_indexDict objectForKey:WX_SDK_ROOT_REF];
[self _addUITask:^{
if (instance.updateFinish) {
instance.updateFinish(root.view);
}
}];
}
- (void)refreshFinish
{
WXAssertComponentThread();
WXSDKInstance *instance = self.weexInstance;
WXComponent *root = [_indexDict objectForKey:WX_SDK_ROOT_REF];
[self _addUITask:^{
if (instance.refreshFinish) {
instance.refreshFinish(root.view);
}
}];
}
- (void)renderFinish
{
WXAssertComponentThread();
WXSDKInstance *instance = self.weexInstance;
[self _addUITask:^{
UIView *rootView = instance.rootView;
[instance.performance onInstanceRenderSuccess:instance];
if (instance.renderFinish) {
instance.renderFinish(rootView);
}
}];
}
- (void)renderFailed:(NSError *)error {
WXAssertComponentThread();
WXSDKInstance *instance = self.weexInstance;
[self _addUITask:^{
if (instance.onFailed) {
instance.onFailed(error);
}
}];
}
- (void)unload
{
WXAssertComponentThread();
[self invalidate];
[self _stopDisplayLink];
// first, unbind with underneath RenderObjects
{
NSEnumerator* enumerator = [_indexDict objectEnumerator];
WXComponent *component;
while ((component = [enumerator nextObject])) {
[component _setRenderObject:nullptr];
}
}
// second, unload views and finally release components in UI thread
{
__block WXComponent* rootComponent = _rootComponent;
NSEnumerator *enumerator = [[_indexDict copy] objectEnumerator];
dispatch_async(dispatch_get_main_queue(), ^{
WXComponent *component;
while ((component = [enumerator nextObject])) {
[component _unloadViewWithReusing:NO];
}
rootComponent = nil; // finally release all components
});
}
// clear containers
_rootComponent = nil;
[_indexDict removeAllObjects];
[_fixedComponents removeAllObjects];
[_uiTaskQueue removeAllObjects];
}
- (void)invalidate
{
_isValid = NO;
}
- (BOOL)isValid
{
return _isValid;
}
#pragma mark Display link task
- (void)_addVdomAndComponentCountTask
{
__weak WXComponentManager* wself = self;
[_displayTaskQueue addObject:[WXComponentThreadTickTask taskWithBlock:^{
__strong WXComponentManager* sself = wself;
if (sself) {
if (sself->_maxComponentCount != sself->_maxComponentCountReported) {
[sself.weexInstance.apmInstance updateMaxStats:KEY_PAGE_STATS_MAX_COMPONENT_NUM curMaxValue:sself->_maxComponentCount];
sself->_maxComponentCountReported = sself->_maxComponentCount;
}
if (sself->_maxVdomDepth != sself->_maxVdomDepthReported) {
[sself.weexInstance.apmInstance updateMaxStats:KEY_PAGE_STATS_MAX_DEEP_DOM curMaxValue:sself->_maxVdomDepth];
sself->_maxVdomDepthReported = sself->_maxVdomDepth;
}
}
} tickCount:30 /* triggered about every 500ms */]];
}
- (void)_startDisplayLink
{
WXAssertComponentThread();
[[WXDisplayLinkManager sharedInstance] registerDisplayClient:self];
_displayTick = 0;
}
- (void)_stopDisplayLink
{
WXAssertComponentThread();
[[WXDisplayLinkManager sharedInstance] unregisterDisplayClient:self];
}
- (void)_suspendDisplayLink
{
WXAssertComponentThread();
_suspend = YES;
[self _executeDisplayTask:YES]; // on suspend, executes every task once
}
- (void)_awakeDisplayLink
{
WXAssertComponentThread();
_suspend = NO;
_displayTick = 0;
}
- (void)_handleDisplayLink
{
WXAssertComponentThread();
[self _layoutAndSyncUI];
if (!_suspend) {
// execute tasks in _displayTaskQueue
_displayTick ++;
[self _executeDisplayTask:NO];
}
}
- (void)_executeDisplayTask:(BOOL)onSuspend
{
for (WXComponentThreadTickTask* task in _displayTaskQueue) {
if (onSuspend || (_displayTick % task.displayTickCount == 0)) {
if (task.block) {
task.block();
}
}
}
}
- (void)_layoutAndSyncUI
{
[self _layout];
if(_uiTaskQueue.count > 0){
[self _syncUITasks];
_noTaskTickCount = 0;
} else {
// suspend display link when there's no task for 1 second, in order to save CPU time.
_noTaskTickCount ++;
if (_noTaskTickCount > 60) {
[self _suspendDisplayLink];
}
}
}
- (void)_layout
{
[WXCoreBridge layoutPage:_weexInstance.instanceId forced:[_rootComponent needsLayout]];
}
- (void) _printFlexComponentFrame:(WXComponent *)component
{
#ifdef DEBUG
WXLogDebug(@"node ref:%@, type:%@ , frame:%@",
component.ref,
component.type,
NSStringFromCGRect(component.view.layer.frame)
);
#endif
for (WXComponent *childComponent in component.subcomponents) {
[self _printFlexComponentFrame:childComponent];
}
}
- (void)_syncUITasks
{
NSInteger mismatchBeginIndex = _uiTaskQueue.count;
for (NSInteger i = _uiTaskQueue.count - 1;i >= 0;i --) {
if (_uiTaskQueue[i] == WXPerformUITaskBatchEndBlock) {
_syncUITaskCount = 0;
// clear when find the matches for end and begin tag
break;
}
if (_uiTaskQueue[i] == WXPerformUITaskBatchBeginBlock) {
mismatchBeginIndex = i;
break;
}
}
if (mismatchBeginIndex == _uiTaskQueue.count) {//!OCLint
// here we get end tag or there are not begin and end directives
} else {
_syncUITaskCount ++;
// we only find begin tag but missing end tag,
if (_syncUITaskCount > (MAX_DROP_FRAME_FOR_BATCH)) {
// when the wait times come to MAX_DROP_FRAME_FOR_BATCH, we will pop all the stashed operations for user experience.
mismatchBeginIndex = _uiTaskQueue.count;
_syncUITaskCount = 0;
}
}
if (mismatchBeginIndex > 0) {
NSArray<dispatch_block_t> *blocks = [_uiTaskQueue subarrayWithRange:NSMakeRange(0, mismatchBeginIndex)];
[_uiTaskQueue removeObjectsInRange:NSMakeRange(0, mismatchBeginIndex)];
if (blocks.count) {
dispatch_async(dispatch_get_main_queue(), ^{
for(dispatch_block_t block in blocks) {
block();
}
});
}
}
}
#pragma mark Fixed
- (void)addFixedComponent:(WXComponent *)fixComponent
{
pthread_mutex_lock(&_propertyMutex);
[_fixedComponents addObject:fixComponent];
pthread_mutex_unlock(&_propertyMutex);
}
- (void)removeFixedComponent:(WXComponent *)fixComponent
{
pthread_mutex_lock(&_propertyMutex);
[_fixedComponents removeObject:fixComponent];
pthread_mutex_unlock(&_propertyMutex);
}
#pragma mark Enumerating
- (void)enumerateComponentsUsingBlock:(void (^)(WXComponent *, BOOL *stop))block
{
if (block == nil || _rootComponent == nil) {
return;
}
NSMutableArray* components = [[NSMutableArray alloc] init];
[components addObject:_rootComponent];
while ([components count] > 0) {
BOOL stop = NO;
NSArray* thisLevelComponents = [components copy];
[components removeAllObjects];
// enumerate thisLevelComponents and add next level components to components
for (WXComponent* c in thisLevelComponents) {
block(c, &stop);
if (stop) {
break;
}
for (WXComponent* nextLevelComponent in c->_subcomponents) {
[components addObject:nextLevelComponent];
}
}
if (stop) {
break;
}
}
}
static void (^WXPerformUITaskBatchBeginBlock)(void) = ^ () {
#if DEBUG
WXLogDebug(@"directive BatchBeginBlock");
#endif
};
static void (^WXPerformUITaskBatchEndBlock)(void) = ^ () {
#if DEBUG
WXLogDebug(@"directive BatchEndBlock");
#endif
};
- (void)performBatchBegin
{
[self _addUITask:WXPerformUITaskBatchBeginBlock];
}
- (void)performBatchEnd
{
[self _addUITask:WXPerformUITaskBatchEndBlock];
}
- (void)handleDisplayLink {
[self _handleDisplayLink];
}
@synthesize suspend=_suspend;
@end
void WXPerformBlockOnComponentThread(void (^block)(void))
{
[WXComponentManager _performBlockOnComponentThread:block];
}
void WXPerformBlockSyncOnComponentThread(void (^block)(void))
{
[WXComponentManager _performBlockSyncOnComponentThread:block];
}