| /* |
| * 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 "WXSDKInstance.h" |
| #import "WXSDKInstance_private.h" |
| #import "WXSDKManager.h" |
| #import "WXSDKError.h" |
| #import "WXMonitor.h" |
| #import "WXAppMonitorProtocol.h" |
| #import "WXNetworkProtocol.h" |
| #import "WXModuleFactory.h" |
| #import "WXHandlerFactory.h" |
| #import "WXDebugTool.h" |
| #import "WXUtility.h" |
| #import "WXAssert.h" |
| #import "WXLog.h" |
| #import "WXView.h" |
| #import "WXRootView.h" |
| #import "WXThreadSafeMutableDictionary.h" |
| #import "WXResourceRequest.h" |
| #import "WXResourceResponse.h" |
| #import "WXResourceLoader.h" |
| #import "WXSDKEngine.h" |
| #import "WXValidateProtocol.h" |
| #import "WXConfigCenterProtocol.h" |
| #import "WXTextComponent.h" |
| #import "WXConvert.h" |
| #import "WXPrerenderManager.h" |
| #import "WXJSExceptionProtocol.h" |
| #import "WXExceptionUtils.h" |
| #import "WXMonitor.h" |
| #import "WXBridgeContext.h" |
| #import "WXJSCoreBridge.h" |
| #import "WXSDKInstance_performance.h" |
| #import "WXPageEventNotifyEvent.h" |
| #import "WXConvertUtility.h" |
| #import "WXCoreBridge.h" |
| #import "WXDataRenderHandler.h" |
| #import "WXDarkSchemeProtocol.h" |
| #import "WXDarkSchemeModule.h" |
| |
| #define WEEX_LITE_URL_SUFFIX @"wlasm" |
| #define WEEX_RENDER_TYPE_PLATFORM @"platform" |
| |
| NSString *const bundleUrlOptionKey = @"bundleUrl"; |
| NSString *const bundleResponseUrlOptionKey = @"bundleResponseUrl"; |
| |
| NSTimeInterval JSLibInitTime = 0; |
| |
| static NSString* lastPageInfoLock = @""; |
| static NSDictionary* lastPageInfo = nil; |
| |
| typedef enum : NSUInteger { |
| WXLoadTypeNormal, |
| WXLoadTypeBack, |
| WXLoadTypeForward, |
| WXLoadTypeReload, |
| WXLoadTypeReplace |
| } WXLoadType; |
| |
| @implementation WXSDKInstance |
| { |
| NSDictionary *_options; |
| id _jsData; |
| |
| WXResourceLoader *_mainBundleLoader; |
| WXComponentManager *_componentManager; |
| UIView *_rootView; |
| WXThreadSafeMutableDictionary *_moduleEventObservers; |
| BOOL _performanceCommit; |
| BOOL _debugJS; |
| id<WXBridgeProtocol> _instanceJavaScriptContext; // sandbox javaScript context |
| BOOL _defaultDataRender; |
| } |
| |
| - (void)dealloc |
| { |
| [_moduleEventObservers removeAllObjects]; |
| [self removeObservers]; |
| } |
| |
| - (instancetype)init |
| { |
| return [self initWithRenderType:WEEX_RENDER_TYPE_PLATFORM]; |
| } |
| |
| - (instancetype)initWithRenderType:(NSString*)renderType |
| { |
| self = [super init]; |
| if (self) { |
| if ([WXUtility isDarkSchemeSupportEnabled]) { |
| self.schemeName = [WXUtility isSystemInDarkScheme] ? @"dark" : @"light"; |
| } |
| else { |
| self.schemeName = @"light"; |
| } |
| |
| // Update scheme value in JS environment. |
| if (([WXUtility isEnvironmentUsingDarkScheme] && [self.schemeName isEqualToString:@"light"]) || |
| (![WXUtility isEnvironmentUsingDarkScheme] && [self.schemeName isEqualToString:@"dark"])) { |
| [[WXBridgeManager sharedManager] resetEnvironment]; |
| } |
| |
| _autoInvertingBehavior = WXAutoInvertingBehaviorDefault; |
| _renderType = renderType; |
| _appearState = YES; |
| |
| NSInteger instanceId = 0; |
| @synchronized(bundleUrlOptionKey) { |
| static NSInteger __instance = 0; |
| instanceId = __instance % (1024*1024); |
| __instance += 2; // always add by 2 as even number |
| } |
| |
| _instanceId = [NSString stringWithFormat:@"%ld", (long)instanceId]; |
| |
| if (self.isCustomRenderType) { |
| // check render type is available |
| NSSet* availableRenderTypes = [WXCustomPageBridge getAvailableCustomRenderTypes]; |
| if ([availableRenderTypes containsObject:_renderType]) { |
| // custom render page, we use odd instanceId, and (instanceId + 1) is sure not used by other pages. |
| _instanceId = [NSString stringWithFormat:@"%ld", (long)(instanceId + 1)]; |
| [WXCoreBridge setPageArgument:_instanceId key:@"renderType" value:_renderType]; |
| } |
| else { |
| WXLogError(@"Unsupported render type '%@'. Regress to platform target.", _renderType); |
| _renderType = WEEX_RENDER_TYPE_PLATFORM; |
| } |
| } |
| |
| WXLogInfo(@"Create instance: %@, render type: %@", _instanceId, _renderType); |
| |
| // TODO self is retained here. |
| [WXSDKManager storeInstance:self forID:_instanceId]; |
| |
| _bizType = @""; |
| _pageName = @""; |
| |
| _performanceDict = [WXThreadSafeMutableDictionary new]; |
| _moduleInstances = [WXThreadSafeMutableDictionary new]; |
| _styleConfigs = [NSMutableDictionary new]; |
| _attrConfigs = [NSMutableDictionary new]; |
| _moduleEventObservers = [WXThreadSafeMutableDictionary new]; |
| _trackComponent = NO; |
| _performanceCommit = NO; |
| |
| _performance = [[WXPerformance alloc] init]; |
| _apmInstance = [[WXApmForInstance alloc] init]; |
| |
| _defaultDataRender = NO; |
| |
| _useBackupJsThread = NO; |
| |
| [self addObservers]; |
| } |
| return self; |
| } |
| |
| - (BOOL)isCustomRenderType |
| { |
| return ![_renderType isEqualToString:WEEX_RENDER_TYPE_PLATFORM]; |
| } |
| |
| - (id<WXBridgeProtocol>)instanceJavaScriptContext |
| { |
| _debugJS = [WXDebugTool isDevToolDebug]; |
| |
| Class bridgeClass = _debugJS ? NSClassFromString(@"WXDebugger") : [WXJSCoreBridge class]; |
| |
| if (_instanceJavaScriptContext && [_instanceJavaScriptContext isKindOfClass:bridgeClass]) { |
| return _instanceJavaScriptContext; |
| } |
| |
| WXAssertBridgeThread(); |
| if (_instanceJavaScriptContext) { |
| _instanceJavaScriptContext = nil; |
| } |
| |
| // WXDebugger is a singleton actually and should not call its init twice. |
| _instanceJavaScriptContext = _debugJS ? [NSClassFromString(@"WXDebugger") alloc] : [[WXJSCoreBridge alloc] initWithoutDefaultContext]; |
| if (!_debugJS) { |
| id<WXBridgeProtocol> jsBridge = [[WXSDKManager bridgeMgr] valueForKeyPath:@"bridgeCtx.jsBridge"]; |
| if (_useBackupJsThread) { |
| jsBridge = [[WXSDKManager bridgeMgr] valueForKeyPath:@"backupBridgeCtx.jsBridge"]; |
| } |
| JSContext* globalContex = jsBridge.javaScriptContext; |
| JSContextGroupRef contextGroup = JSContextGetGroup([globalContex JSGlobalContextRef]); |
| JSClassDefinition classDefinition = kJSClassDefinitionEmpty; |
| classDefinition.attributes = kJSClassAttributeNoAutomaticPrototype; |
| JSClassRef globalObjectClass = JSClassCreate(&classDefinition); |
| JSGlobalContextRef sandboxGlobalContextRef = JSGlobalContextCreateInGroup(contextGroup, globalObjectClass); |
| JSClassRelease(globalObjectClass); |
| JSContext * instanceContext = [JSContext contextWithJSGlobalContextRef:sandboxGlobalContextRef]; |
| JSGlobalContextRelease(sandboxGlobalContextRef); |
| [WXBridgeContext mountContextEnvironment:instanceContext]; |
| [_instanceJavaScriptContext setJSContext:instanceContext]; |
| } |
| |
| if ([_instanceJavaScriptContext respondsToSelector:@selector(setWeexInstanceId:)]) { |
| [_instanceJavaScriptContext setWeexInstanceId:_instanceId]; |
| } |
| if (!_debugJS) { |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_INSTANCE_JSCONTEXT_CREATE_NOTIFICATION object:_instanceJavaScriptContext.javaScriptContext]; |
| } |
| |
| return _instanceJavaScriptContext; |
| } |
| |
| - (NSString *)description |
| { |
| // get _rootView.frame in JS thread may cause deaklock. |
| return [NSString stringWithFormat:@"<%@: %p; id = %@; rootView = %p; url= %@>", NSStringFromClass([self class]), self, _instanceId, (__bridge void*)_rootView, _scriptURL]; |
| } |
| |
| - (void)setParentInstance:(WXSDKInstance *)parentInstance |
| { |
| WXLogInfo(@"Embed instance %@ into parent instance %@", _instanceId, parentInstance.instanceId); |
| _parentInstance = parentInstance; |
| } |
| |
| #pragma mark Public Mehtods |
| |
| - (UIView *)rootView |
| { |
| return _rootView; |
| } |
| |
| - (void)setFrame:(CGRect)frame |
| { |
| #ifdef DEBUG |
| WXLogDebug(@"flexLayout -> setFrame :%@,instance :%@",NSStringFromCGRect(frame),self); |
| #endif |
| if (!CGRectEqualToRect(frame, _frame)) { |
| _frame = frame; |
| CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height; |
| if (screenHeight>0) { |
| CGFloat pageRatio = frame.size.height/screenHeight *100; |
| self.apmInstance.pageRatio = pageRatio>100?100:pageRatio; |
| } |
| WXPerformBlockOnMainThread(^{ |
| if (_rootView) { |
| _rootView.frame = frame; |
| WXPerformBlockOnComponentThread(^{ |
| [self.componentManager rootViewFrameDidChange:frame]; |
| }); |
| } |
| }); |
| } |
| } |
| |
| - (void)setViewportWidth:(CGFloat)viewportWidth |
| { |
| _viewportWidth = viewportWidth; |
| |
| // notify weex core |
| NSString* pageId = _instanceId; |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge setViewportWidth:pageId width:viewportWidth]; |
| }); |
| } |
| |
| - (void)setPageKeepRawCssStyles |
| { |
| [self setPageArgument:@"reserveCssStyles" value:@"true"]; |
| } |
| |
| - (void)isKeepingRawCssStyles:(void(^)(BOOL))callback { |
| NSString* pageId = _instanceId; |
| WXPerformBlockOnComponentThread(^{ |
| if (callback) { |
| callback([WXCoreBridge isKeepingRawCssStyles:pageId]); |
| } |
| }); |
| } |
| |
| - (void)setPageArgument:(NSString*)key value:(NSString*)value |
| { |
| NSString* pageId = _instanceId; |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge setPageArgument:pageId key:key value:value]; |
| }); |
| } |
| |
| - (void)setScriptURL:(NSURL *)scriptURL |
| { |
| _scriptURL = scriptURL; |
| [WXCoreBridge setPageArgument:_instanceId key:@"url" value:[_scriptURL absoluteString]]; |
| } |
| |
| - (void)setPageRequiredWidth:(CGFloat)width height:(CGFloat)height |
| { |
| _screenSize = CGSizeMake(width, height); |
| |
| // notify weex core |
| NSString* pageId = _instanceId; |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge setPageRequired:pageId width:width height:height]; |
| }); |
| } |
| |
| - (void)renderWithURL:(NSURL *)url |
| { |
| [self renderWithURL:url options:nil data:nil]; |
| } |
| |
| - (void)_checkPageName |
| { |
| if (nil == self.pageName || [self.pageName isEqualToString:@""]) { |
| self.pageName = [self.scriptURL isFileURL] ? self.scriptURL.path.lastPathComponent: self.scriptURL.absoluteString; |
| } |
| if (nil == self.pageName || [self.pageName isEqualToString:@""]) { |
| self.pageName = NSStringFromClass(self.viewController.class)?:@"unkonwPageCauseUnsetNameAndUrlAndVc"; |
| } |
| } |
| |
| - (void)renderWithURL:(NSURL *)url options:(NSDictionary *)options data:(id)data |
| { |
| if (!url) { |
| WXLogError(@"Url must be passed if you use renderWithURL"); |
| return; |
| } |
| WXLogInfo(@"pageid: %@ renderWithURL: %@", _instanceId, url.absoluteString); |
| |
| @synchronized (lastPageInfoLock) { |
| lastPageInfo = @{@"pageId": [_instanceId copy], @"url": [url absoluteString] ?: @""}; |
| } |
| |
| [WXCoreBridge install]; |
| if (_useBackupJsThread) { |
| [[WXSDKManager bridgeMgr] executeJSTaskQueue]; |
| } |
| |
| self.scriptURL = url; |
| [self _checkPageName]; |
| [self.apmInstance startRecord:self.instanceId]; |
| self.apmInstance.isStartRender = YES; |
| |
| self.needValidate = [[WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)] needValidate:url]; |
| WXResourceRequest *request = [WXResourceRequest requestWithURL:url resourceType:WXResourceTypeMainBundle referrer:@"" cachePolicy:NSURLRequestUseProtocolCachePolicy]; |
| [self _renderWithRequest:request options:options data:data]; |
| |
| NSURL* nsURL = [NSURL URLWithString:options[@"DATA_RENDER_JS"]]; |
| [self _downloadAndExecScript:nsURL]; |
| } |
| |
| - (void)renderView:(id)source options:(NSDictionary *)options data:(id)data |
| { |
| _options = [options isKindOfClass:[NSDictionary class]] ? options : nil; |
| _jsData = data; |
| WXLogInfo(@"pageid: %@ renderView pageNmae: %@ options: %@", _instanceId, _pageName, options); |
| |
| @synchronized (lastPageInfoLock) { |
| lastPageInfo = @{@"pageId": [_instanceId copy], @"options": options ? [options description] : @""}; |
| } |
| |
| [WXCoreBridge install]; |
| if (_useBackupJsThread) { |
| [[WXSDKManager bridgeMgr] executeJSTaskQueue]; |
| } |
| |
| self.needValidate = [[WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)] needValidate:self.scriptURL]; |
| |
| if ([source isKindOfClass:[NSString class]]) { |
| WXLogDebug(@"Render source: %@, data:%@", self, [WXUtility JSONString:data]); |
| [self _renderWithMainBundleString:source]; |
| } else if ([source isKindOfClass:[NSData class]]) { |
| [self _renderWithData:source]; |
| } |
| NSURL* nsURL = [NSURL URLWithString:options[@"DATA_RENDER_JS"]]; |
| [self _downloadAndExecScript:nsURL]; |
| } |
| |
| - (void)_downloadAndExecScript:(NSURL *)url { |
| [[WXSDKManager bridgeMgr] DownloadJS:_instanceId url:url completion:^(NSString *script) { |
| if (!script) { |
| return; |
| } |
| if (self.dataRender) { |
| id<WXDataRenderHandler> dataRenderHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXDataRenderHandler)]; |
| if (dataRenderHandler) { |
| [[WXSDKManager bridgeMgr] createInstanceForJS:_instanceId template:script options:_options data:_jsData]; |
| |
| NSString* instanceId = self.instanceId; |
| WXPerformBlockOnComponentThread(^{ |
| [dataRenderHandler DispatchPageLifecycle:instanceId]; |
| }); |
| } |
| else { |
| if (self.componentManager.isValid) { |
| WXSDKErrCode errorCode = WX_KEY_EXCEPTION_DEGRADE_EAGLE_RENDER_ERROR; |
| NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN code:errorCode userInfo:@{@"message":@"No data render handler found!"}]; |
| WXPerformBlockOnComponentThread(^{ |
| [self.componentManager renderFailed:error]; |
| }); |
| } |
| } |
| return; |
| } |
| }]; |
| } |
| |
| - (NSString *) bundleTemplate |
| { |
| return self.mainBundleString; |
| } |
| |
| - (void)_renderWithData:(NSData *)contents |
| { |
| if (!self.instanceId) { |
| WXLogError(@"Fail to find instance!"); |
| return; |
| } |
| |
| if (_isRendered) { |
| [WXExceptionUtils commitCriticalExceptionRT:self.instanceId errCode:[NSString stringWithFormat:@"%d", WX_ERR_RENDER_TWICE] function:@"_renderWithData:" exception:[NSString stringWithFormat:@"instance is rendered twice"] extParams:nil]; |
| return; |
| } |
| |
| //some case , with out render (url) |
| [self.apmInstance startRecord:self.instanceId]; |
| self.apmInstance.isStartRender = YES; |
| |
| [_apmInstance setProperty:KEY_PAGE_PROPERTIES_UIKIT_TYPE withValue:_renderType?: WEEX_RENDER_TYPE_PLATFORM]; |
| if (self.dataRender) { |
| [self.apmInstance setProperty:KEY_PAGE_PROPERTIES_RENDER_TYPE withValue:@"eagle"]; |
| } |
| |
| self.performance.renderTimeOrigin = CACurrentMediaTime()*1000; |
| self.performance.renderUnixTimeOrigin = [WXUtility getUnixFixTimeMillis]; |
| long renderOriginTimePlatform = [self.apmInstance onStage:KEY_PAGE_STAGES_RENDER_ORGIGIN]; |
| |
| // pass render origin time to page |
| [WXCoreBridge setPageArgument:_instanceId key:@"renderTimeOrigin" value:[NSString stringWithFormat:@"%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000)]]; |
| [WXCoreBridge setPageArgument:_instanceId key:@"renderTimeOriginPlatform" value:[NSString stringWithFormat:@"%lld", (long long)renderOriginTimePlatform]]; |
| |
| if (![WXUtility isBlankString:self.pageName]) { |
| WXLog(@"Start rendering page:%@", self.pageName); |
| } else { |
| WXLogWarning(@"WXSDKInstance's pageName should be specified."); |
| id<WXJSExceptionProtocol> jsExceptionHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXJSExceptionProtocol)]; |
| if ([jsExceptionHandler respondsToSelector:@selector(onRuntimeCheckException:)]) { |
| WXRuntimeCheckException * runtimeCheckException = [WXRuntimeCheckException new]; |
| runtimeCheckException.exception = @"We highly recommend you to set pageName.\n Using WXSDKInstance * instance = [WXSDKInstance new]; instance.pageName = @\"your page name\" to fix it"; |
| [jsExceptionHandler onRuntimeCheckException:runtimeCheckException]; |
| } |
| } |
| |
| id<WXPageEventNotifyEventProtocol> pageEvent = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)]; |
| if ([pageEvent respondsToSelector:@selector(pageStart:)]) { |
| [pageEvent pageStart:self.instanceId]; |
| } |
| |
| WX_MONITOR_INSTANCE_PERF_START(WXPTFirstScreenRender, self); |
| WX_MONITOR_INSTANCE_PERF_START(WXPTAllRender, self); |
| |
| NSMutableDictionary *dictionary = [_options mutableCopy]; |
| if ([WXLog logLevel] >= WXLogLevelLog) { |
| dictionary[@"debug"] = @(YES); |
| } |
| |
| WXPerformBlockOnMainThread(^{ |
| if (self.isCustomRenderType) { |
| self->_rootView = [WXCustomPageBridge createPageRootView:self.instanceId pageType:self.renderType frame:self.frame]; |
| } |
| else { |
| self->_rootView = [[WXRootView alloc] initWithFrame:self.frame]; |
| ((WXRootView*)(self->_rootView)).instance = self; |
| } |
| |
| if (self.onCreate) { |
| self.onCreate(self->_rootView); |
| } |
| }); |
| // ensure default modules/components/handlers are ready before create instance |
| [WXSDKEngine registerDefaults]; |
| id darkSchemeHandler = [WXSDKInstance darkSchemeColorHandler]; |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_SDKINSTANCE_WILL_RENDER object:self]; |
| if ([WXSDKInstance darkSchemeColorHandler] != darkSchemeHandler) { |
| // After notification, handler for WXDarkSchemeProtocol might change. |
| self.schemeName = [WXUtility isSystemInDarkScheme] ? @"dark" : @"light"; |
| } |
| |
| if ([self _handleConfigCenter]) { |
| int wxErrorCode = 9999; |
| NSError * error = [NSError errorWithDomain:WX_ERROR_DOMAIN code:wxErrorCode userInfo:nil]; |
| if (self.onFailed) { |
| self.onFailed(error); |
| } |
| [self.apmInstance setProperty:KEY_PROPERTIES_ERROR_CODE withValue:[@(wxErrorCode) stringValue]]; |
| return; |
| } |
| |
| [[WXSDKManager bridgeMgr] createInstance:self.instanceId contents:contents options:dictionary data:_jsData]; |
| |
| // WX_MONITOR_PERF_SET(WXPTBundleSize, [data length], self); |
| _isRendered = YES; |
| } |
| |
| - (void)_renderWithMainBundleString:(NSString *)mainBundleString |
| { |
| if (!self.instanceId) { |
| WXLogError(@"Fail to find instance!"); |
| return; |
| } |
| |
| if (_isRendered) { |
| [WXExceptionUtils commitCriticalExceptionRT:self.instanceId errCode:[NSString stringWithFormat:@"%d", WX_ERR_RENDER_TWICE] function:@"_renderWithMainBundleString:" exception:[NSString stringWithFormat:@"instance is rendered twice"] extParams:nil]; |
| return; |
| } |
| |
| //some case , with out render (url) |
| [self _checkPageName]; |
| [self.apmInstance startRecord:self.instanceId]; |
| self.apmInstance.isStartRender = YES; |
| |
| [_apmInstance setProperty:KEY_PAGE_PROPERTIES_UIKIT_TYPE withValue:_renderType?: WEEX_RENDER_TYPE_PLATFORM]; |
| if (self.dataRender) { |
| [self.apmInstance setProperty:KEY_PAGE_PROPERTIES_RENDER_TYPE withValue:@"eagle"]; |
| } |
| |
| self.performance.renderTimeOrigin = CACurrentMediaTime()*1000; |
| self.performance.renderUnixTimeOrigin = [WXUtility getUnixFixTimeMillis]; |
| long renderOriginTimePlatform = [self.apmInstance onStage:KEY_PAGE_STAGES_RENDER_ORGIGIN]; |
| |
| // pass render origin time to page |
| [WXCoreBridge setPageArgument:_instanceId key:@"renderTimeOrigin" value:[NSString stringWithFormat:@"%lld", (long long)([[NSDate date] timeIntervalSince1970] * 1000)]]; |
| [WXCoreBridge setPageArgument:_instanceId key:@"renderTimeOriginPlatform" value:[NSString stringWithFormat:@"%lld", (long long)renderOriginTimePlatform]]; |
| |
| if (![WXUtility isBlankString:self.pageName]) { |
| WXLog(@"Start rendering page:%@", self.pageName); |
| } else { |
| WXLogWarning(@"WXSDKInstance's pageName should be specified."); |
| id<WXJSExceptionProtocol> jsExceptionHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXJSExceptionProtocol)]; |
| if ([jsExceptionHandler respondsToSelector:@selector(onRuntimeCheckException:)]) { |
| WXRuntimeCheckException * runtimeCheckException = [WXRuntimeCheckException new]; |
| runtimeCheckException.exception = @"We highly recommend you to set pageName.\n Using WXSDKInstance * instance = [WXSDKInstance new]; instance.pageName = @\"your page name\" to fix it"; |
| [jsExceptionHandler onRuntimeCheckException:runtimeCheckException]; |
| } |
| } |
| if (!self.userInfo) { |
| self.userInfo = [NSMutableDictionary new]; |
| } |
| if (!self.userInfo[@"jsMainBundleStringContentLength"]) { |
| self.userInfo[@"jsMainBundleStringContentLength"] = @([mainBundleString length]); |
| } |
| |
| id<WXPageEventNotifyEventProtocol> pageEvent = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)]; |
| if ([pageEvent respondsToSelector:@selector(pageStart:)]) { |
| [pageEvent pageStart:self.instanceId]; |
| } |
| |
| WX_MONITOR_INSTANCE_PERF_START(WXPTFirstScreenRender, self); |
| WX_MONITOR_INSTANCE_PERF_START(WXPTAllRender, self); |
| |
| NSMutableDictionary *dictionary = [_options mutableCopy]; |
| if ([WXLog logLevel] >= WXLogLevelLog) { |
| dictionary[@"debug"] = @(YES); |
| } |
| |
| if ([WXDebugTool getReplacedBundleJS]) { |
| mainBundleString = [WXDebugTool getReplacedBundleJS]; |
| } |
| |
| WXPerformBlockOnMainThread(^{ |
| if (self.isCustomRenderType) { |
| self->_rootView = [WXCustomPageBridge createPageRootView:self.instanceId pageType:self.renderType frame:self.frame]; |
| } |
| else { |
| self->_rootView = [[WXRootView alloc] initWithFrame:self.frame]; |
| ((WXRootView*)(self->_rootView)).instance = self; |
| } |
| |
| if (self.onCreate) { |
| self.onCreate(self->_rootView); |
| } |
| }); |
| // ensure default modules/components/handlers are ready before create instance |
| [WXSDKEngine registerDefaults]; |
| id darkSchemeHandler = [WXSDKInstance darkSchemeColorHandler]; |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_SDKINSTANCE_WILL_RENDER object:self]; |
| if ([WXSDKInstance darkSchemeColorHandler] != darkSchemeHandler) { |
| // After notification, handler for WXDarkSchemeProtocol might change. |
| self.schemeName = [WXUtility isSystemInDarkScheme] ? @"dark" : @"light"; |
| } |
| |
| _mainBundleString = mainBundleString; |
| if ([self _handleConfigCenter]) { |
| int wxErrorCode = 9999; |
| NSError * error = [NSError errorWithDomain:WX_ERROR_DOMAIN code:wxErrorCode userInfo:nil]; |
| if (self.onFailed) { |
| self.onFailed(error); |
| } |
| [self.apmInstance setProperty:KEY_PROPERTIES_ERROR_CODE withValue:[@(wxErrorCode) stringValue]]; |
| return; |
| } |
| |
| [[WXSDKManager bridgeMgr] createInstance:self.instanceId template:mainBundleString options:dictionary data:_jsData]; |
| |
| WX_MONITOR_PERF_SET(WXPTBundleSize, [mainBundleString lengthOfBytesUsingEncoding:NSUTF8StringEncoding], self); |
| |
| _isRendered = YES; |
| } |
| |
| - (BOOL)_handleConfigCenter |
| { |
| id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)]; |
| if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) { |
| BOOL enableRTLLayoutDirection = [[configCenter configForKey:@"iOS_weex_ext_config.enableRTLLayoutDirection" defaultValue:@(YES) isDefault:NULL] boolValue]; |
| [WXUtility setEnableRTLLayoutDirection:enableRTLLayoutDirection]; |
| |
| BOOL isIOS13 = [[[UIDevice currentDevice] systemVersion] integerValue] == 13; |
| BOOL useMRCForInvalidJSONObject = [[configCenter configForKey:@"iOS_weex_ext_config.useMRCForInvalidJSONObject" defaultValue:@(YES) isDefault:NULL] boolValue]; |
| BOOL alwaysUseMRCForObjectToWeexCore = [[configCenter configForKey:@"iOS_weex_ext_config.alwaysUseMRC" defaultValue:@(NO) isDefault:NULL] boolValue]; |
| ConvertSwitches(isIOS13, useMRCForInvalidJSONObject, alwaysUseMRCForObjectToWeexCore); |
| |
| BOOL isDarkSchemeSupportEnabled = [[configCenter configForKey:@"iOS_weex_ext_config.supportDarkScheme" defaultValue:@(YES) isDefault:NULL] boolValue]; |
| if (@available(iOS 13.0, *)) { |
| } |
| else { |
| isDarkSchemeSupportEnabled = NO; |
| } |
| [WXUtility setDarkSchemeSupportEnable:isDarkSchemeSupportEnabled]; |
| } |
| else { |
| BOOL isIOS13 = [[[UIDevice currentDevice] systemVersion] integerValue] == 13; |
| ConvertSwitches(isIOS13, YES, NO); |
| } |
| return NO; |
| } |
| |
| - (void)renderWithMainBundleString:(NSNotification*)notification |
| { |
| [self _renderWithMainBundleString:_mainBundleString]; |
| } |
| |
| - (void)_renderWithRequest:(WXResourceRequest *)request options:(NSDictionary *)options data:(id)data; |
| { |
| NSURL *url = request.URL; |
| self.scriptURL = url; |
| _jsData = data; |
| if (![options isKindOfClass:[NSDictionary class]]) { |
| options = @{}; |
| } |
| NSMutableDictionary *newOptions = [options mutableCopy] ?: [NSMutableDictionary new]; |
| |
| if (!newOptions[bundleUrlOptionKey]) { |
| newOptions[bundleUrlOptionKey] = url.absoluteString; |
| } |
| |
| if ( [url.absoluteString containsString:@"__data_render=true"]) { |
| newOptions[@"DATA_RENDER"] = @(YES); |
| } |
| |
| if ([url.absoluteString hasSuffix:WEEX_LITE_URL_SUFFIX] || [url.absoluteString containsString:@"__eagle=true"]) { |
| newOptions[@"WLASM_RENDER"] = @(YES); |
| } |
| |
| // compatible with some wrong type, remove this hopefully in the future. |
| if ([newOptions[bundleUrlOptionKey] isKindOfClass:[NSURL class]]) { |
| WXLogWarning(@"Error type in options with key:bundleUrl, should be of type NSString, not NSURL!"); |
| newOptions[bundleUrlOptionKey] = ((NSURL*)newOptions[bundleUrlOptionKey]).absoluteString; |
| } |
| _options = [newOptions copy]; |
| request.userAgent = [WXUtility userAgent]; |
| |
| WX_MONITOR_INSTANCE_PERF_START(WXPTJSDownload, self); |
| __weak typeof(self) weakSelf = self; |
| _mainBundleLoader = [[WXResourceLoader alloc] initWithRequest:request];; |
| [self.apmInstance onStage:KEY_PAGE_STAGES_DOWN_BUNDLE_START]; |
| _mainBundleLoader.onFinished = ^(WXResourceResponse *response, NSData *data) { |
| __strong typeof(weakSelf) strongSelf = weakSelf; |
| if (strongSelf == nil) { |
| return; |
| } |
| |
| NSMutableDictionary* optionsCopy = [strongSelf->_options mutableCopy]; |
| optionsCopy[bundleResponseUrlOptionKey] = [response.URL absoluteString]; |
| strongSelf->_options = [optionsCopy copy]; |
| |
| NSError *error = nil; |
| if ([response isKindOfClass:[NSHTTPURLResponse class]] && ((NSHTTPURLResponse *)response).statusCode != 200) { |
| error = [NSError errorWithDomain:WX_ERROR_DOMAIN |
| code:((NSHTTPURLResponse *)response).statusCode |
| userInfo:@{@"message":@"status code error."}]; |
| if (strongSelf.onFailed) { |
| strongSelf.onFailed(error); |
| } |
| } |
| |
| if (strongSelf.onJSDownloadedFinish) { |
| strongSelf.onJSDownloadedFinish(response, request, data, error); |
| } |
| |
| if (error) { |
| [WXExceptionUtils commitCriticalExceptionRT:strongSelf.instanceId |
| errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_JS_DOWNLOAD] |
| function:@"_renderWithRequest:options:data:" |
| exception:[NSString stringWithFormat:@"download bundle error :%@",[error localizedDescription]] |
| extParams:nil]; |
| |
| strongSelf.apmInstance.isDownLoadFailed = YES; |
| [strongSelf.apmInstance setProperty:KEY_PROPERTIES_ERROR_CODE withValue:[@(WX_KEY_EXCEPTION_JS_DOWNLOAD) stringValue]]; |
| return; |
| } |
| |
| if (!data) { |
| NSString *errorMessage = [NSString stringWithFormat:@"Request to %@ With no data return", request.URL]; |
| WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_DOWNLOAD, errorMessage, strongSelf.pageName); |
| [WXExceptionUtils commitCriticalExceptionRT:strongSelf.instanceId |
| errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_JS_DOWNLOAD] |
| function:@"_renderWithRequest:options:data:" |
| exception:errorMessage |
| extParams:nil]; |
| |
| if (strongSelf.onFailed) { |
| strongSelf.onFailed(error); |
| } |
| strongSelf.apmInstance.isDownLoadFailed = YES; |
| [strongSelf.apmInstance setProperty:KEY_PROPERTIES_ERROR_CODE withValue:[@(WX_KEY_EXCEPTION_JS_DOWNLOAD) stringValue]]; |
| return; |
| } |
| |
| if (([newOptions[@"DATA_RENDER"] boolValue] && [newOptions[@"RENDER_WITH_BINARY"] boolValue]) || [newOptions[@"WLASM_RENDER"] boolValue]) { |
| [strongSelf.apmInstance onStage:KEY_PAGE_STAGES_DOWN_BUNDLE_END]; |
| [strongSelf _renderWithData:data]; |
| return; |
| } |
| |
| NSString *jsBundleString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; |
| if (!jsBundleString) { |
| WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, WX_ERR_JSBUNDLE_STRING_CONVERT, @"data converting to string failed.", strongSelf.pageName) |
| [strongSelf.apmInstance setProperty:KEY_PROPERTIES_ERROR_CODE withValue:[@(WX_ERR_JSBUNDLE_STRING_CONVERT) stringValue]]; |
| return; |
| } |
| if (!strongSelf.userInfo) { |
| strongSelf.userInfo = [NSMutableDictionary new]; |
| } |
| |
| NSUInteger bundleSize = [jsBundleString length]; |
| [strongSelf.apmInstance updateDiffStats:KEY_PAGE_STATS_BUNDLE_SIZE withDiffValue:bundleSize]; |
| |
| strongSelf.userInfo[@"jsMainBundleStringContentLength"] = @(bundleSize); |
| strongSelf.userInfo[@"jsMainBundleStringContentMd5"] = [WXUtility md5:jsBundleString]; |
| |
| WX_MONITOR_SUCCESS_ON_PAGE(WXMTJSDownload, strongSelf.pageName); |
| WX_MONITOR_INSTANCE_PERF_END(WXPTJSDownload, strongSelf); |
| |
| if (strongSelf.onRenderTerminateWhenJSDownloadedFinish) { |
| if (strongSelf.onRenderTerminateWhenJSDownloadedFinish(response, request, data, error)) { |
| return; |
| } |
| } |
| |
| [strongSelf.apmInstance onStage:KEY_PAGE_STAGES_DOWN_BUNDLE_END]; |
| [strongSelf.apmInstance updateExtInfoFromResponseHeader:response.allHeaderFields]; |
| [strongSelf _renderWithMainBundleString:jsBundleString]; |
| [WXMonitor performanceFinishWithState:DebugAfterRequest instance:strongSelf]; |
| }; |
| |
| _mainBundleLoader.onFailed = ^(NSError *loadError) { |
| WXLogError(@"Request failed with error: %@", loadError); |
| |
| NSString *errorMessage = [NSString stringWithFormat:@"Request to %@ occurs an error:%@, info:%@", request.URL, loadError.localizedDescription, loadError.userInfo]; |
| long wxErrorCode = [loadError.domain isEqualToString:NSURLErrorDomain] && loadError.code == NSURLErrorNotConnectedToInternet ? WX_ERR_NOT_CONNECTED_TO_INTERNET : WX_ERR_JSBUNDLE_DOWNLOAD; |
| |
| WX_MONITOR_FAIL_ON_PAGE(WXMTJSDownload, wxErrorCode, errorMessage, weakSelf.pageName); |
| |
| NSMutableDictionary *allUserInfo = [[NSMutableDictionary alloc] initWithDictionary:error.userInfo]; |
| [allUserInfo addEntriesFromDictionary:loadError.userInfo]; |
| NSError *errorWithReportMsg = [NSError errorWithDomain:error.domain |
| code:error.code |
| userInfo:allUserInfo]; |
| |
| if (weakSelf.onFailed) { |
| weakSelf.onFailed(errorWithReportMsg); |
| } |
| [weakSelf.apmInstance setProperty:KEY_PROPERTIES_ERROR_CODE withValue:[@(wxErrorCode) stringValue]]; |
| }; |
| |
| [_mainBundleLoader start]; |
| } |
| |
| - (void)reload:(BOOL)forcedReload |
| { |
| // TODO: [self unload] |
| if (!_scriptURL) { |
| WXLogError(@"No script URL found while reloading!"); |
| return; |
| } |
| |
| NSURLRequestCachePolicy cachePolicy = forcedReload ? NSURLRequestReloadIgnoringCacheData : NSURLRequestUseProtocolCachePolicy; |
| WXResourceRequest *request = [WXResourceRequest requestWithURL:_scriptURL resourceType:WXResourceTypeMainBundle referrer:_scriptURL.absoluteString cachePolicy:cachePolicy]; |
| [self _renderWithRequest:request options:_options data:_jsData]; |
| } |
| |
| - (void)reloadLayout |
| { |
| NSString* pageId = _instanceId; |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge reloadPageLayout:pageId]; |
| }); |
| } |
| |
| - (void)refreshInstance:(id)data |
| { |
| WXLogDebug(@"refresh instance: %@, data:%@", self, [WXUtility JSONString:data]); |
| |
| if (!self.instanceId) { |
| WXLogError(@"Fail to find instance!"); |
| return; |
| } |
| |
| [[WXSDKManager bridgeMgr] refreshInstance:self.instanceId data:data]; |
| } |
| |
| - (void)destroyInstance |
| { |
| WXLogInfo(@"Destroy instance: %@", _instanceId); |
| |
| [self.apmInstance endRecord]; |
| NSString *url = @""; |
| if ([WXPrerenderManager isTaskExist:[self.scriptURL absoluteString]]) { |
| url = [self.scriptURL absoluteString]; |
| } |
| if (!self.instanceId) { |
| WXLogError(@"Fail to find instance!"); |
| return; |
| } |
| |
| id<WXPageEventNotifyEventProtocol> pageEvent = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)]; |
| if ([pageEvent respondsToSelector:@selector(pageDestroy:)]) { |
| [pageEvent pageDestroy:self.instanceId]; |
| } |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_INSTANCE_WILL_DESTROY_NOTIFICATION object:nil userInfo:@{@"instanceId":self.instanceId}]; |
| |
| [WXPrerenderManager removePrerenderTaskforUrl:[self.scriptURL absoluteString]]; |
| [WXPrerenderManager destroyTask:self.instanceId]; |
| BOOL dataRender = self.dataRender; |
| BOOL wlasmRender = self.wlasmRender; |
| if (!dataRender) { |
| [[WXSDKManager bridgeMgr] destroyInstance:self.instanceId]; |
| } |
| |
| WXComponentManager* componentManager = self.componentManager; |
| NSString* instanceId = self.instanceId; |
| |
| /* Custom render target, currently we manage the pages by ourselves not by WeexCore. |
| We remove the WeexCore page immediately so that any later render commands will be ignored. */ |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] invalidatePage:instanceId]; |
| } |
| |
| WXPerformBlockOnComponentThread(^{ |
| // Destroy components and views in main thread. Unbind with underneath RenderObjects. |
| [componentManager unload]; |
| |
| // Destroy weexcore c++ page and objects. |
| [WXCoreBridge closePage:instanceId]; |
| |
| if (dataRender && !wlasmRender) { |
| [[WXSDKManager bridgeMgr] destroyInstance:instanceId]; |
| } |
| |
| // Destroy heron render target page |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] removePage:instanceId]; |
| } |
| |
| // Reading config from orange for Release instance in Main Thread or not, for Bug #15172691 +{ |
| dispatch_async(dispatch_get_main_queue(), ^{ |
| [WXSDKManager removeInstanceforID:instanceId]; |
| WXLogInfo(@"Finally remove instance: %@", instanceId); |
| }); |
| //+} |
| }); |
| |
| if (url.length > 0) { |
| [WXPrerenderManager addGlobalTask:url callback:nil]; |
| } |
| } |
| |
| - (void)forceGarbageCollection |
| { |
| [[WXSDKManager bridgeMgr] forceGarbageCollection]; |
| } |
| |
| - (void)updateState:(WXState)state |
| { |
| if (!self.instanceId) { |
| WXLogError(@"Fail to find instance!"); |
| return; |
| } |
| |
| if (!_performanceCommit && state == WeexInstanceDisappear) { |
| [self updatePerDicBeforExit]; |
| WX_MONITOR_INSTANCE_PERF_COMMIT(self); |
| _performanceCommit = YES; |
| } |
| |
| NSMutableDictionary *data = [NSMutableDictionary dictionary]; |
| [data setObject:[NSString stringWithFormat:@"%ld",(long)state] forKey:@"state"]; |
| //[[WXSDKManager bridgeMgr] updateState:self.instanceId data:data]; |
| |
| // First post internal notification |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_INSTANCE_NOTIFICATION_UPDATE_STATE_INTERNAL object:self userInfo:data]; |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_INSTANCE_NOTIFICATION_UPDATE_STATE object:self userInfo:data]; |
| } |
| |
| - (id)moduleForClass:(Class)moduleClass |
| { |
| if (!moduleClass) |
| return nil; |
| |
| id<WXModuleProtocol> moduleInstance = self.moduleInstances[NSStringFromClass(moduleClass)]; |
| if (!moduleInstance) { |
| moduleInstance = [[moduleClass alloc] init]; |
| if ([moduleInstance respondsToSelector:@selector(setWeexInstance:)]) |
| [moduleInstance setWeexInstance:self]; |
| self.moduleInstances[NSStringFromClass(moduleClass)] = moduleInstance; |
| } |
| |
| return moduleInstance; |
| } |
| |
| - (WXComponent *)componentForRef:(NSString *)ref |
| { |
| WXAssertComponentThread(); |
| |
| return [_componentManager componentForRef:ref]; |
| } |
| |
| - (NSUInteger)numberOfComponents |
| { |
| WXAssertComponentThread(); |
| |
| return [_componentManager numberOfComponents]; |
| } |
| |
| - (void)enumerateComponentsUsingBlock:(void (^)(WXComponent *, BOOL *stop))block |
| { |
| WXAssertComponentThread(); |
| |
| [_componentManager enumerateComponentsUsingBlock:block]; |
| } |
| |
| - (void)fireGlobalEvent:(NSString *)eventName params:(NSDictionary *)params |
| { |
| if (!params){ |
| params = [NSDictionary dictionary]; |
| } |
| NSDictionary * userInfo = @{ |
| @"weexInstance":self.instanceId, |
| @"param":params |
| }; |
| [[NSNotificationCenter defaultCenter] postNotificationName:eventName object:self userInfo:userInfo]; |
| } |
| |
| - (void)fireModuleEvent:(Class)module eventName:(NSString *)eventName params:(NSDictionary*)params |
| { |
| NSDictionary * userInfo = @{ |
| @"moduleId":NSStringFromClass(module)?:@"", |
| @"param":params?:@{}, |
| @"eventName":eventName |
| }; |
| |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_MODULE_EVENT_FIRE_NOTIFICATION object:self userInfo:userInfo]; |
| } |
| |
| - (CGFloat)pixelScaleFactor |
| { |
| CGFloat usingScreenWidth = _screenSize.width > 0 ? _screenSize.width : [WXCoreBridge getDeviceSize].width; |
| CGFloat usingViewPort = _viewportWidth > 0 ? _viewportWidth : WXDefaultScreenWidth; |
| return usingScreenWidth / usingViewPort; |
| } |
| |
| - (BOOL)wlasmRender { |
| if ([_options[@"WLASM_RENDER"] boolValue] || [_scriptURL.pathExtension isEqualToString:@"wlasm"] || [_scriptURL.pathExtension isEqualToString:@"wlm"]) { |
| return YES; |
| } |
| return NO; |
| } |
| |
| - (BOOL)dataRender |
| { |
| if ([_options[@"DATA_RENDER"] boolValue] || [_options[@"WLASM_RENDER"] boolValue]) { |
| return YES; |
| } |
| return _defaultDataRender; |
| } |
| |
| - (NSURL *)completeURL:(NSString *)url |
| { |
| if (!_scriptURL) { |
| return [NSURL URLWithString:url]; |
| } |
| if ([url hasPrefix:@"//"] && [_scriptURL isFileURL]) { |
| return [NSURL URLWithString:url]; |
| } |
| if (!url) { |
| return nil; |
| } |
| NSURL *result = [NSURL URLWithString:url relativeToURL:_scriptURL]; |
| if (result) { |
| return result; |
| } |
| // if result is nil, try url-encode the 'url' string. |
| return [NSURL URLWithString:[url stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]] relativeToURL:_scriptURL]; |
| } |
| |
| - (BOOL)checkModuleEventRegistered:(NSString*)event moduleClassName:(NSString*)moduleClassName |
| { |
| NSDictionary * observer = [_moduleEventObservers objectForKey:moduleClassName]; |
| return observer && observer[event]? YES:NO; |
| } |
| |
| #pragma mark Private Methods |
| |
| - (void)_addModuleEventObserversWithModuleMethod:(WXModuleMethod *)method |
| { |
| if ([method.arguments count] < 2) { |
| WXLogError(@"please check your method parameter!!"); |
| return; |
| } |
| if(![method.arguments[0] isKindOfClass:[NSString class]]) { |
| // arguments[0] will be event name, so it must be a string type value here. |
| return; |
| } |
| NSMutableArray * methodArguments = [method.arguments mutableCopy]; |
| if ([methodArguments count] == 2) { |
| [methodArguments addObject:@{@"once": @false}]; |
| } |
| if (![methodArguments[2] isKindOfClass:[NSDictionary class]]) { |
| //arguments[2] is the option value, so it must be a dictionary. |
| return; |
| } |
| Class moduleClass = [WXModuleFactory classWithModuleName:method.moduleName]; |
| NSMutableDictionary * option = [methodArguments[2] mutableCopy]; |
| [option setObject:method.moduleName forKey:@"moduleName"]; |
| // the value for moduleName in option is for the need of callback |
| [self addModuleEventObservers:methodArguments[0] callback:methodArguments[1] option:option moduleClassName:NSStringFromClass(moduleClass)]; |
| } |
| |
| - (void)addModuleEventObservers:(NSString*)event callback:(NSString*)callbackId option:(NSDictionary *)option moduleClassName:(NSString*)moduleClassName |
| { |
| BOOL once = [[option objectForKey:@"once"] boolValue]; |
| NSString * moduleName = [option objectForKey:@"moduleName"]; |
| NSMutableDictionary * observer = nil; |
| NSDictionary * callbackInfo = @{@"callbackId":callbackId,@"once":@(once),@"moduleName":moduleName}; |
| if(![self checkModuleEventRegistered:event moduleClassName:moduleClassName]) { |
| //had not registered yet |
| observer = [NSMutableDictionary new]; |
| [observer setObject:[@{event:[@[callbackInfo] mutableCopy]} mutableCopy] forKey:moduleClassName]; |
| if (_moduleEventObservers[moduleClassName]) { //support multi event |
| [_moduleEventObservers[moduleClassName] addEntriesFromDictionary:observer[moduleClassName]]; |
| }else { |
| [_moduleEventObservers addEntriesFromDictionary:observer]; |
| } |
| } else { |
| observer = _moduleEventObservers[moduleClassName]; |
| [[observer objectForKey:event] addObject:callbackInfo]; |
| } |
| } |
| |
| - (void)_removeModuleEventObserverWithModuleMethod:(WXModuleMethod *)method |
| { |
| if (![method.arguments count] && [method.arguments[0] isKindOfClass:[NSString class]]) { |
| return; |
| } |
| Class moduleClass = [WXModuleFactory classWithModuleName:method.moduleName]; |
| [self removeModuleEventObserver:method.arguments[0] moduleClassName:NSStringFromClass(moduleClass)]; |
| } |
| |
| - (void)removeModuleEventObserver:(NSString*)event moduleClassName:(NSString*)moduleClassName |
| { |
| if (![self checkModuleEventRegistered:event moduleClassName:moduleClassName]) { |
| return; |
| } |
| [_moduleEventObservers[moduleClassName] removeObjectForKey:event]; |
| } |
| |
| - (void)moduleEventNotification:(NSNotification *)notification |
| { |
| NSMutableDictionary *moduleEventObserversCpy = (NSMutableDictionary *)CFBridgingRelease(CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)_moduleEventObservers, kCFPropertyListMutableContainers));// deep |
| NSDictionary * userInfo = notification.userInfo; |
| NSMutableArray * listeners = [moduleEventObserversCpy[userInfo[@"moduleId"]] objectForKey:userInfo[@"eventName"]]; |
| if (![listeners isKindOfClass:[NSArray class]]) { |
| return; |
| // something wrong |
| } |
| for (int i = 0;i < [listeners count]; i ++) { |
| NSDictionary * callbackInfo = listeners[i]; |
| NSString *callbackId = callbackInfo[@"callbackId"]; |
| BOOL once = [callbackInfo[@"once"] boolValue]; |
| NSDictionary * retData = @{@"type":userInfo[@"eventName"], |
| @"module":callbackInfo[@"moduleName"], |
| @"data":userInfo[@"param"]}; |
| [[WXSDKManager bridgeMgr] callBack:self.instanceId funcId:callbackId params:retData keepAlive:!once]; |
| // if callback function is not once, then it is keepalive |
| if (once) { |
| NSMutableArray * moduleEventListener = [_moduleEventObservers[userInfo[@"moduleId"]] objectForKey:userInfo[@"eventName"]]; |
| [moduleEventListener removeObject:callbackInfo]; |
| if ([moduleEventListener count] == 0) { |
| [self removeModuleEventObserver:userInfo[@"eventName"] moduleClassName:userInfo[@"moduleId"]]; |
| } |
| // if callback function is once. clear it after fire it. |
| } |
| } |
| } |
| |
| - (void)addObservers |
| { |
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(moduleEventNotification:) name:WX_MODULE_EVENT_FIRE_NOTIFICATION object:nil]; |
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; |
| [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; |
| [self addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew context:NULL]; |
| } |
| |
| - (void)removeObservers |
| { |
| @try { |
| [self removeObserver:self forKeyPath:@"state" context:NULL]; |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| } |
| @catch (NSException *exception) {//!OCLint |
| } |
| } |
| |
| - (void)applicationWillResignActive:(NSNotification*)notification |
| { |
| [self fireGlobalEvent:WX_APPLICATION_WILL_RESIGN_ACTIVE params:nil]; |
| } |
| |
| - (void)applicationDidBecomeActive:(NSNotification*)notification |
| { |
| [self fireGlobalEvent:WX_APPLICATION_DID_BECOME_ACTIVE params:nil]; |
| } |
| |
| - (WXComponentManager *)componentManager |
| { |
| if (!_componentManager) { |
| _componentManager = [[WXComponentManager alloc] initWithWeexInstance:self]; |
| } |
| |
| return _componentManager; |
| } |
| |
| - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context |
| { |
| if ([keyPath isEqualToString:@"state"]) { |
| WXState state = [change[@"new"] longValue]; |
| [self updateState:state]; |
| |
| if (state == WeexInstanceDestroy) { |
| [self destroyInstance]; |
| } |
| } |
| } |
| |
| - (void)willAppear |
| { |
| if (self.isCustomRenderType) { |
| if (!self.appearState) { |
| // do create window, |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_INSTANCE_NOTIFICATION_CHANGE_VISIBILITY_INTERNAL object:self userInfo:@{@"visible": @(YES)}]; |
| self.appearState = YES; |
| } |
| } |
| } |
| |
| - (void)didDisappear |
| { |
| if (self.isCustomRenderType) { |
| if (self.appearState) { |
| // do destroy window |
| [[NSNotificationCenter defaultCenter] postNotificationName:WX_INSTANCE_NOTIFICATION_CHANGE_VISIBILITY_INTERNAL object:self userInfo:@{@"visible": @(NO)}]; |
| self.appearState = NO; |
| } |
| } |
| } |
| |
| + (NSDictionary*)lastPageInfo |
| { |
| NSDictionary* result; |
| @synchronized (lastPageInfoLock) { |
| result = [lastPageInfo copy]; |
| } |
| return result; |
| } |
| |
| - (void)setAutoInvertingBehavior:(WXAutoInvertingBehavior)behavior |
| { |
| _autoInvertingBehavior = behavior; |
| } |
| |
| + (id<WXDarkSchemeProtocol>)darkSchemeColorHandler |
| { |
| return [WXHandlerFactory handlerForProtocol:@protocol(WXDarkSchemeProtocol)]; |
| } |
| |
| - (NSString*)currentSchemeName |
| { |
| return self.schemeName; |
| } |
| |
| - (BOOL)isDarkScheme |
| { |
| return [self.schemeName isEqualToString:@"dark"]; |
| } |
| |
| - (void)setCurrentSchemeName:(NSString*)name |
| { |
| if (![WXUtility isDarkSchemeSupportEnabled]) { |
| self.schemeName = @"light"; |
| return; |
| } |
| |
| if (name && ![name isEqualToString:self.schemeName]) { |
| self.schemeName = name; |
| |
| if (self.isCustomRenderType) { |
| return; |
| } |
| |
| WXDarkSchemeModule* darkSchemeModule = [self moduleForClass:[WXDarkSchemeModule class]]; |
| [darkSchemeModule onInstanceSchemeChanged]; |
| |
| // Recursively visit all components and notify that scheme had changed. |
| __weak WXSDKInstance* weakSelf = self; |
| WXPerformBlockOnComponentThread(^{ |
| __strong WXSDKInstance* strongSelf = weakSelf; |
| if (strongSelf == nil) { |
| return; |
| } |
| |
| if (!strongSelf->_componentManager.isValid) { |
| return; |
| } |
| |
| [strongSelf->_componentManager enumerateComponentsUsingBlock:^(WXComponent * _Nonnull component, BOOL * _Nonnull stop) { |
| __weak WXComponent* wcomp = component; |
| WXPerformBlockOnMainThread(^{ |
| __strong WXComponent* scomp = wcomp; |
| if (scomp) { |
| [scomp schemeDidChange:name]; |
| } |
| }); |
| }]; |
| }); |
| |
| [[WXSDKManager bridgeMgr] fireEvent:_instanceId |
| ref:WX_SDK_ROOT_REF |
| type:@"schemechange" |
| params:@{@"scheme": self.schemeName?:@"light"} |
| domChanges:nil]; |
| } |
| } |
| |
| - (UIColor*)chooseColor:(UIColor*)originalColor |
| lightSchemeColor:(UIColor*)lightColor |
| darkSchemeColor:(UIColor*)darkColor |
| invert:(BOOL)invert |
| scene:(WXColorScene)scene |
| { |
| if (![WXUtility isDarkSchemeSupportEnabled]) { |
| return originalColor; |
| } |
| |
| if ([self isDarkScheme]) { |
| if (darkColor) { |
| return darkColor; |
| } |
| else if (invert) { |
| // Invert originalColor |
| if (originalColor == [UIColor clearColor]) { |
| return originalColor; |
| } |
| return [[WXSDKInstance darkSchemeColorHandler] getInvertedColorFor:originalColor ofScene:scene withDefault:originalColor]; |
| } |
| else { |
| return originalColor; |
| } |
| } |
| else if (lightColor) { |
| return lightColor; |
| } |
| else { |
| return originalColor; |
| } |
| } |
| |
| @end |
| |
| @implementation WXSDKInstance (Deprecated) |
| |
| # pragma mark - Deprecated |
| |
| - (void)reloadData:(id)data |
| { |
| [self refreshInstance:data]; |
| } |
| |
| - (void)finishPerformance |
| { |
| //deprecated |
| } |
| |
| - (void)creatFinish |
| { |
| |
| } |
| |
| @end |