Merge pull request #3350 from jianhan-he/master

For release version
diff --git a/WeexSDK.podspec b/WeexSDK.podspec
index 8689ebb..869e849 100644
--- a/WeexSDK.podspec
+++ b/WeexSDK.podspec
@@ -69,6 +69,7 @@
                           'ios/sdk/WeexSDK/Sources/Controller/WXBaseViewController.h',
                           'ios/sdk/WeexSDK/Sources/Controller/WXRootViewController.h',
                           'ios/sdk/WeexSDK/Sources/Handler/WXNavigationDefaultImpl.h',
+                          'ios/sdk/WeexSDK/Sources/Handler/WXUnicornEventListenerHandler.h',
                           'ios/sdk/WeexSDK/Sources/View/WXView.h',
                           'ios/sdk/WeexSDK/Sources/View/WXErrorView.h',
                           'ios/sdk/WeexSDK/Sources/Protocol/*.h',
diff --git a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
index d14d0a7..d6b88ee 100644
--- a/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
+++ b/ios/sdk/WeexSDK.xcodeproj/project.pbxproj
@@ -583,6 +583,11 @@
 		BDB1129F2459D8FC008492F9 /* reactor_page.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BDB112992459D71E008492F9 /* reactor_page.cpp */; };
 		BDB112A02459D90F008492F9 /* reactor_page.h in Headers */ = {isa = PBXBuildFile; fileRef = BDB1129A2459D71E008492F9 /* reactor_page.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		BDBA319B248D0A5200C6EDD0 /* reactor_page.cpp in Headers */ = {isa = PBXBuildFile; fileRef = BDB112992459D71E008492F9 /* reactor_page.cpp */; };
+		BDC402D925889EF900EA838A /* WXUnicornRenderProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = BDD94B6425761DAA002AD864 /* WXUnicornRenderProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		BDC402DE25889F0900EA838A /* WXUnicornEventListenerHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = BDD94B5125761CC7002AD864 /* WXUnicornEventListenerHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		BDD94B5225761CC7002AD864 /* WXUnicornEventListenerHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = BDD94B5025761CC7002AD864 /* WXUnicornEventListenerHandler.m */; };
+		BDD94B5325761CC7002AD864 /* WXUnicornEventListenerHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = BDD94B5125761CC7002AD864 /* WXUnicornEventListenerHandler.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		BDD94B6525761DAA002AD864 /* WXUnicornRenderProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = BDD94B6425761DAA002AD864 /* WXUnicornRenderProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
 		BDEEADBA22F2902E0099F1D7 /* time_calculator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BDEEADB822F2902D0099F1D7 /* time_calculator.cpp */; };
 		BDEEADBB22F2902E0099F1D7 /* time_calculator.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BDEEADB822F2902D0099F1D7 /* time_calculator.cpp */; };
 		BDEEADBC22F2902E0099F1D7 /* time_calculator.h in Headers */ = {isa = PBXBuildFile; fileRef = BDEEADB922F2902E0099F1D7 /* time_calculator.h */; };
@@ -913,6 +918,8 @@
 		ED053503207F4DEB007B4568 /* JSContext+Weex.m in Sources */ = {isa = PBXBuildFile; fileRef = ED0534FF207F4DEB007B4568 /* JSContext+Weex.m */; };
 		F75C58EB2313C03C002FFF94 /* WXTimerModule.h in Headers */ = {isa = PBXBuildFile; fileRef = D3FC0DF51C508B2A002B9E31 /* WXTimerModule.h */; };
 		F75C591C2313C1FC002FFF94 /* WXStreamModule.h in Headers */ = {isa = PBXBuildFile; fileRef = 74A4BAA41CB4F98300195969 /* WXStreamModule.h */; settings = {ATTRIBUTES = (Public, ); }; };
+		F769EECA2578E3000027B29B /* WXLegacyAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = F769EEC92578E3000027B29B /* WXLegacyAdapter.h */; };
+		F769EECB2578E3000027B29B /* WXLegacyAdapter.h in Headers */ = {isa = PBXBuildFile; fileRef = F769EEC92578E3000027B29B /* WXLegacyAdapter.h */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -1342,6 +1349,9 @@
 		BDB112972459D6C2008492F9 /* WXReactorProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXReactorProtocol.h; sourceTree = "<group>"; };
 		BDB112992459D71E008492F9 /* reactor_page.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = reactor_page.cpp; sourceTree = "<group>"; };
 		BDB1129A2459D71E008492F9 /* reactor_page.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = reactor_page.h; sourceTree = "<group>"; };
+		BDD94B5025761CC7002AD864 /* WXUnicornEventListenerHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXUnicornEventListenerHandler.m; sourceTree = "<group>"; };
+		BDD94B5125761CC7002AD864 /* WXUnicornEventListenerHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXUnicornEventListenerHandler.h; sourceTree = "<group>"; };
+		BDD94B6425761DAA002AD864 /* WXUnicornRenderProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WXUnicornRenderProtocol.h; sourceTree = "<group>"; };
 		BDEEADB822F2902D0099F1D7 /* time_calculator.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = time_calculator.cpp; sourceTree = "<group>"; };
 		BDEEADB922F2902E0099F1D7 /* time_calculator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = time_calculator.h; sourceTree = "<group>"; };
 		C401945D1E344E8300D19C31 /* WXFloatCompareTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXFloatCompareTests.m; sourceTree = "<group>"; };
@@ -1420,6 +1430,7 @@
 		DCF343661E49CAEE00A2FB34 /* WXJSExceptionInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = WXJSExceptionInfo.m; sourceTree = "<group>"; };
 		ED0534FE207F4DEB007B4568 /* JSContext+Weex.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "JSContext+Weex.h"; sourceTree = "<group>"; };
 		ED0534FF207F4DEB007B4568 /* JSContext+Weex.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "JSContext+Weex.m"; sourceTree = "<group>"; };
+		F769EEC92578E3000027B29B /* WXLegacyAdapter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = WXLegacyAdapter.h; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -1515,6 +1526,8 @@
 		59A583031CF5B2FD0081FD3E /* Handler */ = {
 			isa = PBXGroup;
 			children = (
+				BDD94B5125761CC7002AD864 /* WXUnicornEventListenerHandler.h */,
+				BDD94B5025761CC7002AD864 /* WXUnicornEventListenerHandler.m */,
 				D7D6B6E5238E1B2A00BE56DD /* WXDarkSchemeDefaultImpl.h */,
 				D7D6B6E4238E1B2A00BE56DD /* WXDarkSchemeDefaultImpl.m */,
 				33CE190C2153443000CF9670 /* WXJSFrameworkLoadDefaultImpl.h */,
@@ -1898,6 +1911,7 @@
 		77D1611C1C02DD3C0010B15B /* Protocol */ = {
 			isa = PBXGroup;
 			children = (
+				BDD94B6425761DAA002AD864 /* WXUnicornRenderProtocol.h */,
 				BDB112972459D6C2008492F9 /* WXReactorProtocol.h */,
 				D7D6B6E1238E1B1D00BE56DD /* WXDarkSchemeProtocol.h */,
 				33CE19122153444900CF9670 /* WXJSFrameworkLoadProtocol.h */,
@@ -1935,6 +1949,7 @@
 		77D161481C02E3670010B15B /* Utility */ = {
 			isa = PBXGroup;
 			children = (
+				F769EEC92578E3000027B29B /* WXLegacyAdapter.h */,
 				453F374B219A76A500A03F1D /* WXConvertUtility.h */,
 				453F374C219A76A500A03F1D /* WXConvertUtility.mm */,
 				C4D872231E5DDF7500E39BC1 /* WXBoxShadow.h */,
@@ -2462,6 +2477,8 @@
 			isa = PBXHeadersBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				BDD94B5325761CC7002AD864 /* WXUnicornEventListenerHandler.h in Headers */,
+				BDD94B6525761DAA002AD864 /* WXUnicornRenderProtocol.h in Headers */,
 				BDB112982459D6C2008492F9 /* WXReactorProtocol.h in Headers */,
 				BDB1129C2459D71E008492F9 /* reactor_page.h in Headers */,
 				77E659F11C0C3612008B8775 /* WXModuleFactory.h in Headers */,
@@ -2618,6 +2635,7 @@
 				B8394F3721468AF100CA1EFF /* render_action_trigger_vsync.h in Headers */,
 				B8D66C8921255730003960BD /* render_object_interface.h in Headers */,
 				74BA4AB31F70F4B600AC29BF /* WXRecycleListLayout.h in Headers */,
+				F769EECA2578E3000027B29B /* WXLegacyAdapter.h in Headers */,
 				B8D66C3521255730003960BD /* render_action_move_element.h in Headers */,
 				742AD7311DF98C45007DC46C /* WXResourceRequestHandlerDefaultImpl.h in Headers */,
 				C4F0127D1E1502A6003378D0 /* WXWebSocketHandler.h in Headers */,
@@ -2718,6 +2736,8 @@
 			files = (
 				BDB1129D2459D802008492F9 /* WXReactorProtocol.h in Headers */,
 				BDB112A02459D90F008492F9 /* reactor_page.h in Headers */,
+				BDC402DE25889F0900EA838A /* WXUnicornEventListenerHandler.h in Headers */,
+				BDC402D925889EF900EA838A /* WXUnicornRenderProtocol.h in Headers */,
 				DCA4461D1EFA5AAA00D0CFA8 /* WXHandlerFactory.h in Headers */,
 				DCA446101EFA5A8500D0CFA8 /* WXBridgeMethod.h in Headers */,
 				DCA4461A1EFA5AA000D0CFA8 /* WXInvocationConfig.h in Headers */,
@@ -2872,6 +2892,7 @@
 				B8F3323D2141A4C600701BA0 /* string_util.h in Headers */,
 				77788B782229252D000D5102 /* render_page_base.h in Headers */,
 				DCA446091EFA5A6D00D0CFA8 /* WXThreadSafeCounter.h in Headers */,
+				F769EECB2578E3000027B29B /* WXLegacyAdapter.h in Headers */,
 				B8D66C3421255730003960BD /* render_action_update_attr.h in Headers */,
 				DCA445F11EFA5A2000D0CFA8 /* WXCanvasComponent.h in Headers */,
 				B8D66CB021255730003960BD /* wson_parser.h in Headers */,
@@ -3009,7 +3030,6 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = 77D161111C02DBE70010B15B /* Build configuration list for PBXNativeTarget "WeexSDK" */;
 			buildPhases = (
-				59D3CA601D003832008835DC /* Generate WeexSDK.h */,
 				77D160F81C02DBE70010B15B /* Sources */,
 				77D160F91C02DBE70010B15B /* Frameworks */,
 				77D160FA1C02DBE70010B15B /* Headers */,
@@ -3028,7 +3048,6 @@
 			isa = PBXNativeTarget;
 			buildConfigurationList = DCA4452C1EFA555400D0CFA8 /* Build configuration list for PBXNativeTarget "WeexSDK-Dynamic" */;
 			buildPhases = (
-				DCA445C81EFA584000D0CFA8 /* Generate WeexSDK.h */,
 				DCA445201EFA555400D0CFA8 /* Sources */,
 				DCA445211EFA555400D0CFA8 /* Frameworks */,
 				DCA445221EFA555400D0CFA8 /* Headers */,
@@ -3143,35 +3162,6 @@
 			shellPath = /bin/sh;
 			shellScript = "# Sets the target folders and the final framework product.\n# 如果工程名称和Framework的Target名称不一样的话,要自定义FMKNAME\nFMK_NAME=${PROJECT_NAME}\n# Install dir will be the final output to the framework.\n# The following line create it in the root folder of the current project.\nINSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework\n# Working dir will be deleted after the framework creation.\nWRK_DIR=build\nDEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework\nSIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework\n# -configuration ${CONFIGURATION}\n# Clean and Building both architectures.\necho xcodebuild -configuration \"Release\" -target \"${FMK_NAME}\" -sdk iphoneos \"CODE_SIGN_IDENTITY=${CODE_SIGN_IDENTITY}\" clean build\nxcodebuild -configuration \"Release\" -target \"${FMK_NAME}\" -sdk iphoneos \"CODE_SIGN_IDENTITY=${CODE_SIGN_IDENTITY}\" clean build\nif [ \"$?\" != \"0\" ]; then\nexit 1\nfi\necho xcodebuild -configuration \"Release\" -target \"${FMK_NAME}\" -sdk iphonesimulator \"CODE_SIGN_IDENTITY=${CODE_SIGN_IDENTITY}\" build\nxcodebuild -configuration \"Release\" -target \"${FMK_NAME}\" -sdk iphonesimulator \"CODE_SIGN_IDENTITY=${CODE_SIGN_IDENTITY}\" build\nif [ \"$?\" != \"0\" ]; then\nexit 1\nfi\n# Cleaning the oldest.\nif [ -d \"${INSTALL_DIR}\" ]\nthen\nrm -rf \"${INSTALL_DIR}\"\nfi\nmkdir -p \"${INSTALL_DIR}\"\ncp -R \"${SIMULATOR_DIR}/\" \"${INSTALL_DIR}/\"\n# 移除签名资源和 Info.plist\nrm \"${INSTALL_DIR}/Info.plist\"\nrm -rf \"${INSTALL_DIR}/_CodeSignature\"\n# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.\nlipo -create \"${DEVICE_DIR}/${FMK_NAME}\" \"${SIMULATOR_DIR}/${FMK_NAME}\" -output \"${INSTALL_DIR}/${FMK_NAME}\"\nrm -r \"${WRK_DIR}\"\n";
 		};
-		59D3CA601D003832008835DC /* Generate WeexSDK.h */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "Generate WeexSDK.h";
-			outputPaths = (
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = ". \"${PROJECT_DIR}/buildScripts.sh\"\n\ngenerateSDKHeader 'WeexSDK'\ngenerateBuildTime \"${PROJECT_DIR}/WeexSDK/Sources/Utility/WXVersion.m\"\n";
-		};
-		DCA445C81EFA584000D0CFA8 /* Generate WeexSDK.h */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-			);
-			name = "Generate WeexSDK.h";
-			outputPaths = (
-				"$(PROJECT_DIR)/myfile",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = ". \"${PROJECT_DIR}/buildScripts.sh\"\n\ngenerateSDKHeader 'WeexSDK'\ngenerateBuildTime \"${PROJECT_DIR}/WeexSDK/Sources/Utility/WXVersion.m\"\n";
-		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */
@@ -3311,6 +3301,7 @@
 				DCAB35FF1D658EB700C0EA70 /* WXRuleManager.m in Sources */,
 				77D161251C02DDD10010B15B /* WXSDKInstance.m in Sources */,
 				DC7764931F3C2CA300B5727E /* WXRecyclerDragController.m in Sources */,
+				BDD94B5225761CC7002AD864 /* WXUnicornEventListenerHandler.m in Sources */,
 				744D61151E4AF23E00B624B3 /* WXDiffUtil.m in Sources */,
 				B8D66C4321255730003960BD /* render_action_remove_element.cpp in Sources */,
 				B8D66BFB2125572F003960BD /* render_performance.cpp in Sources */,
@@ -3886,6 +3877,7 @@
 				DYLIB_COMPATIBILITY_VERSION = 1;
 				DYLIB_CURRENT_VERSION = 1;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
+				GCC_OPTIMIZATION_LEVEL = z;
 				GCC_PRECOMPILE_PREFIX_HEADER = YES;
 				GCC_PREFIX_HEADER = "WeexSDK/Sources/Supporting Files/WeexSDK-Prefix.pch";
 				GCC_PREPROCESSOR_DEFINITIONS = (
@@ -3993,6 +3985,7 @@
 				DYLIB_CURRENT_VERSION = 1;
 				DYLIB_INSTALL_NAME_BASE = "@rpath";
 				ENABLE_BITCODE = NO;
+				GCC_OPTIMIZATION_LEVEL = z;
 				GCC_PRECOMPILE_PREFIX_HEADER = YES;
 				GCC_PREFIX_HEADER = "WeexSDK/Sources/Supporting Files/WeexSDK-Prefix.pch";
 				GCC_PREPROCESSOR_DEFINITIONS = (
diff --git a/ios/sdk/WeexSDK/Sources/Bridge/WXBridgeContext.m b/ios/sdk/WeexSDK/Sources/Bridge/WXBridgeContext.m
index 1df0948..2ba1fad 100644
--- a/ios/sdk/WeexSDK/Sources/Bridge/WXBridgeContext.m
+++ b/ios/sdk/WeexSDK/Sources/Bridge/WXBridgeContext.m
@@ -545,6 +545,7 @@
                                                                 };
             __weak typeof(self) weakSelf = self;
             [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_LOAD_BUNDLE_END];
+            [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_CREATE_INSTANCE_START];
             [self callJSMethod:@"createInstanceContext" args:@[instanceIdString, immutableOptions, data?:@[]] onContext:nil completion:^(JSValue *instanceContextEnvironment) {
                 if (sdkInstance.pageName) {
                     [sdkInstance.instanceJavaScriptContext.javaScriptContext setName:sdkInstance.pageName];
@@ -596,6 +597,7 @@
                 }
                 sdkInstance.instanceJavaScriptContext.javaScriptContext[@"wxExtFuncInfo"] = nil;
                 [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_EXECUTE_BUNDLE_END];
+                [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_CREATE_INSTANCE_END];
                 WX_MONITOR_INSTANCE_PERF_END(WXPTJSCreateInstance, [WXSDKManager instanceForID:instanceIdString]);
             }];
         }
@@ -603,6 +605,7 @@
     } else {
         [sdkInstance.apmInstance setProperty:KEY_PAGE_PROPERTIES_BUNDLE_TYPE withValue:@"other"];
         [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_LOAD_BUNDLE_END];
+        [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_CREATE_INSTANCE_START];
         if (data){
             args = @[instanceIdString, jsBundleString, options ?: @{}, data];
         } else {
@@ -617,6 +620,7 @@
         [self callJSMethod:@"createInstance" args:args];
         sdkInstance.instanceJavaScriptContext.javaScriptContext[@"wxExtFuncInfo"] = nil;
         [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_EXECUTE_BUNDLE_END];
+        [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_CREATE_INSTANCE_END];
         WX_MONITOR_INSTANCE_PERF_END(WXPTJSCreateInstance, [WXSDKManager instanceForID:instanceIdString]);
     }
 }
diff --git a/ios/sdk/WeexSDK/Sources/Bridge/WXCoreBridge.h b/ios/sdk/WeexSDK/Sources/Bridge/WXCoreBridge.h
index dd81f7f..4c120eb 100644
--- a/ios/sdk/WeexSDK/Sources/Bridge/WXCoreBridge.h
+++ b/ios/sdk/WeexSDK/Sources/Bridge/WXCoreBridge.h
@@ -20,6 +20,8 @@
 #ifndef WXCORE_BRIDGE_PLATFORM_H
 #define WXCORE_BRIDGE_PLATFORM_H
 
+#import <JavaScriptCore/JavaScriptCore.h>
+
 #if defined __cplusplus
 
 #include "core/bridge/platform_bridge.h"
@@ -278,6 +280,13 @@
 
 + (BOOL)isKeepingRawCssStyles:(NSString*)pageId;
 
++ (void)callUnicornRenderAction:(NSString*)instanceId
+                         module:(const char*)module
+                         method:(const char*)method
+                        context:(JSContext*)context
+                           args:(JSValueRef[])args
+                       argCount:(int)argCount;
+
 @end
 
 #endif
diff --git a/ios/sdk/WeexSDK/Sources/Bridge/WXCoreBridge.mm b/ios/sdk/WeexSDK/Sources/Bridge/WXCoreBridge.mm
index 07db550..8ecba0f 100644
--- a/ios/sdk/WeexSDK/Sources/Bridge/WXCoreBridge.mm
+++ b/ios/sdk/WeexSDK/Sources/Bridge/WXCoreBridge.mm
@@ -300,12 +300,12 @@
                     if ([object isKindOfClass:[NSString class]]) {
                         returnValue->type = ParamsType::BYTEARRAYSTRING;
                         const char *pcstr_utf8 = [(NSString *)object UTF8String];
-                        returnValue->value.byteArray = generator_bytes_array(pcstr_utf8, ((NSString *)object).length);
+                        returnValue->value.byteArray = generator_bytes_array(pcstr_utf8, [(NSString *)object lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
                     }
                     if ([object isKindOfClass:[NSDictionary class]] || [object isKindOfClass:[NSArray class]]) {
                         NSString *jsonString = [WXUtility JSONString:object];
                         returnValue->type = ParamsType::BYTEARRAYJSONSTRING;
-                        returnValue->value.byteArray = generator_bytes_array(jsonString.UTF8String, jsonString.length);
+                        returnValue->value.byteArray = generator_bytes_array(jsonString.UTF8String, [jsonString lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
                     }
                     break;
                 }
@@ -1529,6 +1529,7 @@
 
 static WeexCore::PlatformBridge* platformBridge = nullptr;
 static WeexCore::ScriptBridge* jsBridge = nullptr;
+static UnicornRenderFunc unicornRenderFunction = nullptr;
 
 + (void)install
 {
@@ -1559,6 +1560,9 @@
 #else
         weex::base::LogImplement::getLog()->setDebugMode(false);
 #endif
+
+        Class UnicornRenderClass = NSClassFromString(@"UnicornRender");
+        unicornRenderFunction = [(id<WXUnicornRenderProtocol>)UnicornRenderClass getRenderFunc];
         
         platformBridge = new WeexCore::PlatformBridge();
         platformBridge->set_platform_side(new WeexCore::IOSSide());
@@ -1930,4 +1934,26 @@
     return static_cast<RenderPage*>(page)->reserve_css_styles();
 }
 
++ (void)callUnicornRenderAction:(NSString*)instanceId
+                         module:(const char*)module
+                         method:(const char*)method
+                        context:(JSContext*)context
+                           args:(JSValueRef[])args
+                       argCount:(int)argCount {
+  std::unique_ptr<JSValueRef[]> values = std::unique_ptr<JSValueRef[]>(new JSValueRef[argCount]);
+  for (int i = 0; i < argCount; i ++) {
+    values[i] = args[i];
+  }
+  if (!unicornRenderFunction) {
+      Class UnicornRenderClass = NSClassFromString(@"UnicornRender");
+      unicornRenderFunction = [(id<WXUnicornRenderProtocol>)UnicornRenderClass getRenderFunc];
+  }
+  unicornRenderFunction([instanceId UTF8String],
+                        module,
+                        method,
+                        context.JSGlobalContextRef,
+                        values.get(),
+                        argCount);
+}
+
 @end
diff --git a/ios/sdk/WeexSDK/Sources/Bridge/WXJSCoreBridge.mm b/ios/sdk/WeexSDK/Sources/Bridge/WXJSCoreBridge.mm
index 413b6d9..15593fe 100644
--- a/ios/sdk/WeexSDK/Sources/Bridge/WXJSCoreBridge.mm
+++ b/ios/sdk/WeexSDK/Sources/Bridge/WXJSCoreBridge.mm
@@ -24,6 +24,7 @@
 #import "WXDefine.h"
 #import "WXUtility.h"
 #import "WXSDKEngine.h"
+#import "WXSDKInstance_private.h"
 #import "WXSDKError.h"
 #import <sys/utsname.h>
 #import <JavaScriptCore/JavaScriptCore.h>
@@ -38,6 +39,7 @@
 #import "JSContext+Weex.h"
 #import "WXCoreBridge.h"
 #import "WXAnalyzerCenter.h"
+#import "WXSDKInstance_performance.h"
 
 
 #import <dlfcn.h>
@@ -148,7 +150,7 @@
         [[WXBridgeManager sharedManager].lastMethodInfo setObject:method ?: @"" forKey:@"method"];
         [[WXBridgeManager sharedManager].lastMethodInfo setObject:args ?: @[] forKey:@"args"];
     });
-    return [[_jsContext globalObject] invokeMethod:method withArguments:args];
+    return [[_jsContext globalObject] invokeMethod:method withArguments:[args copy]];
 }
 
 - (void)registerCallNative:(WXJSCallNative)callNative
@@ -199,8 +201,19 @@
 - (void)registerCallAddElement:(WXJSCallAddElement)callAddElement
 {
     id callAddElementBlock = ^(JSValue *instanceId, JSValue *ref, JSValue *element, JSValue *index, JSValue *ifCallback) {
-        
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef, ref.JSValueRef, element.JSValueRef, index.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"addElement"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:4];
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
+
         NSDictionary *componentData = [element toDictionary];
         NSString *parentRef = [ref toString];
         NSInteger insertIndex = [[index toNumber] integerValue];
@@ -216,8 +229,19 @@
 - (void)registerCallCreateBody:(WXJSCallCreateBody)callCreateBody
 {
     id WXJSCallCreateBodyBlock = ^(JSValue *instanceId, JSValue *body,JSValue *ifCallback) {
-        
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef, body.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"createBody"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:2];
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
+
         NSDictionary *bodyData = [body toDictionary];
         
         WXLogDebug(@"callCreateBody...%@, %@,", instanceIdString, bodyData);
@@ -232,6 +256,17 @@
     id WXJSCallCreateBodyBlock = ^(JSValue *instanceId, JSValue *ref,JSValue *ifCallback) {
         
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef, ref.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"removeElement"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:2];
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
         NSString *refString = [ref toString];
         
         WXLogDebug(@"callRemoveElement...%@, %@,", instanceIdString, refString);
@@ -246,6 +281,18 @@
     id WXJSCallMoveElementBlock = ^(JSValue *instanceId, JSValue *ref,JSValue *parentRef,JSValue *index, JSValue *ifCallback) {
         
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef, ref.JSValueRef, parentRef.JSValueRef, index.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"moveElement"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:4];
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
+
         NSString *refString = [ref toString];
         NSString *parentRefString = [parentRef toString];
         NSInteger moveIndex = [[index toNumber] integerValue];
@@ -262,6 +309,17 @@
     id WXJSCallUpdateAttrsBlock = ^(JSValue *instanceId, JSValue *ref,JSValue *attrs, JSValue *ifCallback) {
         
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef, ref.JSValueRef, attrs.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"updateAttributes"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:3];
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
         NSString *refString = [ref toString];
         NSDictionary *attrsData = [attrs toDictionary];
         
@@ -277,6 +335,17 @@
     id WXJSCallUpdateStyleBlock = ^(JSValue *instanceId, JSValue *ref,JSValue *styles, JSValue *ifCallback) {
         
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef, ref.JSValueRef, styles.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"updateStyle"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:3];
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
         NSString *refString = [ref toString];
         NSDictionary *stylessData = [styles toDictionary];
         
@@ -292,6 +361,17 @@
     id WXJSCallAddEventBlock = ^(JSValue *instanceId, JSValue *ref,JSValue *event, JSValue *ifCallback) {
         
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef, ref.JSValueRef, event.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"addEvent"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:3];
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
         NSString *refString = [ref toString];
         NSString *eventString = [event toString];
         
@@ -307,6 +387,17 @@
     id WXJSCallRemoveEventBlock = ^(JSValue *instanceId, JSValue *ref,JSValue *event, JSValue *ifCallback) {
         
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef, ref.JSValueRef, event.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"removeEvent"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:3];
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
         NSString *refString = [ref toString];
         NSString *eventString = [event toString];
         
@@ -321,6 +412,31 @@
 {
     id WXJSCallCreateFinishBlock = ^(JSValue *instanceId, JSValue *ifCallback) {
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef args[] = {instanceId.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"createFinish"
+                                          context:[JSContext currentContext]
+                                             args:args
+                                         argCount:1];
+
+            WXPerformBlockOnMainThread(^{
+                WX_MONITOR_INSTANCE_PERF_END(WXPTFirstScreenRender, instance);
+                WX_MONITOR_INSTANCE_PERF_END(WXPTAllRender, instance);
+                WX_MONITOR_SUCCESS(WXMTJSBridge);
+                WX_MONITOR_SUCCESS(WXMTNativeRender);
+                [instance updatePerDicAfterCreateFinish];
+
+                UIView *rootView = instance.rootView;
+                [instance.performance onInstanceRenderSuccess:instance];
+                if (instance.renderFinish) {
+                    instance.renderFinish(rootView);
+                }
+            });
+            return [JSValue valueWithInt32:0 inContext:[JSContext currentContext]];
+        }
         WXLogDebug(@"callCreateFinish...%@", instanceIdString);
         return [JSValue valueWithInt32:(int32_t)callCreateFinish(instanceIdString) inContext:[JSContext currentContext]];
     };
@@ -378,6 +494,18 @@
 {
     _jsContext[@"callNativeComponent"] = ^void(JSValue *instanceId, JSValue *componentName, JSValue *methodName, JSValue *args, JSValue *options) {
         NSString *instanceIdString = [instanceId toString];
+        WXSDKInstance *instance = [WXSDKManager instanceForID:instanceIdString];
+        if (instance.unicornRender) {
+            JSValueRef arguments[] = {instanceId.JSValueRef, componentName.JSValueRef, methodName.JSValueRef, args.JSValueRef};
+            [WXCoreBridge callUnicornRenderAction:instanceIdString
+                                           module:"dom"
+                                           method:"callNativeComponent"
+                                          context:[JSContext currentContext]
+                                             args:arguments
+                                         argCount:4];
+            return;
+        }
+
         NSString *componentNameString = [componentName toString];
         NSString *methodNameString = [methodName toString];
         NSArray *argsArray = [args toArray];
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.h b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.h
index 923af9d..3004fee 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.h
+++ b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.h
@@ -46,4 +46,6 @@
 @property (nonatomic, weak) id<WXCellRenderDelegate> delegate;
 @property (nonatomic, strong) NSString *zIndex;
 
+@property (nonatomic, assign) BOOL ignoreScrollSnap;
+
 @end
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.mm
index 1a3b294..4b8098c 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXCellComponent.mm
@@ -61,6 +61,10 @@
             //in Android, cellReuse is not equal cellDataRecycle
             [weexInstance.apmInstance updateDiffStats:KEY_PAGE_STATS_CELL_DATA_UN_RECYCLE_NUM withDiffValue:1];
         }
+        if (attributes[@"scrollSnapIgnore"]) {
+            _ignoreScrollSnap = [WXConvert BOOL:attributes[@"scrollSnapIgnore"]];
+        }
+        
     }
     
     return self;
@@ -106,6 +110,10 @@
     if (attributes[@"keepScrollPosition"]) {
         _keepScrollPosition = [WXConvert BOOL:attributes[@"keepScrollPosition"]];
     }
+    
+    if (attributes[@"scrollSnapIgnore"]) {
+        _ignoreScrollSnap = [WXConvert BOOL:attributes[@"scrollSnapIgnore"]];
+    }
 }
 
 - (void)_moveToSupercomponent:(WXComponent *)newSupercomponent atIndex:(NSUInteger)index
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
index dee6a8c..ec61ffd 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
+++ b/ios/sdk/WeexSDK/Sources/Component/WXComponent_internal.h
@@ -98,6 +98,8 @@
     UILongPressGestureRecognizer *_longPressGesture;
     UIPanGestureRecognizer *_panGesture;
     
+    BOOL _enableScreenEdgePanGesture;
+
     BOOL _cancelsTouchesInView;
     
     BOOL _listenPanStart;
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXListComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXListComponent.mm
index 7b31e26..1670498 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXListComponent.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXListComponent.mm
@@ -171,6 +171,9 @@
     BOOL _isUpdating;
     NSMutableArray<void(^)(void)> *_updates;
     NSTimeInterval _reloadInterval;
+    
+    CGPoint *_targetContentOffset;
+    CGPoint _nextStepContentOffset;
 }
 
 - (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
@@ -282,6 +285,18 @@
     contentOffsetY += cellRect.origin.y;
     contentOffsetY += offset * self.weexInstance.pixelScaleFactor;
     
+    if (self.snapData.useSnap) {
+        CGFloat snapOffset = [self.snapData calcScrollSnapPositionOffset];
+        contentOffsetY -= (offset * self.weexInstance.pixelScaleFactor + snapOffset);
+        if (self.snapData.alignment == WXScrollSnapAlignCenter) {
+            contentOffsetY += (cellRect.size.height / 2);
+        } else if (self.snapData.alignment == WXScrollSnapAlignEnd) {
+            contentOffsetY += cellRect.size.height;
+        }
+        if (contentOffsetY < 0) {
+            contentOffsetY = 0;
+        }
+    }
     if (_tableView.contentSize.height >= _tableView.frame.size.height && contentOffsetY > _tableView.contentSize.height - _tableView.frame.size.height) {
         contentOffset.y = _tableView.contentSize.height - _tableView.frame.size.height;
     } else {
@@ -623,7 +638,8 @@
     NSIndexPath *fromIndexPath = [self indexPathForCell:cell sections:_sections];
     NSIndexPath *toIndexPath = [self indexPathForSubIndex:index];
     if (toIndexPath.row > [_sections[toIndexPath.section].rows count] || toIndexPath.row < 0) {
-        WXLogError(@"toIndexPath %@ is out of range as the current is %lu",toIndexPath ,(unsigned long)[_sections[toIndexPath.section].rows count]);
+    //FIXME: WXLogError trigger crash
+//        WXLogError(@"toIndexPath %@ is out of range as the current is %lu",toIndexPath ,(unsigned long)[_sections[toIndexPath.section].rows count]);
         return;
     }
     [self removeCellForIndexPath:fromIndexPath withSections:_sections];
@@ -728,6 +744,140 @@
     }
 }
 
+- (NSIndexPath *)getNeighbouringIndexPath:(NSIndexPath *)currentIndexPath findNext:(BOOL)findNext {
+    NSIndexPath *neighbourIndexPath;
+    if (findNext) {
+        if (currentIndexPath.row == _completedSections[currentIndexPath.section].rows.count - 1) {
+            if (currentIndexPath.section == _completedSections.count-1) {// the last cell
+                neighbourIndexPath = [NSIndexPath indexPathForRow:currentIndexPath.row inSection:currentIndexPath.section];
+            } else {// the first cell on next section
+                neighbourIndexPath = [NSIndexPath indexPathForRow:0 inSection:currentIndexPath.section+1];
+            }
+        } else {// the next cell
+            neighbourIndexPath = [NSIndexPath indexPathForRow:currentIndexPath.row+1 inSection:currentIndexPath.section];
+        }
+    } else {
+        if (currentIndexPath.row == 0) {
+            if (currentIndexPath.section == 0) {// the first cell
+                neighbourIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
+            } else {// the last cell on previous section
+                neighbourIndexPath = [NSIndexPath indexPathForRow:_completedSections[currentIndexPath.section-1].rows.count-1 inSection:currentIndexPath.section-1];
+            }
+        } else {// the previous cell
+            neighbourIndexPath = [NSIndexPath indexPathForRow:currentIndexPath.row-1 inSection:currentIndexPath.section];
+        }
+    }
+    return neighbourIndexPath;
+}
+
+
+- (CGPoint)calculateSnapPosition:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity startPosition:(CGPoint)startPosition targetPosition:(CGPoint)preTargetPosition{
+    [self.snapData bindingScrollView:scrollView];
+    WXTableView *tableView = (WXTableView *)scrollView;
+    /// The offset for start position, to avoid the start position error in continuous sliding if last sliding was not finished
+    CGFloat kstartPositionOffset = 10.f;
+    
+    /// The snap point of scroll container when finger touch down
+    self.snapData.startPosition = startPosition;
+    CGPoint snapContainerPosition, currentOffset;
+    CGPoint currentPoint = scrollView.contentOffset;
+    /// The offset of the snap point relative to the container vertex
+    CGFloat snapOffset = [self.snapData calcScrollSnapPositionOffset];
+    if (self.scrollDirection == WXScrollDirectionHorizontal) {
+        currentOffset = CGPointMake(currentPoint.x-startPosition.x, currentPoint.y);
+        snapContainerPosition = CGPointMake(startPosition.x + snapOffset, startPosition.y);
+    } else {
+        currentOffset = CGPointMake(currentPoint.x, currentPoint.y-startPosition.y);
+        snapContainerPosition = CGPointMake(startPosition.x, startPosition.y + snapOffset);
+    }
+    /// Calculate snap staus
+    WXScrollSnapStatus snapStatus = [self.snapData shouldTriggerSnap:currentOffset velocity:velocity];
+    
+    CGPoint targetContentOffset = startPosition;
+    if (currentPoint.x < 0 || currentPoint.y < 0) {
+        return targetContentOffset;
+    }
+    /// Bounce to origin offset
+    if (snapStatus == WXScrollSnapNone) {
+        return targetContentOffset;
+    }
+    
+    /// Determine the start position, if align-start + 4, else if align-end - 4
+    CGPoint correctSnapPosition = snapContainerPosition;
+    if (self.scrollDirection == WXScrollDirectionHorizontal) {
+        switch (self.snapData.alignment) {
+            case WXScrollSnapAlignStart:
+                correctSnapPosition.x += kstartPositionOffset;
+                break;
+            case WXScrollSnapAlignEnd:
+                correctSnapPosition.x -= kstartPositionOffset;
+                break;
+            default:
+                break;
+        }
+    } else {
+        switch (self.snapData.alignment) {
+            case WXScrollSnapAlignStart:
+                correctSnapPosition.y += kstartPositionOffset;
+                break;
+            case WXScrollSnapAlignEnd:
+                correctSnapPosition.y -= kstartPositionOffset;
+                break;
+            default:
+                break;
+        }
+    }
+    /// The cell corresponding to the starting point
+    NSIndexPath *beginIndexPath = [tableView indexPathForRowAtPoint:correctSnapPosition];
+    CGRect beginCellRect = [tableView rectForRowAtIndexPath:beginIndexPath];
+    if (CGRectIsNull(beginCellRect)) {
+        return targetContentOffset;
+    }
+    NSIndexPath *targetIndexPath = beginIndexPath;
+    
+    if (snapStatus == WXScrollSnapStay) {
+        // Do nothing
+    } else {
+        NSIndexPath *lastIndexPath = beginIndexPath;
+        targetIndexPath = [self getNeighbouringIndexPath:beginIndexPath findNext:(snapStatus == WXScrollSnapToNext)];
+        WXCellComponent *cell = [self cellForIndexPath:targetIndexPath];
+        while (cell.ignoreScrollSnap && [lastIndexPath compare:targetIndexPath] != NSOrderedSame) {
+            lastIndexPath = targetIndexPath;
+            targetIndexPath = [self getNeighbouringIndexPath:targetIndexPath findNext:(snapStatus == WXScrollSnapToNext)];
+            cell = [self cellForIndexPath:targetIndexPath];
+        }
+    }
+    
+    CGRect targetCellRect = [tableView rectForRowAtIndexPath:targetIndexPath];
+    
+    if (self.scrollDirection == WXScrollDirectionVertical) {
+        targetContentOffset.y = targetCellRect.origin.y - snapOffset;
+        if (self.snapData.alignment == WXScrollSnapAlignCenter) {
+            targetContentOffset.y += (targetCellRect.size.height / 2);
+        } else if (self.snapData.alignment == WXScrollSnapAlignEnd) {
+            targetContentOffset.y += targetCellRect.size.height;
+        }
+        if (targetContentOffset.y < 0) {
+            targetContentOffset.y = 0;
+        }
+    } else {
+        targetContentOffset.x = targetCellRect.origin.x - snapOffset;
+        if (self.snapData.alignment == WXScrollSnapAlignCenter) {
+            targetContentOffset.x += (targetCellRect.size.width / 2);
+        } else if (self.snapData.alignment == WXScrollSnapAlignEnd) {
+            targetContentOffset.x += targetCellRect.size.width;
+        }
+        if (targetContentOffset.x < 0) {
+            targetContentOffset.x = 0;
+        }
+    }
+    WXLogInfo(@"[scroll snap] veloc:%.2f (%.2f,%.2f)=>(%.2f,%.2f)", velocity.y, startPosition.x, startPosition.y, targetContentOffset.x, targetContentOffset.y);
+    self.snapData.targetIndexPath = targetIndexPath;
+    self.snapData.snapping = true;
+    
+    return targetContentOffset;
+}
+
 - (void)scrollViewDidScroll:(UIScrollView *)scrollView
 {
     [super scrollViewDidScroll:scrollView];
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
index d007d60..cfbbea6 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXRichText.mm
@@ -28,6 +28,7 @@
 #import "WXComponentManager.h"
 #import "WXLog.h"
 #import "WXDarkSchemeProtocol.h"
+#import "WXAssert.h"
 #include <pthread/pthread.h>
 
 @interface WXRichNode : NSObject
@@ -80,7 +81,6 @@
         self.accessibilityTraits |= UIAccessibilityTraitStaticText;
         self.opaque = NO;
         self.editable = NO;
-        self.selectable = YES;
         self.contentMode = UIViewContentModeRedraw;
         self.textContainerInset = UIEdgeInsetsZero;
         self.textContainer.lineFragmentPadding = 0.0f;
@@ -126,6 +126,7 @@
     pthread_mutex_t _attributedStringMutex;
     pthread_mutexattr_t _propertMutexAttr;
     CGFloat _lineHeight;
+    BOOL _selectable;
 }
 
 - (void)dealloc
@@ -140,6 +141,7 @@
         textView = [[WXRichTextView alloc]init];
         textView.delegate = self;
         textView.scrollEnabled = NO;
+        textView.selectable = _selectable;
     }
     return textView;
 }
@@ -162,6 +164,10 @@
         pthread_mutexattr_init(&(_propertMutexAttr));
         pthread_mutexattr_settype(&(_propertMutexAttr), PTHREAD_MUTEX_RECURSIVE);
         pthread_mutex_init(&(_attributedStringMutex), &(_propertMutexAttr));
+        _selectable = YES;
+        if (_attributes[@"selectable"]) {
+            _selectable = [WXConvert BOOL:_attributes[@"selectable"]];
+        }
     }
     return self;
 }
@@ -595,10 +601,24 @@
 }
 
 - (void)updateAttributes:(NSDictionary *)attributes {
+    WXAssertMainThread();
+
+    if (attributes[@"selectable"]) {
+        _selectable = [WXConvert BOOL:attributes[@"selectable"]];
+    }
+    [self textView].selectable = _selectable;
+
+    __weak WXRichText* weakSelf = self;
     WXPerformBlockOnComponentThread(^{
-        _attributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
-        [self syncTextStorageForView];
+        __strong WXRichText* strongSelf = weakSelf;
+        if (strongSelf == nil) {
+            return;
+        }
+        strongSelf->_attributes = [NSMutableDictionary dictionaryWithDictionary:attributes];
+        [strongSelf syncTextStorageForView];
     });
+
+
 }
 
 - (void)updateChildNodeAttributes:(NSDictionary *)attributes ref:(NSString*)ref parentRef:(NSString*)parentRef {
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h
index 687aa80..f12d2ba 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h
+++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.h
@@ -22,6 +22,8 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
+@class WXScrollSnapData, WXScrollAnimator;
+
 @interface WXScrollerComponent : WXComponent <WXScrollerProtocol, UIScrollViewDelegate>
 
 @property (nonatomic, copy) void (^onScroll)(UIScrollView *scrollView);
@@ -32,10 +34,114 @@
 
 @property (nonatomic, assign) CGSize contentSize;
 
+@property (nonatomic, strong) WXScrollSnapData *snapData;
+
+@property (nonatomic, strong) WXScrollAnimator * _Nullable scrollAnimator;
+
 - (void)handleAppear;
 
 - (CGPoint)absolutePositionForComponent:(WXComponent *)component;
 
 @end
 
+typedef NS_ENUM(NSUInteger, WXScrollSnapStatus) {
+    WXScrollSnapToNext,
+    WXScrollSnapToPrev,
+    WXScrollSnapStay,
+    WXScrollSnapNone,
+};
+
+typedef NS_ENUM(NSUInteger, WXScrollSnapAlignment) {
+    WXScrollSnapAlignStart,
+    WXScrollSnapAlignCenter,
+    WXScrollSnapAlignEnd,
+    WXScrollSnapAlignNone,
+};
+
+typedef NS_ENUM(NSUInteger, WXScrollAnimateFunction) {
+    WXScrollAnimateNone = 0,
+    WXScrollAnimateLinear,
+    WXScrollAnimateQuadOut,
+    WXScrollAnimateQuadInOut,
+    WXScrollAnimateQuadIn,
+    WXScrollAnimateCubicIn,
+    WXScrollAnimateCubicOut,
+    WXScrollAnimateCubicInOut,
+    WXScrollAnimateQuartIn,
+    WXScrollAnimateQuartOut,
+    WXScrollAnimateQuartInOut,
+    WXScrollAnimateSineIn,
+    WXScrollAnimateSineOut,
+    WXScrollAnimateSineInOut,
+    WXScrollAnimateQuintIn,
+    WXScrollAnimateQuintOut,
+    WXScrollAnimateQuintInOut,
+    WXScrollAnimateExpoIn,
+    WXScrollAnimateExpoOut,
+    WXScrollAnimateExpoInOut,
+    WXScrollAnimateCircleIn,
+    WXScrollAnimateCircleOut,
+    WXScrollAnimateCircleInOut,
+};
+
+@interface WXScrollSnapData : NSObject
+
+@property (nonatomic, assign) BOOL useSnap;
+
+@property (nonatomic, assign) WXScrollDirection axis;
+
+@property (nonatomic, assign) WXScrollSnapAlignment alignment;
+
+@property (nonatomic, assign) UIEdgeInsets padding;
+
+@property (nonatomic, strong) NSIndexPath *targetIndexPath;
+
+@property (nonatomic, weak) UIScrollView *scrollView;
+
+@property (nonatomic, assign) CGPoint startPosition;
+
+@property (nonatomic, assign) CGPoint targetPosition;
+
+@property (nonatomic, assign) BOOL snapping;
+
+/// scroll animate
+@property (nonatomic, assign) WXScrollAnimateFunction timingFunction;
+@property (nonatomic, assign) CGFloat scrollAnimateDuration;
+
+/// bind scrollView
+- (void)bindingScrollView:(UIScrollView *)scrollView;
+/// calc snap status;
+- (WXScrollSnapStatus)shouldTriggerSnap:(CGPoint)offset velocity:(CGPoint)velocity;
+/// 
+- (CGFloat)calcScrollSnapPositionOffset;
+
+@end
+
+typedef void(^WXScrollAnimatorCompletion)(void);
+
+@interface WXScrollAnimator : NSObject
+
+@property (nonatomic, weak) UIScrollView *scrollView;
+
+@property (nonatomic, assign) WXScrollAnimateFunction timingFunction;
+
+@property (nonatomic, assign) NSTimeInterval startTime;
+@property (nonatomic, assign) NSTimeInterval duration;
+@property (nonatomic, assign) NSTimeInterval runTime;
+
+@property (nonatomic, assign) CGPoint startOffset;
+@property (nonatomic, assign) CGPoint destinationOffset;
+
+@property (nonatomic, copy) WXScrollAnimatorCompletion completion;
+
+@property (nonatomic, strong) CADisplayLink *timer;
+
+- (instancetype)initWithScrollView:(UIScrollView *)scrollView timingFunction:(WXScrollAnimateFunction)timingFunction;
+
+- (void)setContentOffset:(CGPoint)offset duration:(NSTimeInterval)duration;
+
+- (CGFloat)computeAnimateWithTime:(CGFloat)time begin:(CGFloat)begin change:(CGFloat)change duration:(CGFloat)duration;
+
+@end
+
 NS_ASSUME_NONNULL_END
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm
index 981a7af..84225fa 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXScrollerComponent.mm
@@ -84,6 +84,285 @@
 @end
 
 
+
+@interface WXScrollSnapData ()
+
+@property (nonatomic, assign) CGPoint triggerOffset;
+
+@end
+
+/// Default snap trigger velocity and offset
+CGFloat kDefaultScrollSnapVelocity = 1.2;
+CGFloat kDefaultScrollSnapTriggerOffset = 60;
+
+@implementation WXScrollSnapData
+
+- (instancetype)init
+{
+    self = [super init];
+    if (self) {
+        self.triggerOffset = CGPointMake(kDefaultScrollSnapTriggerOffset, kDefaultScrollSnapTriggerOffset);
+        self.useSnap = false;
+        self.padding = UIEdgeInsetsZero;
+        self.scrollAnimateDuration = 0.25;
+        self.timingFunction = WXScrollAnimateNone;
+    }
+    return self;
+}
+
+- (void)bindingScrollView:(UIScrollView *)scrollView {
+    _scrollView = scrollView;
+    _scrollView.pagingEnabled = NO;
+    _scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
+}
+
+- (WXScrollSnapStatus)shouldTriggerSnap:(CGPoint)offset velocity:(CGPoint)velocity {
+    BOOL shouldScrollToNextCell = false;
+    BOOL shouldScrollToPrevCell = false;
+    
+    if (self.axis == WXScrollDirectionVertical) {
+        if (velocity.y > kDefaultScrollSnapVelocity) {
+            shouldScrollToNextCell = true;
+        } else if (velocity.y < -kDefaultScrollSnapVelocity) {
+            shouldScrollToPrevCell = true;
+        } else if (offset.y > self.triggerOffset.y) {
+            shouldScrollToNextCell = true;
+        } else if (offset.y < -(self.triggerOffset.y * 2)) {
+            shouldScrollToPrevCell = true;
+        }
+    } else {
+        if (velocity.x > kDefaultScrollSnapVelocity) {
+            shouldScrollToNextCell = true;
+        } else if (velocity.x < -kDefaultScrollSnapVelocity) {
+            shouldScrollToPrevCell = true;
+        } else if (offset.x > self.triggerOffset.x) {
+            shouldScrollToNextCell = true;
+        } else if (offset.x < -(self.triggerOffset.x * 2)) {
+            shouldScrollToPrevCell = true;
+        }
+    }
+    if (shouldScrollToPrevCell == false && shouldScrollToNextCell == false) {
+        return WXScrollSnapStay;
+    }
+    if (shouldScrollToNextCell == true && shouldScrollToPrevCell == true) {
+        return WXScrollSnapNone;
+    }
+    return shouldScrollToNextCell ? WXScrollSnapToNext : WXScrollSnapToPrev;
+}
+
+- (CGFloat)calcScrollSnapPositionOffset {
+    CGFloat targetOffset = 0;
+    if (!self.scrollView) {
+        return targetOffset;
+    }
+    CGSize containerSize = self.scrollView.frame.size;
+    if (self.axis == WXScrollDirectionHorizontal) {
+        switch (self.alignment) {
+            case WXScrollSnapAlignStart:
+                targetOffset = self.padding.left;
+                break;
+            case WXScrollSnapAlignCenter:
+                targetOffset = (containerSize.width + self.padding.left - self.padding.right)/2;
+                break;
+            case WXScrollSnapAlignEnd:
+                targetOffset = containerSize.width - self.padding.right;
+                break;
+            default:
+                targetOffset = self.padding.left;
+                break;
+        }
+        if (targetOffset < 0) {
+            targetOffset = 0;
+        }
+        if (targetOffset > self.scrollView.contentSize.width - containerSize.width) {
+            targetOffset = self.scrollView.contentSize.width - containerSize.width;
+        }
+    } else {
+        switch (self.alignment) {
+            case WXScrollSnapAlignStart:
+                targetOffset = self.padding.top;
+                break;
+            case WXScrollSnapAlignCenter:
+                targetOffset = (containerSize.height + self.padding.top - self.padding.bottom)/2;
+                break;
+            case WXScrollSnapAlignEnd:
+                targetOffset = containerSize.height - self.padding.bottom;
+                break;
+            default:
+                targetOffset = self.padding.top;
+                break;
+        }
+        if (targetOffset < 0) {
+            targetOffset = 0;
+        }
+        if (targetOffset > self.scrollView.contentSize.height - containerSize.height) {
+            targetOffset = self.scrollView.contentSize.height - containerSize.height;
+        }
+    }
+    return targetOffset;
+}
+
+
+- (NSString *)description
+{
+    return [NSString stringWithFormat:@"[ScrollSnap] {\n\tuseSnap:%d \n\tpadding:(top:%.2f, right:%.2f, bottom:%.2f, left:%.2f)\n\tstartPosition:(%.2f, %.2f)\n\ttargetIndexPath:%@\n\ttargetPosition(%.2f, %.2f)}", _useSnap, _padding.top, _padding.right, _padding.bottom, _padding.left ,_startPosition.x, _startPosition.y, _targetIndexPath, _targetPosition.x, _targetPosition.y];
+}
+
+@end
+
+@interface WXScrollAnimator()
+
+@end
+
+@implementation WXScrollAnimator
+
+- (instancetype)initWithScrollView:(WXScrollerComponentView *)scrollView timingFunction:(WXScrollAnimateFunction)timingFunction {
+    if (self = [super init]) {
+        _scrollView = scrollView;
+        _timingFunction = timingFunction;
+    }
+    return self;
+}
+
+- (CGFloat)computeAnimateWithTime:(CGFloat)time begin:(CGFloat)begin change:(CGFloat)change duration:(CGFloat)duration {
+    switch (self.timingFunction) {
+        case WXScrollAnimateNone:
+            return 0;
+        case WXScrollAnimateLinear:
+            return change * time / duration + begin;
+        case WXScrollAnimateQuadOut:
+            time /= duration;
+            return -change * time * (time - 2) + begin;
+        case WXScrollAnimateQuadInOut:
+            time /= (duration / 2);
+            if (time < 1) {
+                return change / 2 * time * time + begin;
+            }
+            time -= 1;
+            return -change / 2 * (time * (time - 2) - 1) + begin;
+        case WXScrollAnimateQuadIn:
+            time /= duration;
+            return change * time * time + begin;
+        case WXScrollAnimateCubicIn:
+            time /= duration;
+            return change * time * time * time + begin;
+        case WXScrollAnimateCubicOut:
+            time = time / duration - 1;
+            return change * (time * time * time + 1) + begin;
+        case WXScrollAnimateCubicInOut:
+            time /= duration / 2;
+            if (time < 1) {
+                return change / 2 * time * time * time + begin;
+            }
+            time -= 2;
+            return change / 2 * ( time * time * time + 2) + begin;
+        case WXScrollAnimateQuartIn:
+            time /= duration;
+            return change * time * time * time * time + begin;
+        case WXScrollAnimateQuartOut:
+            time = time / duration - 1;
+            return -change * (time * time * time * time - 1) + begin;
+        case WXScrollAnimateQuartInOut:
+            time /= duration / 2;
+            if (time < 1) {
+                return change / 2 * time * time * time * time + begin;
+            }
+            time -= 2;
+            return -change / 2 * (time * time * time * time - 2) + begin;
+        case WXScrollAnimateSineIn:
+            return -change * cos(time / duration * M_PI_2) + change + begin;
+        case WXScrollAnimateSineOut:
+            return change * sin(time / duration * M_PI_2) + begin;
+        case WXScrollAnimateSineInOut:
+            return -change / 2 * (cos(M_PI * time / duration) - 1) + begin;
+        case WXScrollAnimateQuintIn:
+            time /= duration;
+            return change * time * time * time * time * time + begin;
+        case WXScrollAnimateQuintOut:
+            time = time / duration - 1;
+            return change * (time * time * time * time * time + 1) + begin;
+        case WXScrollAnimateQuintInOut:
+            time /= duration / 2;
+            if (time < 1) {
+                return change / 2 * time * time * time * time * time + begin;
+            }
+            time -= 2;
+            return change / 2 * (time * time * time * time * time + 2) + begin;
+        case WXScrollAnimateExpoIn:
+            return (time == 0) ? begin : change * pow(2, 10*(time / duration - 1)) + begin;
+        case WXScrollAnimateExpoOut:
+            return (time == duration) ? begin + change : change * (-pow(2, -10 * time / duration) + 1) + begin;
+        case WXScrollAnimateExpoInOut:
+            if (time == 0) {
+                return begin;
+            }
+            if (time == duration) {
+                return begin + change;
+            }
+            time /= duration / 2;
+            if (time < 1) {
+                return change / 2 * pow(2, 10 * (time - 1)) + begin;
+            }
+            time -= 1;
+            return change / 2 * (-pow(2, -10 * time) + 2) + begin;
+        case WXScrollAnimateCircleIn:
+            time /= duration;
+            return -change * (sqrt(1 - time * time) - 1) + begin;
+        case WXScrollAnimateCircleOut:
+            time = time / duration - 1;
+            return change * sqrt(1 - time * time) + begin;
+        case WXScrollAnimateCircleInOut:
+            time /= duration / 2;
+            if (time < 1) {
+                return -change / 2 * (sqrt(1 - time * time) - 1) + begin;
+            }
+            time -= 2;
+            return change / 2 * (sqrt(1 - time * time) + 1) + begin;
+    }
+}
+
+- (void)setContentOffset:(CGPoint)offset duration:(NSTimeInterval)duration {
+    if (!_scrollView) {
+        return;
+    }
+    _startTime = [[NSDate date] timeIntervalSince1970];
+    _startOffset = _scrollView.contentOffset;
+    _destinationOffset = offset;
+    _duration = duration;
+    _runTime = 0;
+    if (_duration < 0) {
+        [_scrollView setContentOffset:offset animated:false];
+        return;
+    }
+    if (_timer == nil) {
+        _timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(animatedScroll)];
+        [_timer addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
+    }
+}
+
+- (void)animatedScroll {
+    if (!_timer || !_scrollView) {
+        return;
+    }
+    _runTime += _timer.duration;
+    if (_runTime >= _duration) {
+        [_scrollView setContentOffset:_destinationOffset animated:false];
+        [_timer invalidate];
+        _timer = nil;
+        if (self.completion) {
+            self.completion();
+        }
+        return;
+    }
+    CGPoint offset = _scrollView.contentOffset;
+    offset.x = [self computeAnimateWithTime:_runTime begin:_startOffset.x change:(_destinationOffset.x - _startOffset.x) duration:_duration];
+    offset.y = [self computeAnimateWithTime:_runTime begin:_startOffset.y change:(_destinationOffset.y - _startOffset.y) duration:_duration];
+    [_scrollView setContentOffset:offset animated:false];
+}
+
+@end
+
+
 @interface WXScrollerComponent()
 
 @property (nonatomic, strong) NSMutableArray *  stickyArray;
@@ -176,6 +455,7 @@
         
         _stickyArray = [NSMutableArray array];
         _listenerArray = [NSMutableArray array];
+        _snapData = [[WXScrollSnapData alloc] init];
         _scrollEvent = NO;
         _scrollStartEvent = NO;
         _scrollEndEvent = NO;
@@ -231,11 +511,83 @@
         if (weexInstance.instanceCallback) {
             weexInstance.instanceCallback(weexInstance, WXScrollerComponentCreatedCallback, self);
         }
+        
+        [self handleScrollSnapAttributes:attributes];
     }
     
     return self;
 }
 
+- (void)handleScrollSnapAttributes:(NSDictionary *)attrs {
+    if (attrs[@"scrollSnap"]) {
+        _snapData.useSnap = attrs[@"scrollSnap"] ? [WXConvert BOOL:attrs[@"scrollSnap"]] : NO;
+        _snapData.axis = _scrollDirection;
+    }
+    CGFloat top=0, right=0, bottom=0, left=0;
+    if (attrs[@"scrollPaddingTop"]) {
+        top = attrs[@"scrollPaddingTop"] ? [WXConvert WXPixelType:attrs[@"scrollPaddingTop"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0.f;
+    }
+    if (attrs[@"scrollPaddingRight"]) {
+        right = attrs[@"scrollPaddingRight"] ? [WXConvert WXPixelType:attrs[@"scrollPaddingRight"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0.f;
+    }
+    if (attrs[@"scrollPaddingBottom"]) {
+        bottom = attrs[@"scrollPaddingBottom"] ? [WXConvert WXPixelType:attrs[@"scrollPaddingBottom"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0.f;
+    }
+    if (attrs[@"scrollPaddingLeft"]) {
+        left = attrs[@"scrollPaddingLeft"] ? [WXConvert WXPixelType:attrs[@"scrollPaddingLeft"] scaleFactor:self.weexInstance.pixelScaleFactor] : 0.f;
+    }
+    _snapData.padding = UIEdgeInsetsMake(top, left, bottom, right);
+    if (attrs[@"scrollSnapAlign"]) {
+        NSString *alignment = attrs[@"scrollSnapAlign"];
+        if ([alignment isEqualToString:@"start"]) {
+            _snapData.alignment = WXScrollSnapAlignStart;
+        } else if ([alignment isEqualToString:@"center"]) {
+            _snapData.alignment = WXScrollSnapAlignCenter;
+        } else if ([alignment isEqualToString:@"end"]) {
+            _snapData.alignment = WXScrollSnapAlignEnd;
+        } else {
+            _snapData.alignment = WXScrollSnapAlignNone;
+        }
+    }
+    if (attrs[@"scrollAnimateFunc"]) {
+        _snapData.timingFunction = [self translateScrollAnimateFunction:attrs[@"scrollAnimateFunc"]];
+    }
+    if (attrs[@"scrollAnimateDuration"]) {
+        _snapData.scrollAnimateDuration = [WXConvert CGFloat:attrs[@"scrollAnimateDuration"]];
+    }
+}
+
+- (WXScrollAnimateFunction)translateScrollAnimateFunction:(NSString *)name {
+    NSDictionary *dic = @{
+        @"linear" : @(WXScrollAnimateLinear),
+        @"quadIn" : @(WXScrollAnimateQuadIn),
+        @"quadOut" : @(WXScrollAnimateQuadOut),
+        @"quadInOut" : @(WXScrollAnimateQuadInOut),
+        @"cubicIn" : @(WXScrollAnimateCubicIn),
+        @"cubicOut" : @(WXScrollAnimateCubicOut),
+        @"cubicInOut" : @(WXScrollAnimateCubicInOut),
+        @"quartIn" : @(WXScrollAnimateQuartIn),
+        @"quartOut" : @(WXScrollAnimateQuartOut),
+        @"quartInOut" : @(WXScrollAnimateQuartInOut),
+        @"quintIn" : @(WXScrollAnimateQuintIn),
+        @"quintOut" : @(WXScrollAnimateQuintOut),
+        @"quintInOut" : @(WXScrollAnimateQuintInOut),
+        @"sineIn" : @(WXScrollAnimateSineIn),
+        @"sineOut" : @(WXScrollAnimateSineOut),
+        @"sineInOut" : @(WXScrollAnimateSineInOut),
+        @"expoIn" : @(WXScrollAnimateExpoIn),
+        @"expoOut" : @(WXScrollAnimateExpoOut),
+        @"expoInOut" : @(WXScrollAnimateExpoInOut),
+        @"circleIn" : @(WXScrollAnimateCircleIn),
+        @"circleOut" : @(WXScrollAnimateCircleOut),
+        @"circleInOut" : @(WXScrollAnimateCircleInOut),
+    };
+    if (dic[name]) {
+        return (WXScrollAnimateFunction)[dic[name] integerValue];
+    }
+    return WXScrollAnimateNone;
+}
+
 - (UIView *)loadView
 {
     return [[WXScrollerComponentView alloc] init];
@@ -278,9 +630,10 @@
         scrollView.scrollsToTop = YES;
     }
     
-    if (_pagingEnabled && _pageSize > 0) {
+    if ((_pagingEnabled && _pageSize > 0) || _snapData.useSnap) {
         scrollView.pagingEnabled = NO; // turn off system default paging
         scrollView.decelerationRate = UIScrollViewDecelerationRateFast;
+        [_snapData bindingScrollView:scrollView];
     }
     else {
         scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
@@ -337,10 +690,13 @@
         }
     }
     
+    [self handleScrollSnapAttributes:attributes];
+    
     if ([self isViewLoaded]) {
-        if (_pagingEnabled && _pageSize > 0) {
+        if ((_pagingEnabled && _pageSize > 0) || _snapData.useSnap) {
             ((UIScrollView *)self.view).pagingEnabled = NO; // turn off system default paging
             ((UIScrollView *)self.view).decelerationRate = UIScrollViewDecelerationRateFast;
+            [_snapData bindingScrollView:(UIScrollView *)self.view];
         }
         else {
             ((UIScrollView *)self.view).decelerationRate = UIScrollViewDecelerationRateNormal;
@@ -859,6 +1215,9 @@
     if (!_isScrolling) {
         [self dispatchScrollEndEvent:scrollView];
         _scrollEndPoint = scrollView.contentOffset;
+        if (_snapData.useSnap && _snapData.snapping == true) {
+            _snapData.snapping = false;
+        }
         id<WXPageEventNotifyEventProtocol> eventNotify = [WXSDKEngine handlerForProtocol:@protocol(WXPageEventNotifyEventProtocol)];
         if ([eventNotify respondsToSelector:@selector(notifyScrollEvent:from:to:)]) {
             [eventNotify notifyScrollEvent:self.weexInstance.instanceId from:_scrollStartPoint to:_scrollEndPoint];
@@ -873,8 +1232,49 @@
     }
 }
 
+- (CGPoint)calculateSnapPosition:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity startPosition:(CGPoint)startPosition targetPosition:(CGPoint)targetPosition{
+    return CGPointMake(-1, -1);
+}
+
+- (void)setContentOffset:(CGPoint)contentOffset duration:(NSTimeInterval)duration timingFunction:(WXScrollAnimateFunction)timingFunction completion:(void(^)(void))completion {
+    if (!_scrollAnimator) {
+        _scrollAnimator = [[WXScrollAnimator alloc] initWithScrollView:(UIScrollView *)self.view timingFunction:timingFunction];
+    }
+    __weak typeof(self) weakSelf = self;
+    _scrollAnimator.completion = ^{
+        __strong typeof(self) strongSelf = weakSelf;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            strongSelf.scrollAnimator = nil;
+        });
+        if (completion) {
+            completion();
+        }
+    };
+    [_scrollAnimator setContentOffset:contentOffset duration:duration];
+}
+
 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
 {
+    if (_snapData.useSnap) {
+        CGPoint offset = [self calculateSnapPosition:scrollView withVelocity:velocity startPosition:_scrollStartPoint targetPosition:CGPointMake(targetContentOffset->x, targetContentOffset->y)];
+        // offset beyond bounary use default animation, make sure bounce effect
+        if (offset.x + scrollView.frame.size.width > scrollView.contentSize.width ||
+            offset.y + scrollView.frame.size.height > scrollView.contentSize.height ||
+            (self.scrollDirection == WXScrollDirectionVertical && offset.y <= 0) ||
+            (self.scrollDirection == WXScrollDirectionHorizontal && offset.x <= 0)) {
+            targetContentOffset->x = offset.x;
+            targetContentOffset->y = offset.y;
+        }
+        else if (self.snapData.timingFunction != WXScrollAnimateNone) {
+            [self setContentOffset:offset duration:self.snapData.scrollAnimateDuration timingFunction:self.snapData.timingFunction completion:^{
+                WXLogInfo(@"snap scroll animate over");
+            }];
+        } else {
+            targetContentOffset->x = offset.x;
+            targetContentOffset->y = offset.y;
+        }
+        self.snapData.targetPosition = offset;
+    }
     // Page stop effect
     if (_pagingEnabled && _pageSize > 0) {
         if (_scrollDirection == WXScrollDirectionVertical) {
@@ -1037,6 +1437,9 @@
             _scrollEventListener(self, @"scrollend", @{@"contentSize":contentSizeData, @"contentOffset":contentOffsetData});
         }
     }
+    if (_snapData.useSnap) {
+        [self fireEvent:@"snapend" params:@{@"section" : @(self.snapData.targetIndexPath.section), @"row" : @(self.snapData.targetIndexPath.row)} domChanges:nil];
+    }
 }
 
 - (void)scrollToTarget:(WXScrollToTarget *)target scrollRect:(CGRect)rect
diff --git a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
index 27538dc..ac9f5a4 100644
--- a/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
+++ b/ios/sdk/WeexSDK/Sources/Component/WXTextComponent.mm
@@ -947,7 +947,7 @@
         attrs = attrs ? attrs.mutableCopy : [NSMutableDictionary new];
         CTFontRef font = (__bridge CTFontRef)(attrs[(id)kCTFontAttributeName]);
         CGFloat fontSize = font ? CTFontGetSize(font):32 * self.weexInstance.pixelScaleFactor;
-        UIFont * uiFont = [UIFont systemFontOfSize:fontSize];
+        UIFont *uiFont = [WXUtility fontWithSize:fontSize textWeight:_fontWeight textStyle:WXTextStyleNormal fontFamily:self.fontFamily scaleFactor:self.weexInstance.pixelScaleFactor useCoreText:[self useCoreText]];
         if (uiFont) {
             font = CTFontCreateWithFontDescriptor((__bridge CTFontDescriptorRef)uiFont.fontDescriptor, uiFont.pointSize, NULL);
         }
@@ -1079,6 +1079,8 @@
     CGFloat ascent = 0;
     CGFloat descent = 0;
     CGFloat leading = 0;
+    CGPoint lineOrigins[lineCount];
+    CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), lineOrigins);
     
     // height = ascent + descent + lineCount*leading
     // ignore linespaing
@@ -1091,7 +1093,6 @@
         totalHeight += ascent + descent;
         actualLineCount ++;
     }
-    
     totalHeight = totalHeight + actualLineCount * leading;
     CFRelease(frameRef);
     
@@ -1102,6 +1103,18 @@
         }
         return CGSizeMake(aWidth, suggestSize.height);
     }
+    if (WX_SYS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"14.0")) {
+        if (lineCount <= 1) {
+            return CGSizeMake(aWidth, totalHeight);
+        }
+        if (_lines && lineCount > _lines) {
+            actualLineCount = _lines;
+        } else {
+            actualLineCount = lineCount;
+        }
+        CGFloat actualLineHeight = lineOrigins[0].y - lineOrigins[1].y;
+        return CGSizeMake(aWidth, actualLineCount * actualLineHeight);
+    }
     return CGSizeMake(aWidth, totalHeight);
 }
 
diff --git a/ios/sdk/WeexSDK/Sources/Engine/WXSDKError.h b/ios/sdk/WeexSDK/Sources/Engine/WXSDKError.h
index 0800003..fd4813e 100644
--- a/ios/sdk/WeexSDK/Sources/Engine/WXSDKError.h
+++ b/ios/sdk/WeexSDK/Sources/Engine/WXSDKError.h
@@ -63,7 +63,7 @@
     WX_ERR_JS_EXECUTE = -2013,
     WX_ERR_JSBRIDGE_END = -2099,
     
-    WX_ERR_RENDER_START = -2100,
+    WX_ERR_RENDER_START = -2120,
     WX_ERR_RENDER_CREATEBODY = -2100,
     WX_ERR_RENDER_UPDATTR = -2101,
     WX_ERR_RENDER_UPDSTYLE = -2102,
diff --git a/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m b/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m
index d7b594c..3177a7e 100644
--- a/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m
+++ b/ios/sdk/WeexSDK/Sources/Events/WXComponent+Events.m
@@ -33,6 +33,8 @@
 #import <UIKit/UIGestureRecognizerSubclass.h>
 #import "WXComponent+PseudoClassManagement.h"
 #import "WXCoreBridge.h"
+#import "WXSDKEngine.h"
+#import "WXConfigCenterProtocol.h"
 
 #pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
 
@@ -575,9 +577,16 @@
 - (void)addPanGesture
 {
     if (!_panGesture) {
+        _enableScreenEdgePanGesture = YES;
         _panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(onPan:)];
+
         _panGesture.delegate = self;
         [self.view addGestureRecognizer:_panGesture];
+
+        id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)];
+        if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) {
+            _enableScreenEdgePanGesture = [[configCenter configForKey:@"iOS_weex_ext_config.enableScreenEdgePanGesture" defaultValue:@(YES) isDefault:NULL] boolValue];
+        }
     }
 }
 
@@ -840,6 +849,12 @@
     if ([gestureRecognizer isKindOfClass:[WXTouchGestureRecognizer class]]) {
         return YES;
     }
+
+    if (_enableScreenEdgePanGesture && gestureRecognizer == _panGesture && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && ![otherGestureRecognizer isKindOfClass:NSClassFromString(panGestureRecog)]) {
+        [gestureRecognizer requireGestureRecognizerToFail:otherGestureRecognizer];
+        return YES;
+    }
+
     // swipe and scroll
     if ([gestureRecognizer isKindOfClass:[UISwipeGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:NSClassFromString(panGestureRecog)]) {
         return YES;
diff --git a/ios/sdk/WeexSDK/Sources/Handler/WXUnicornEventListenerHandler.h b/ios/sdk/WeexSDK/Sources/Handler/WXUnicornEventListenerHandler.h
new file mode 100644
index 0000000..a3fe4c3
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Handler/WXUnicornEventListenerHandler.h
@@ -0,0 +1,30 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface WXUnicornEventListenerHandler : NSObject
+
++ (void)fireEvent:(NSDictionary *)args;
+
+@end
+
+NS_ASSUME_NONNULL_END
+
diff --git a/ios/sdk/WeexSDK/Sources/Handler/WXUnicornEventListenerHandler.m b/ios/sdk/WeexSDK/Sources/Handler/WXUnicornEventListenerHandler.m
new file mode 100644
index 0000000..23d461b
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Handler/WXUnicornEventListenerHandler.m
@@ -0,0 +1,36 @@
+/*
+ * 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 "WXUnicornEventListenerHandler.h"
+
+#import "WXSDKManager.h"
+
+@implementation WXUnicornEventListenerHandler
+
++ (void)fireEvent:(NSDictionary *)args {
+    NSMutableDictionary* eventParams = [args[@"params"] mutableCopy];
+    NSTimeInterval timeSp = [[NSDate date] timeIntervalSince1970] * 1000;
+    [eventParams setObject:@(timeSp) forKey:@"timestamp"];
+    NSDictionary* domChanges = [args[@"domChanges"] copy];
+    [[WXSDKManager bridgeMgr] fireEvent:args[@"pageId"] ref:args[@"ref"] type:args[@"type"] params:eventParams domChanges:domChanges?:@{}];
+}
+
+@end
+
+
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h
index e5fefd3..295fcd8 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.h
@@ -248,6 +248,8 @@
 
 - (void)callJSMethod:(NSString *)method args:(NSArray *)args;
 
+- (void)callJSMethod:(NSString *)method args:(NSArray *)args completion:(void (^ _Nullable)(JSValue * _Nullable))completion;
+
 - (void)executeJSTaskQueue;
 
 - (void)checkJSThread;
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m
index 5c1f455..7b1da3d 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXBridgeManager.m
@@ -769,8 +769,7 @@
     });
 }
 
-- (void)callJSMethod:(NSString *)method args:(NSArray *)args
-{
+- (void)callJSMethod:(NSString *)method args:(NSArray *)args completion:(void (^)(JSValue *))completion {
     if (!method) return;
 
     __weak typeof(self) weakSelf = self;
@@ -778,10 +777,14 @@
     WXPerformBlockOnBridgeThreadForInstance(^(){
         WXSDKInstance* sdkInstance = [WXSDKManager instanceForID:instanceId];
         WXBridgeContext* context = sdkInstance && sdkInstance.useBackupJsThread ? weakSelf.backupBridgeCtx :  weakSelf.bridgeCtx;
-        [context callJSMethod:method args:args onContext:nil completion:nil];
+        [context callJSMethod:method args:args onContext:nil completion:completion];
     }, instanceId);
 }
 
+- (void)callJSMethod:(NSString *)method args:(NSArray *)args {
+    [self callJSMethod:method args:args completion:nil];
+}
+
 #pragma mark JS Thread Check
 - (void)checkJSThread {
     if (!_timer) {
diff --git a/ios/sdk/WeexSDK/Sources/Manager/WXDatePickerManager.m b/ios/sdk/WeexSDK/Sources/Manager/WXDatePickerManager.m
index 9c67181..0636fa4 100644
--- a/ios/sdk/WeexSDK/Sources/Manager/WXDatePickerManager.m
+++ b/ios/sdk/WeexSDK/Sources/Manager/WXDatePickerManager.m
@@ -22,6 +22,7 @@
 #import <UIKit/UIKit.h>
 #import "WXConvert.h"
 #import "WXUtility.h"
+#import "WXLegacyAdapter.h"
 
 #define WXPickerHeight 266
 
@@ -61,13 +62,12 @@
         {
             datePicker = [[UIDatePicker alloc]init];
         }
-        
-        datePicker.datePickerMode=UIDatePickerModeDate;
-#ifdef __IPHONE_13_4
+    #if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130400)
         if (@available(iOS 13.4, *)) {
             datePicker.preferredDatePickerStyle = UIDatePickerStyleWheels;
         }
-#endif
+    #endif
+        datePicker.datePickerMode=UIDatePickerModeDate;
         CGRect pickerFrame = CGRectMake(0, 44, [UIScreen mainScreen].bounds.size.width, WXPickerHeight-44);
         datePicker.backgroundColor = [UIColor whiteColor];
         datePicker.frame = pickerFrame;
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
index 2fd7bb7..3e5ad21 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
+++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance.m
@@ -91,6 +91,9 @@
 
 - (void)dealloc
 {
+    if (self.unicornRender) {
+        [self.unicornRender shutdown];
+    }
     [_moduleEventObservers removeAllObjects];
     [self removeObservers];
 }
@@ -364,6 +367,16 @@
     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];
+    if ([options[@"USE_UNICORN"] boolValue]) {
+        __weak typeof(self) weakSelf = self;
+        WXPerformBlockOnMainThread(^{
+            __strong typeof(weakSelf) strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
+            [strongSelf initUnicornRender];
+        });
+    }
 
     if (self.renderPlugin.isSupportExecScript) {
         NSMutableDictionary *newOptions = [NSMutableDictionary dictionaryWithDictionary:options];
@@ -389,7 +402,21 @@
         [[WXSDKManager bridgeMgr] executeJSTaskQueue];
     }
 
+    [self _checkPageName];
+    [self.apmInstance startRecord:self.instanceId];
+    self.apmInstance.isStartRender = YES;
+
     self.needValidate = [[WXHandlerFactory handlerForProtocol:@protocol(WXValidateProtocol)] needValidate:self.scriptURL];
+    if ([options[@"USE_UNICORN"] boolValue]) {
+        __weak typeof(self) weakSelf = self;
+        WXPerformBlockOnMainThread(^{
+            __strong typeof(weakSelf) strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
+            [strongSelf initUnicornRender];
+        });
+    }
 
     if ([source isKindOfClass:[NSString class]]) {
         WXLogDebug(@"Render source: %@, data:%@", self, [WXUtility JSONString:data]);
@@ -523,21 +550,19 @@
         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.renderPlugin) {
         [self.apmInstance setProperty:KEY_PAGE_PROPERTIES_RENDER_TYPE withValue:@"eagle"];
     }
+    if (_options[@"USE_UNICORN"]) {
+        [self.apmInstance setProperty:KEY_PAGE_PROPERTIES_RENDER_TYPE withValue:@"weex2"];
+    }
     
     self.performance.renderTimeOrigin = CACurrentMediaTime()*1000;
     self.performance.renderUnixTimeOrigin = [WXUtility getUnixFixTimeMillis];
@@ -581,10 +606,15 @@
     if ([WXDebugTool getReplacedBundleJS]) {
         mainBundleString = [WXDebugTool getReplacedBundleJS];
     }
-    
+
     WXPerformBlockOnMainThread(^{
         if (self.isCustomRenderType) {
             self->_rootView = [WXCustomPageBridge createPageRootView:self.instanceId pageType:self.renderType frame:self.frame];
+        } else if ([self->_options[@"USE_UNICORN"] boolValue]) {
+            [self initUnicornRender];
+            self->_rootView = [[WXRootView alloc] initWithFrame:self.frame];
+            [self->_rootView addSubview:self.unicornRender.rootView];
+            ((WXRootView*)(self->_rootView)).instance = self;
         }
         else {
             self->_rootView = [[WXRootView alloc] initWithFrame:self.frame];
@@ -862,14 +892,7 @@
     [WXPrerenderManager removePrerenderTaskforUrl:[self.scriptURL absoluteString]];
     [WXPrerenderManager destroyTask:self.instanceId];
 
-    if (_useReactor) {
-        id<WXReactorProtocol> reactorHandler = [WXHandlerFactory handlerForProtocol:NSProtocolFromString(@"WXReactorProtocol")];
-        if (reactorHandler) {
-            [reactorHandler unregisterJSContext:self.instanceId];
-        } else {
-            WXLogError(@"There is no reactor handler");
-        }
-    } else if (!self.renderPlugin) {
+    if (!self.renderPlugin && !_useReactor) {
         [[WXSDKManager bridgeMgr] destroyInstance:self.instanceId];
     }
     
@@ -900,6 +923,14 @@
         
         // Reading config from orange for Release instance in Main Thread or not, for Bug #15172691 +{
         dispatch_async(dispatch_get_main_queue(), ^{
+            if (self.useReactor) {
+                id<WXReactorProtocol> reactorHandler = [WXHandlerFactory handlerForProtocol:NSProtocolFromString(@"WXReactorProtocol")];
+                if (reactorHandler) {
+                    [reactorHandler unregisterJSContext:self.instanceId];
+                } else {
+                    WXLogError(@"There is no reactor handler");
+                }
+            }
             [WXSDKManager removeInstanceforID:instanceId];
             WXLogInfo(@"Finally remove instance: %@", instanceId);
         });
@@ -1142,6 +1173,22 @@
     }
 }
 
+- (void)initUnicornRender {
+    if (_unicornRender) {
+        return;
+    }
+    Class UnicornRenderClass = NSClassFromString(@"UnicornRender");
+    if (UnicornRenderClass) {
+        [self.apmInstance onStage:KEY_PAGE_UNICORN_ENGINE_INIT_START];
+        _unicornRender = (id<WXUnicornRenderProtocol>)[[UnicornRenderClass alloc] initWithInstanceId:self.instanceId];
+        _unicornRender.frame = self.frame;
+        [_unicornRender startEngine:self->_viewController];
+        [self.apmInstance onStage:KEY_PAGE_UNICORN_ENGINE_INIT_END];
+    } else {
+        WXLogError(@"There is no UnicornRender");
+    }
+}
+
 - (void)applicationWillResignActive:(NSNotification*)notification
 {
     [self fireGlobalEvent:WX_APPLICATION_WILL_RESIGN_ACTIVE params:nil];
diff --git a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
index 6ea7e4d..51d79ae 100644
--- a/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
+++ b/ios/sdk/WeexSDK/Sources/Model/WXSDKInstance_private.h
@@ -21,6 +21,7 @@
 #import "WXSDKInstance.h"
 #import "WXModuleMethod.h"
 #import "WXThreadSafeMutableDictionary.h"
+#import "WXUnicornRenderProtocol.h"
 #import <JavaScriptCore/JavaScriptCore.h>
 #import <WeexSDK/WXEaglePlugin.h>
 
@@ -38,6 +39,8 @@
 
 @property (nonatomic, assign) BOOL useReactor;
 
+@property (nonatomic, strong) id<WXUnicornRenderProtocol> unicornRender;
+
 // add monitor information
 @property (nonatomic, strong) NSString *callCreateInstanceContext;
 @property (nonatomic, strong) NSString *createInstanceContextResult;
diff --git a/ios/sdk/WeexSDK/Sources/Module/WXDomModule.m b/ios/sdk/WeexSDK/Sources/Module/WXDomModule.m
index 4b3607b..3a892ad 100644
--- a/ios/sdk/WeexSDK/Sources/Module/WXDomModule.m
+++ b/ios/sdk/WeexSDK/Sources/Module/WXDomModule.m
@@ -31,6 +31,8 @@
 #import "WXRecycleListComponent.h"
 #import "WXCoreBridge.h"
 #import <objc/message.h>
+#import "WXSDKInstance_performance.h"
+#import "WXMonitor.h"
 
 @interface WXDomModule ()
 
@@ -155,7 +157,27 @@
 - (void)createFinish
 {
     NSString* instanceId = self.weexInstance.instanceId;
-    if ([WXCustomPageBridge isCustomPage:instanceId]) {
+    if (self.weexInstance.unicornRender) {
+        __weak typeof(self) weakSelf = self;
+        WXPerformBlockOnMainThread(^{
+            __strong WXDomModule* strongSelf = weakSelf;
+            if (strongSelf == nil) {
+                return;
+            }
+
+            WX_MONITOR_INSTANCE_PERF_END(WXPTFirstScreenRender, strongSelf.weexInstance);
+            WX_MONITOR_INSTANCE_PERF_END(WXPTAllRender, strongSelf.weexInstance);
+            WX_MONITOR_SUCCESS(WXMTJSBridge);
+            WX_MONITOR_SUCCESS(WXMTNativeRender);
+            [strongSelf.weexInstance updatePerDicAfterCreateFinish];
+
+            UIView *rootView = strongSelf.weexInstance.rootView;
+            [strongSelf.weexInstance.performance onInstanceRenderSuccess:strongSelf.weexInstance];
+            if (strongSelf.weexInstance.renderFinish) {
+                strongSelf.weexInstance.renderFinish(rootView);
+            }
+        });
+    } else if ([WXCustomPageBridge isCustomPage:instanceId]) {
         [[WXCustomPageBridge sharedInstance] callCreateFinish:instanceId];
     }
     else {
diff --git a/ios/sdk/WeexSDK/Sources/Module/WXModalUIModule.m b/ios/sdk/WeexSDK/Sources/Module/WXModalUIModule.m
index 6e6f353..4607e4a 100644
--- a/ios/sdk/WeexSDK/Sources/Module/WXModalUIModule.m
+++ b/ios/sdk/WeexSDK/Sources/Module/WXModalUIModule.m
@@ -65,6 +65,8 @@
 
 @interface WXModalUIModule () <UIAlertViewDelegate>
 
+@property (nonatomic, assign) CGFloat maxWidth;
+
 @end
 
 @implementation WXModalUIModule
@@ -107,9 +109,52 @@
         duration = WXToastDefaultDuration;
     }
     
-    WXPerformBlockOnMainThread(^{
-        [self toast:message duration:duration];
-    });
+    _maxWidth = WXToastDefaultWidth;
+    if (param[@"maxWidth"]) {
+        double maxWidth = [param[@"maxWidth"] doubleValue];
+        if (maxWidth > 0) {
+            _maxWidth = maxWidth;
+        }
+    }
+    
+    double animationTime = [param[@"animationTime"] doubleValue];
+    if (animationTime > 0) {
+        WXPerformBlockOnMainThread(^{
+            [self toast:message duration:duration animationTime:animationTime];
+        });
+    } else {
+        WXPerformBlockOnMainThread(^{
+            [self toast:message duration:duration];
+        });
+    }
+}
+
+- (void)toast:(NSString *)message duration:(double)duration animationTime:(double)animationTime{
+    WXAssertMainThread();
+    UIView *superView = self.weexInstance.rootView.window;
+    if (!superView) {
+        superView =  self.weexInstance.rootView;
+    }
+    UIView *toastView = [self toastViewForMessage:message superView:superView];
+    
+    UIView* toastingView = [WXToastManager sharedManager].toastingView;
+    if (toastingView) {
+        [toastingView removeFromSuperview];
+        [WXToastManager sharedManager].toastingView = nil;
+    }
+    if (!toastView || !superView) {
+        return;
+    }
+    [WXToastManager sharedManager].toastingView = toastView;
+    [superView addSubview:toastView];
+    [UIView animateWithDuration:animationTime delay:duration options:UIViewAnimationOptionCurveEaseInOut animations:^{
+        toastView.alpha = 0;
+    } completion:^(BOOL finished) {
+        [toastView removeFromSuperview];
+        if ([WXToastManager sharedManager].toastingView == toastView) {
+            [WXToastManager sharedManager].toastingView = nil;
+        }
+    }];
 }
 
 - (void)toast:(NSString *)message duration:(double)duration
@@ -132,7 +177,7 @@
 - (UIView *)toastViewForMessage:(NSString *)message superView:(UIView *)superView
 {
     CGFloat padding = WXToastDefaultPadding;
-    UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(padding/2, padding/2, WXToastDefaultWidth, WXToastDefaultHeight)];
+    UILabel *messageLabel = [[UILabel alloc] initWithFrame:CGRectMake(padding/2, padding/2, _maxWidth, WXToastDefaultHeight)];
     messageLabel.numberOfLines =  0;
     messageLabel.textAlignment = NSTextAlignmentCenter;
     messageLabel.text = message;
diff --git a/ios/sdk/WeexSDK/Sources/Module/WXPickerModule.m b/ios/sdk/WeexSDK/Sources/Module/WXPickerModule.m
index c5b57a3..0c4da1c 100644
--- a/ios/sdk/WeexSDK/Sources/Module/WXPickerModule.m
+++ b/ios/sdk/WeexSDK/Sources/Module/WXPickerModule.m
@@ -24,6 +24,7 @@
 #import <UIKit/UIPickerView.h>
 #import <UIKit/UIDatePicker.h>
 #import <UIKit/UIKit.h>
+#import "WXLegacyAdapter.h"
 
 #define WXPickerHeight 266
 #define WXPickerToolBarHeight 44
@@ -432,7 +433,7 @@
 {
     self.callback = callback;
     self.datePicker = [[UIDatePicker alloc]init];
-#ifdef __IPHONE_13_4
+#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130400)
     if (@available(iOS 13.4, *)) {
         self.datePicker.preferredDatePickerStyle = UIDatePickerStyleWheels;
     }
diff --git a/ios/sdk/WeexSDK/Sources/Module/WXStreamModule.m b/ios/sdk/WeexSDK/Sources/Module/WXStreamModule.m
index 72106b9..7c6e3fd 100644
--- a/ios/sdk/WeexSDK/Sources/Module/WXStreamModule.m
+++ b/ios/sdk/WeexSDK/Sources/Module/WXStreamModule.m
@@ -27,6 +27,7 @@
 #import "WXSDKEngine.h"
 #import "WXSDKInstance_performance.h"
 #import "WXMonitor.h"
+#import "WXConvert.h"
 
 @implementation WXStreamModule
 
@@ -141,9 +142,17 @@
     if (self.weexInstance && !weexInstance.isJSCreateFinish) {
         self.weexInstance.performance.fsReqNetNum++;
     }
-    
-    WXResourceRequest *request = [WXResourceRequest requestWithURL:[NSURL URLWithString:urlStr] resourceType:WXResourceTypeOthers referrer:nil cachePolicy:NSURLRequestUseProtocolCachePolicy];
-    
+    BOOL shouldSetReferer = NO;
+    if ([options objectForKey:@"referer"] ) {
+        shouldSetReferer = [WXConvert BOOL:[options objectForKey:@"referer"]];
+    }
+    NSRange range = [weexInstance.scriptURL.absoluteString rangeOfString:@"?"];
+    NSString *referer = nil;
+    if (range.length > 0 && shouldSetReferer) {
+        referer = [weexInstance.scriptURL.absoluteString substringToIndex:range.location];
+    }
+    WXResourceRequest *request = [WXResourceRequest requestWithURL:[NSURL URLWithString:urlStr] resourceType:WXResourceTypeOthers referrer:referer cachePolicy:NSURLRequestUseProtocolCachePolicy];
+
     // parse http method
     NSString *method = [options objectForKey:@"method"];
     if ([WXUtility isBlankString:method]) {
diff --git a/ios/sdk/WeexSDK/Sources/Performance/WXApmForInstance.h b/ios/sdk/WeexSDK/Sources/Performance/WXApmForInstance.h
index 7ed92fe..3a19547 100644
--- a/ios/sdk/WeexSDK/Sources/Performance/WXApmForInstance.h
+++ b/ios/sdk/WeexSDK/Sources/Performance/WXApmForInstance.h
@@ -46,6 +46,7 @@
 
 ///************** stages *****************/
 extern NSString* const KEY_PAGE_STAGES_START;
+extern NSString* const KEY_PAGE_STAGES_CONTAINER_READY;
 extern NSString* const KEY_PAGE_STAGES_DOWN_BUNDLE_START;
 extern NSString* const KEY_PAGE_STAGES_DOWN_BUNDLE_END;
 extern NSString* const KEY_PAGE_STAGES_DOWN_JS_START;
@@ -63,6 +64,10 @@
 extern NSString* const KEY_PAGE_STAGES_NEW_FSRENDER;
 extern NSString* const KEY_PAGE_STAGES_INTERACTION;
 extern NSString* const KEY_PAGE_STAGES_DESTROY;
+extern NSString* const KEY_PAGE_STAGES_CREATE_INSTANCE_START;
+extern NSString* const KEY_PAGE_STAGES_CREATE_INSTANCE_END;
+extern NSString* const KEY_PAGE_UNICORN_ENGINE_INIT_START;
+extern NSString* const KEY_PAGE_UNICORN_ENGINE_INIT_END;
 
 ///************** stats *****************/
 extern NSString* const KEY_PAGE_STATS_BUNDLE_SIZE;
diff --git a/ios/sdk/WeexSDK/Sources/Performance/WXApmForInstance.m b/ios/sdk/WeexSDK/Sources/Performance/WXApmForInstance.m
index 712afd0..a529a2f 100644
--- a/ios/sdk/WeexSDK/Sources/Performance/WXApmForInstance.m
+++ b/ios/sdk/WeexSDK/Sources/Performance/WXApmForInstance.m
@@ -30,6 +30,7 @@
 #import "WXExceptionUtils.h"
 #import "WXSDKInstance_performance.h"
 #import "WXAnalyzerCenter+Transfer.h"
+#import "WXSDKInstance_private.h"
 
 
 #pragma mark - const static string
@@ -57,6 +58,7 @@
 
 ///************** stages *****************/
 NSString* const KEY_PAGE_STAGES_START = @"wxRecordStart";
+NSString* const KEY_PAGE_STAGES_CONTAINER_READY = @"wxContainerReady";
 NSString* const KEY_PAGE_STAGES_DOWN_BUNDLE_START  = @"wxStartDownLoadBundle";
 NSString* const KEY_PAGE_STAGES_DOWN_BUNDLE_END  = @"wxEndDownLoadBundle";
 NSString* const KEY_PAGE_STAGES_DOWN_JS_START  = @"wxStartDownLoadJS";
@@ -73,7 +75,12 @@
 NSString* const KEY_PAGE_STAGES_FSRENDER  = @"wxFsRender";
 NSString* const KEY_PAGE_STAGES_NEW_FSRENDER = @"wxNewFsRender";
 NSString* const KEY_PAGE_STAGES_INTERACTION  = @"wxInteraction";
+NSString* const KEY_PAGE_STAGES_INTERACTION_TM  = @"wxInteractionTimeStamp";
 NSString* const KEY_PAGE_STAGES_DESTROY  = @"wxDestroy";
+NSString* const KEY_PAGE_STAGES_CREATE_INSTANCE_START  = @"wxCreateInstanceStart";
+NSString* const KEY_PAGE_STAGES_CREATE_INSTANCE_END  = @"wxCreateInstanceEnd";
+NSString* const KEY_PAGE_UNICORN_ENGINE_INIT_START  = @"wxUnicornEngineInitStart";
+NSString* const KEY_PAGE_UNICORN_ENGINE_INIT_END  = @"wxUnicornEngineInitEnd";
 
 ///************** stats *****************/
 NSString* const KEY_PAGE_STATS_BUNDLE_SIZE  = @"wxBundleSize";
@@ -346,6 +353,7 @@
     if (nil != _apmProtocolInstance) {
         [self.apmProtocolInstance onStart:instanceId topic:WEEX_PAGE_TOPIC];
     }
+    [self onStage:KEY_PAGE_STAGES_CONTAINER_READY];
     [self onStage:KEY_PAGE_STAGES_START];
     WXSDKInstance* instance = [WXSDKManager instanceForID:instanceId];
     if (nil == instance) {
@@ -379,11 +387,23 @@
     if (_isEnd) {
         return;
     }
-    _isEnd = YES;
+    WXSDKInstance* instance = [WXSDKManager instanceForID:self.instanceId];
     [self onStage:KEY_PAGE_STAGES_DESTROY];
+    if (instance.unicornRender) {
+        [self onStageWithTime:KEY_PAGE_STAGES_INTERACTION_TM time:[instance.unicornRender getFirstScreenTimeStamp]];
+        [self onStageWithTime:KEY_PAGE_STAGES_INTERACTION time:[instance.unicornRender getFirstScreenTimeInterval] + [WXUtility getIntervalTime]];
+
+        NSString* timeLine = [instance.unicornRender getEngineTimeline];
+        NSDictionary* timeLineDic = [WXUtility objectFromJSON:timeLine];
+        for (NSString* key in timeLineDic) {
+            long time = [[timeLineDic objectForKey:key] longLongValue] + [WXUtility getIntervalTime];
+            [self onStageWithTime:[@"wxUni" stringByAppendingString:key] time:time];
+        }
+    }
     if (nil != _apmProtocolInstance) {
          [self.apmProtocolInstance onEnd];
     }
+    _isEnd = YES;
     
     WXPerformBlockOnComponentThread(^{
         WXLogInfo(@"APM data of instance: %@, %@", self.instanceId, self.recordStageMap);
diff --git a/ios/sdk/WeexSDK/Sources/Protocol/WXUnicornRenderProtocol.h b/ios/sdk/WeexSDK/Sources/Protocol/WXUnicornRenderProtocol.h
new file mode 100644
index 0000000..f5c97ed
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Protocol/WXUnicornRenderProtocol.h
@@ -0,0 +1,63 @@
+/*
+ * 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 <Foundation/Foundation.h>
+
+
+#ifdef __cplusplus
+#include <JavaScriptCore/JavaScriptCore.h>
+#include <string>
+#endif
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol WXUnicornRenderProtocol <NSObject>
+
+@property (nonatomic, assign) CGRect frame;
+
+@property (nonatomic, strong) UIView *rootView;
+
+@property(nonatomic, copy) NSString * instanceId;
+
+- (instancetype)initWithInstanceId:(NSString *)instanceId;
+
+- (void)startEngine:(UIViewController*)parentViewController;
+
+- (void)shutdown;
+
+- (uint64_t)getFirstScreenTimeStamp;
+- (uint64_t)getFirstScreenTimeInterval;
+
+- (NSString*)getEngineTimeline;
+
+#ifdef __cplusplus
+typedef void(*UnicornRenderFunc)(const std::string& instance_id,
+                                 const std::string& module,
+                                 const std::string& method,
+                                 JSContextRef,
+                                 _Nullable JSValueRef* _Nullable,
+                                 int argc);
++ (UnicornRenderFunc)getRenderFunc;
+#endif
+
++ (void)installUnicornExternalAdapterImageProvider:(id)provider;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXConvert.m b/ios/sdk/WeexSDK/Sources/Utility/WXConvert.m
index 85ab360..b749144 100644
--- a/ios/sdk/WeexSDK/Sources/Utility/WXConvert.m
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXConvert.m
@@ -389,13 +389,22 @@
               unichar t =   [rgba characterAtIndex:3];
               rgba = [NSString stringWithFormat:@"#%C%C%C%C%C%C", f, f, s, s, t, t];
             }
-            
-            // 3. #rrggbb
-            uint32_t colorValue = 0;
-            sscanf(rgba.UTF8String, "#%x", &colorValue);
-            red     = ((colorValue & 0xFF0000) >> 16) / 255.0;
-            green   = ((colorValue & 0x00FF00) >> 8) / 255.0;
-            blue    = (colorValue & 0x0000FF) / 255.0;
+            // 2. #aarrggbb
+            if ([rgba length] == 9) {
+                uint32_t colorValue = 0;
+                sscanf(rgba.UTF8String, "#%x", &colorValue);
+                alpha = ((colorValue & 0xFF000000) >> 24) / 255.0;
+                red = ((colorValue & 0x00FF0000) >> 16) / 255.0;
+                green = ((colorValue & 0x0000FF00) >> 8) / 255.0;
+                blue = (colorValue & 0x000000FF) / 255.0;
+            } else {
+                // 3. #rrggbb
+                uint32_t colorValue = 0;
+                sscanf(rgba.UTF8String, "#%x", &colorValue);
+                red     = ((colorValue & 0xFF0000) >> 16) / 255.0;
+                green   = ((colorValue & 0x00FF00) >> 8) / 255.0;
+                blue    = (colorValue & 0x0000FF) / 255.0;
+            }
         } else if ([rgba hasPrefix:@"rgb("]) {
             // 4. rgb(r,g,b)
             int r,g,b;
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXLegacyAdapter.h b/ios/sdk/WeexSDK/Sources/Utility/WXLegacyAdapter.h
new file mode 100644
index 0000000..6c50ff0
--- /dev/null
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXLegacyAdapter.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+#ifndef WXLegacyAdapter_h
+#define WXLegacyAdapter_h
+
+API_AVAILABLE(ios(13.4))
+@interface UIDatePicker (LegacyXcode)
+
+#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 130400)
+@property (nonatomic, readwrite, assign) UIDatePickerStyle preferredDatePickerStyle;
+#endif
+
+@end
+
+
+#endif /* WXLegacyAdapter_h */
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
index 0112322..66d6ad8 100644
--- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.h
@@ -540,6 +540,8 @@
 
 + (long) getUnixFixTimeMillis;
 
++ (long)getIntervalTime;
+
 + (NSArray<NSString *> *_Nullable)extractPropertyNamesOfJSValueObject:(JSValue *_Nullable)jsvalue;
 
 @end
diff --git a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
index ebe1a8f..6caf13e 100644
--- a/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
+++ b/ios/sdk/WeexSDK/Sources/Utility/WXUtility.m
@@ -46,6 +46,7 @@
 static BOOL enableRTLLayoutDirection = YES;
 static BOOL isDarkSchemeSupportEnabled = YES;
 static BOOL enableAdaptiveLayout = NO;
+static long intervalTime = 0;
 
 void WXPerformBlockOnMainThread(void (^ _Nonnull block)(void))
 {
@@ -1115,10 +1116,15 @@
     
     dispatch_once(&unixTimeToken, ^{
         sInterval = [[NSDate date] timeIntervalSince1970] * 1000 - CACurrentMediaTime()*1000;
+        intervalTime = sInterval;
     });
     return sInterval+CACurrentMediaTime()*1000;
 }
 
++ (long)getIntervalTime {
+    return intervalTime;
+}
+
 + (NSArray<NSString *> *)extractPropertyNamesOfJSValueObject:(JSValue *)jsvalue
 {
     if (!jsvalue) {
diff --git a/ios/sdk/WeexSDK/Sources/WeexSDK.h b/ios/sdk/WeexSDK/Sources/WeexSDK.h
index 372078a..e992aeb 100644
--- a/ios/sdk/WeexSDK/Sources/WeexSDK.h
+++ b/ios/sdk/WeexSDK/Sources/WeexSDK.h
@@ -33,6 +33,8 @@
 #import <WeexSDK/WXView.h>
 #import <WeexSDK/WXValidateProtocol.h>
 #import <WeexSDK/WXUtility.h>
+#import <WeexSDK/WXUnicornRenderProtocol.h>
+#import <WeexSDK/WXUnicornEventListenerHandler.h>
 #import <WeexSDK/WXURLRewriteProtocol.h>
 #import <WeexSDK/WXType.h>
 #import <WeexSDK/WXStreamModule.h>
diff --git a/weex_core/Source/core/render/page/reactor_page.cpp b/weex_core/Source/core/render/page/reactor_page.cpp
index 081e9b33..7e62e80 100644
--- a/weex_core/Source/core/render/page/reactor_page.cpp
+++ b/weex_core/Source/core/render/page/reactor_page.cpp
@@ -190,6 +190,11 @@
                                            options_length);
 }
 
+void ReactorPage::ReportJSException(const std::string& message){
+    WeexCoreManager::Instance()->getPlatformBridge()
+            ->platform_side()->ReportException(page_id_.c_str(), "ReactorException", message.c_str());
+}
+
 RenderObject* ReactorPage::CreateRenderObject(const std::string& ref,
                                               const std::string& type,
                                               unsigned index,
@@ -224,5 +229,7 @@
     return render_object;
 }
 
+
+
 }  // namespace WeexCore
 
diff --git a/weex_core/Source/core/render/page/reactor_page.h b/weex_core/Source/core/render/page/reactor_page.h
index dd32e8a..f6d10b3 100644
--- a/weex_core/Source/core/render/page/reactor_page.h
+++ b/weex_core/Source/core/render/page/reactor_page.h
@@ -73,6 +73,7 @@
                              const std::string& options,
                              int options_length);
 
+    void ReportJSException(const std::string& message);
 private:
     RenderObject* CreateRenderObject(const std::string& ref, const std::string& type, unsigned index, const std::map<std::string, std::string>& styles, const std::map<std::string, std::string>& attrs, const std::vector<std::string>& events, bool reserve_styles, WeexCore::RenderObject* parent);