| /* |
| * 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]; |
| } |