EMAS provides WindVane to embed H5 applications in your native application. This topic describes how to integrate WindVane on iOS.
Integrate the SDK
In your Podfile, specify the repository, add the required dependencies, and run the
pod installorpod updatecommand to integrate the SDK into your project.source 'https://github.com/CocoaPods/Specs.git' source 'https://github.com/aliyun/aliyun-specs.git' pod 'WindVane', '11.2.2.1' pod 'ZipArchive', '1.4.0' pod 'NetworkSDK', '10.0.4.6'Add system library dependencies
In your project, go to Build Phases > Link Binary With Libraries and add the following libraries.
AssetsLibrary.framework AVFoundation.framework MobileCoreServices.framework AudioToolbox.framework Photos.framework WebKit.frameworkInitialize the SDK using the following configuration.
#import <WindVane/WindVane.h> @interface EMASWindVaneConfig : NSObject + (void)setUpWindVanePlugin; @end @implementation EMASWindVaneConfig + (void)setUpWindVanePlugin { // Set the APPKey. If you use the security black box, the key from the black box is used. // [WVUserConfig setAppKey:@"Your appKey"secrect:@"Your secretKey"]; [WVUserConfig setAppKey:@"Your appKey"]; // Set the environment. // [WVUserConfigsetEnvironment:WVEnvironmentDaily]; [WVUserConfig setEnvironment:WVEnvironmentRelease]; // Set the TTID. // [WVUserConfig setTTid:@"windvane@****"]; // Set the UA. [WVUserConfig setAppUA:[NSString stringWithFormat:@"TBIOS"]]; // Set the app name. This appears in the UserAgent. Make sure to set it correctly. [WVUserConfig setAppName:@"EMASDemo"]; // Set the app version. NSDictionary *infoDictionary =[[NSBundle mainBundle] infoDictionary]; [WVUserConfig setAppVersion:[infoDictionary objectForKey:@"CFBundleShortVersionString"]]; // Enable NSURLProtocol support for WKWebView. [WVURLProtocolService setSupportWKURLProtocol:YES]; #ifdef DEBUG [WVUserConfig setDebugMode:YES]; // Enable WindVane logs. [WVUserConfig openWindVaneLog]; [WVUserConfig setLogLevel:WVLogLevelVerbose]; [WVBasic setJSLogLevel:WVLogLevelVerbose]; #endif // Initialize WindVane modules. [WVBasic setup]; [WVAPI setup]; [WVMonitor startMonitoring]; } @endIn the
AppDelegate.mfile, call the following code in theapplication:didFinishLaunchingWithOptionsmethod to initialize the SDK.- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [EMASWindVaneConfig setUpWindVanePlugin]; }
Use WVWKWebView
We recommend that you use WKWebView only on iOS 9 or later because earlier versions have many known issues.
WVWKWebView is based on WKWebView, which was introduced in iOS 9.0. It uses the WebKit engine for rendering, which provides higher rendering and JavaScript execution efficiency and supports more HTML5 features. Native developers can use WVWKWebView to display pages as required.
WindVane for iOS provides full support for WVWKWebView. After you enable NSURLProtocol interception by calling [WVURLProtocolService setSupportWKURLProtocol:YES], preloading functions normally. Persistent cookies that have an `expires` or `max-age` attribute are also synchronized correctly. However, POST request bodies are not preserved, and non-persistent cookies are not synchronized. Currently, only synchronous XMLHTTPRequest POST requests that use Blob or FormData are unsupported.
Monitor page information
WindVane provides basic page information. You can use Key-Value Observing (KVO) to monitor changes to the WebView's `title` and `estimatedProgress` properties to receive real-time updates on the page title and loading progress. You can then decide how to display this information in your UI.
// Add KVO observers. Remove them before the WebView is destroyed.
[_webView addObserver:self forKeyPath:@"title" options:NSKeyValueObservingOptionNew context:nil];
[_webView addObserver:self forKeyPath:@"estimatedProgress" options:NSKeyValueObservingOptionNew context:nil];
// Handle KVO callbacks.
- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
if ([keyPath isEqual:@"title"]) {
// change[NSKeyValueChangeNewKey] contains the new page title.
} else if ([keyPath isEqual:@"estimatedProgress"]){
// [change[NSKeyValueChangeNewKey] doubleValue] is the new progress value (0.0–1.0).
// Note: Progress updates may occur off the main thread. Switch to the main thread for UI updates.
}
}
- (void)dealloc {
// Remove KVO observers. This is just an example—adjust based on your implementation.
[_webView removeObserver:self forKeyPath:@"title"];
[_webView removeObserver:self forKeyPath:@"estimatedProgress"];
}Integrate the grayscale SDK
Integrate the grayscale SDK.
pod 'DynamicConfiguration', '~> 11.0.0' pod 'DynamicConfigurationAdaptor', '~> 1.0.0'Initialize the grayscale SDK.
// Initialize grayscale. - (void)initDyConfig { NSString *appKey = @"xxxxx"; NSString *logicIdentifier = [NSString stringWithFormat:@"%@@%@", appKey, [self isDeviceIphone] ? @"iphoneos" : @"iPad"]; [[DynamicConfigurationAdaptorManager sharedInstance] setUpWithMaxUpateTimesPerDay:10 AndIdentifier:logicIdentifier AndAppkey:appKey]; }
Integrate the ZCache SDK
1. Integrate the ZCache SDK.
pod 'ZCache', '~> 11.1.0.0'2. Initialize the ZCache SDK.
- (void)initZCache
{
NSString *appKey = @"xxx";
NSString *appSecret = @"xxx";
NSString *version = @"xxx";
[ZCache setupWithAppKey:appKey appSecret:appSecret appVersion:version];
}(Optional) Use JSBridge
Native providers of JSBridge methods must implement them as regular methods and ensure that they meet the following requirements:
Always return a result. You must call the callback in all cases to return results to JavaScript. You can return different data as required.
Return only one value. You must call the callback exactly once. To return multiple values to an H5 page, Android must use the standard event mechanism. On iOS, you must use the
dispatchEventmethod of the JSBridge context.Release resources properly. On iOS, you must not hold strong references to views or view controllers.
Register JSBridge.
EMAS offers two registration methods: dynamic and static.
Dynamic registration: This method does not require manual registration with WindVane and is simple to use. However, the class name must match the `ClassName` used in the JSBridge call. This method is currently available only for WindVane on iOS.
Static registration: This method requires you to call WindVane's registration method. This method is more complex but offers greater flexibility because it does not require a specific class name.
In general, we recommend that you use dynamic registration if the `ClassName` used by the JSBridge is not already taken.
iOS dynamic JSBridge registration.
WindVane provides a new interface. All JSBridge functionality is unified in
WVBridgeCallbackContext, which supports non-WebView scenarios such as Weex. For more information, see New interface overview.Assume that you need to implement two JSBridges:
MyJSBridge.firstAPIandMyJSBridge.secondAPI. The ClassName is MyJSBridge, and the HandlerNames are firstAPI and secondAPI.Create a MyJSBridge class. The class name must match the ClassName used in the JSBridge call. Ensure that no other class has the same name.
Make MyJSBridge inherit from WVDynamicHandler by importing WVDynamicHandler.h.
Implement the firstAPI and secondAPI methods. The method signature must be
-/+(void)handlerName:(NSDictionary *)params withWVBridgeContext:(id<WVBridgeCallbackContext>)context:The method can be a static method or an instance method. Static methods are called directly. For instance methods, a new instance is created before the method is invoked.
The method name must match the HandlerName used in the JSBridge call. In this example, the method names are firstAPI and secondAPI.
The first parameter is an `NSDictionary*` that contains the parameters passed from JavaScript.
The second parameter is an `id<WVBridgeCallbackContext>` that represents the JSBridge call context.
Implement your JSBridge logic. You can use `[context callbackSuccess:RESULT]` or
[context callbackFailure:RET withResult:RESULT]to return success or failure results to the caller. You must always call one, and only one,[context callbackXXX]method.NoteJSBridge calls are always made on the main thread. For time-consuming operations, you must switch threads manually.
If your service produces multi-stage outputs, you can use
[context dispatchEvent:eventName withParam:param]to send results as events.After your JSBridge logic is complete, call
[context releaseHandler:self]to immediately release memory. Otherwise, memory is released only when the page is destroyed. Calling this method in `dealloc` has no effect because the object is already deallocated.
#import <Foundation/Foundation.h> #import <WindVane/WindVane.h> @interface MyJSBridge : WVDynamicHandler @end @implementation MyJSBridge // This is a static method, called directly. + (void)firstAPI:(NSDictionary *)params withWVBridgeContext:(id<WVBridgeCallbackContext>)context{ // Handle business logic in the callback. [context callbackSuccess:nil]; } // This is an instance method. A new instance is created before calling. // API instance - (void)secondAPI:(NSDictionary *)params // Handle business logic in the callback. [context callbackSuccess:nil]; } @endFor dynamically registered JSBridges, class names must be unique. To extend an existing ClassName, you can use a JSBridge alias.
Common errors
If you encounter the following error when you integrate WindVane, add WebKit to your project's Build Phases under Link Binary With Libraries and set its Status to Optional.
[MISSING IMAGE:error message | left | 618x118, error message | left | 618x118 ]
[MISSING IMAGE:WebKit framework | left | 479x80, WebKit framework | left | 479x80 ]If you encounter the following error when you integrate WindVane:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Completion handler passed to -[WVCommonWebView webView:decidePolicyForNavigationAction:decisionHandler:] was not called'You must manually implement the delegate methods for WKNavigationDelegate and call the decisionHandler:
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
decisionHandler(WKNavigationActionPolicyAllow);
}Use iOS WebView
WindVane on iOS supports both WVWebView, which is based on UIWebView, and WVWKWebView, which is based on WKWebView. The features and interfaces for both are compatible. We recommend that you always use
UIView*as the WebView type to ensure compatibility with the core features of both WVWebView and WVWKWebView.You must also import WindVane headers using
#import <WindVane/WindVane.h>. This works for both monolithic and modular WindVane packages. When you upgrade WindVane, you must check for and fix any warnings related to deprecated methods as soon as possible.Use WindVane-provided ViewControllers
You can use WindVane's built-in
WVUIWebViewController, which includes a WebView and encapsulates common WebView interfaces. It supports switching between WVWebView and WVWKWebView, status bar background compatibility, a toolbar, error pages, loading indicators, and landscape orientation.For more information about UI-independent core features, see
WVViewController.h. For more information about UI-related features, seeWVUIWebViewController.h.To switch to
WVWKWebViewinWVUIWebViewController, you can set theuseWKWebViewproperty:WVUseWKWebViewNeveralways usesWVWebView. This is the default value ofuseWKWebView.WVUseWKWebViewAlwaysforces the use ofWVWKWebView.The return value of the
WVUseWKWebViewCustomdecideIsUseWKWebViewmethod determines whether to useWVWKWebVieworWVWebView. Because this decision must be made before theWebViewis initialized,WVUIWebViewControllerby default checks whetherloadUrlcontains the_wvUseWKWebView=YESparameter. If the parameter is present, it switches toWVWKWebView.#import <WindVane/WindVane.h> WVUIWebViewController * controller =[[WVUIWebViewController alloc] init]; controller.loadUrl = @"http://m.taobao.com"; // Enable JSBridge. Call this if you plan to use JSBridge. controller.openLocalService = YES; // [iOS 7 compatibility] Add a white background to the status bar when the navigation bar is hidden. Set as needed. [controller supportiOS7WithoutStatusBar]; // Disable WebView long-press events. Set as needed. // controller.openWebKitLongPress = NO; [self.navigationController pushViewController:controller animated:YES];
WindVane feature overview
WindVaneCore
The WindVane core library defines inter-module interfaces, constraints, and utility classes. All modules depend on this library.
WindVaneBridge
The WindVane JSBridge library provides JSBridge functionality. You can use it within your own WebView or independently of any WebView.
WindVaneBasic
The WindVane basic WebView library enhances and extends WebView functionality. It includes modules for WebView, WKWebView, ViewController, and StandardEventModal.
WindVaneAPI
This library provides built-in JSBridge APIs from WindVane. You must add the following system libraries: CoreBluetooth.framework, AddressBookUI.framework, AddressBook.framework, MessageUI.framework, Messages.framework, ContactsUI.framework, Contacts.framework, and UserNotifications.framework.
WindVaneMonitor
The WindVane instrumentation support library provides unit tests (UTs) for instrumentation.
WindVaneEMASExtension
The EMAS extension library primarily handles URL interception for preloading configurations and crash reporting. Preloading allows frontend developers to package HTML, JS, and CSS resources into a ZIP file on the EMAS platform. Clients can download the ZIP file after launch or pre-bundle it, and then extract it locally. This avoids re-downloading resources during browsing, which improves the user experience.
Custom lifecycle
For instance methods, a new instance is created by default on each call. The instance is strongly referenced to prevent premature deallocation and is released only when the WebView is destroyed. You can customize the instance lifecycle as follows:
@implementation MyJSBridge
+ (WVBridgeScope)instanceScope {
return WVBridgeScopeInvocation;
}
@endWindVane supports four lifecycles: one instance per call (WVBridgeScopeInvocation), one instance per page (WVBridgeScopePage), one instance per view (WVBridgeScopeView), and a single global instance (WVBridgeScopeStatic).
For ScopeInvocation and ScopePage, the instance remains referenced until the page changes, for example, when the WebView navigates to a new page or goes back in history. For ScopeView, the reference lasts until the view is destroyed. To save memory, you can call `[context releaseHandler:self]` as soon as the instance can be safely released. ScopeStatic instances persist indefinitely but can also be released manually using [context releaseHandler:self].
Interact with views or view controllers
JSBridge can interact with views, including WebViews, or view controllers to implement special logic. You can also check whether the view controller is of a specific type to restrict certain private JSBridges to specific view controllers.
You must not hold strong references to views or view controllers to avoid circular references and memory leaks. The context provides `view` and `viewController` properties for instance methods. In WebView environments, it provides `webViewEnv`. In Weex scenarios, it provides `weexEnv`. These properties are automatically set and updated, and you can use them directly.
Send events
To send events from JSBridge to the container, you can use `[context dispatchEvent:withParam:]`. This method adapts automatically to the current environment, such as WebView or Weex.
Note: The event mechanism supports only WebView environments.
Advanced JSBridge lifecycle management
For long-running JSBridges, such as audio playback or motion sensing that continuously send events, WindVane provides lifecycle callbacks for handling transitions, such as navigating to another native page or another H5 page.
@implementation MyJSBridge /** * Reset handler – Called when loading a new page. Use this to perform cleanup. * * @param context The context when the WVBridge is reset. * @param request The new page to load. */ -(void)resetWithContext:(id<WVBridgeContext>)context withNextRequest:(NSURLRequest *)request { } /** * Pause handler – Called when the UIViewController's viewWillDisappear is triggered. * Reduces performance overhead (e.g., pauses music playback or continuous listeners). * Not called for WVBridgeScopeStatic. * * @param context The context when the WVBridge is paused. */ -(void)pauseWithContext:(id<WVBridgeContext>)context { } /** * Resume handler – Called when the UIViewController's viewWillAppear is triggered. * Restores performance (e.g., resumes music playback or continuous listeners). * Not called for WVBridgeScopeStatic. * * @param context The context when the WVBridge is resumed. */ -(void)resumeWithContext:(id<WVBridgeContext>)context { } @endYou can implement
resetWithContext:withNextRequest:if your JSBridge requires automatic cleanup when switching to a new page. You can implementpauseWithContext:andresumeWithContext:to pause and resume when the currentUIViewControlleris covered by another native screen. We generally recommend that you use these methods together with WVBridgeScopeView.To handle special tasks when the application moves to the foreground or background, you can register for
UIApplicationDidBecomeActiveNotificationandUIApplicationWillResignActiveNotification.New interface overview
The new JSBridge interface consolidates all functionality into a unified `id<WVBridgeCallbackContext>`. Everything is accessible through the context:
Obtain environment context: Basic information includes the referrer URL, view, viewController, and current environment (`env UIView<WVWebViewBasicProtocol> *`,
WeexSDKInstance *).Send events using
dispatchEvent:withParam:.Release instances using
releaseHandler:.Return results using
callbackSuccess:andcallbackFailure:withResult:. Shortcut methods are also provided for common failure cases.All JSBridge method parameters are standardized as
NSDictionary* paramsandid<WVBridgeCallbackContext> context, regardless of whether dynamic or static registration is used.
Support for lazy-loaded dynamic libraries
Some clients use lazy loading for dynamic libraries to improve application launch speed. This delays library loading until the libraries are first used, which avoids startup delays.
If a dynamic library provides dynamically registered JSBridges, the corresponding classes will not be found before the library loads, which prevents them from being called normally. To address this issue, WindVane supports lazy-loaded dynamic libraries by allowing them to declare JSBridge class names in a plist file. WindVane loads the library automatically when the JSBridge is needed.
The solution is to place a file named XXX_bundle.plist in the root directory of the dynamic library's framework, where XXX can be any name. A single framework can contain multiple such plists. Add an array named `windvane_bridge_list` to the plist and include the class names of the dynamic JSBridges. Configurations from multiple plists are merged automatically. The following is an example:
[MISSING IMAGE:Dynamic Framework Config | left | 600x374, Dynamic Framework Config | left |600x374 ]Ensure that all dynamic libraries are in the client's Frameworks directory. If mapping conflicts occur between libraries, WindVane logs a message, such as "WVBridge 'XXX' registered by 'A' was overridden by 'B'", when the JSBridge is first used. In DEBUG mode, an alert is also displayed.
iOS static JSBridge registration
WindVane on iOS lets you register JSBridges globally or for a specific WebView. Global registration enables calls from any WebView. WebView-specific registration restricts usage to that WebView and is suitable for stricter access control. However, even globally registered JSBridges can implement partial access control by validating the view controller's type or properties. Unless strict isolation is required, we recommend that you always register globally to reduce memory usage.
Register JSBridge globally
#import <WindVane/WindVane.h> // JSBridge block. WVBridgeHandler handler = ^(NSDictionary *params, id<WVBridgeCallbackContext> context) { [context callbackSuccess:nil]; }; // Register JSBridge globally. Note the combined format: @"className.handlerName". WVBridgeRegisterHandler(@"className.handlerName", handler);Register JSBridge to a specific WebView
JSBridges registered this way can be called only using `className` and `handlerName` in the registered WebView.
#import <WindVane/WindVane.h> // JSBridge block. WVBridgeHandler handler = ^(NSDictionary *params, id<WVBridgeCallbackContext> context) { [context callbackSuccess:nil]; }; // Register JSBridge to a specific WebView. Note the combined format: @"className.handlerName". [webview registerHandler:@"className.handlerName" withBlock:handler]; // Register JSBridge to the WebView in a specific view controller. Note the combined format: @"className.handlerName". [wvViewController registerHandler:@"className.handlerName" withBlock:handler];Statically registered JSBridges do not support advanced lifecycle management. For complex JSBridges, we recommend that you use dynamic registration.
If two static registrations use the same `className` and `handlerName`, the later one overrides the earlier one. It also overrides any dynamically registered method that has the same name.
JSBridge aliases
WindVane provides JSBridge aliasing to extend or override existing ClassName-based JSBridges and to handle upgrades, renames, or naming conflicts.
You can map an original JSBridge className.handlerName to a new alias aliasClassName.aliasHandlerName. Both the original name and the alias remain accessible.
WindVane resolves aliases to original names before it looks up the JSBridge. Do not chain aliases, for example, alias → alias → original, because this may prevent correct resolution.
iOS global alias registration: Global aliases must be used with globally registered JSBridges.
#import <WindVane/WindVane.h>
// Register global JSBridge aliases.
NSDictionary * alias = @{
@"Alias of the class name.Alias of the handler name": @"Class name.Handler name",
@"aliasClassName.aliasHandlerName":@"className.handlerName"
};
WVBridgeRegisterAlias(alias);Global JSBridge authentication
WindVane provides a global JSBridge authentication interface for app-level API authorization. This layer integrates into the JSBridge call flow, allowing you to define custom authentication logic centrally.
For individual JSBridge authentication, you can implement it within the JSBridge itself.
#import <WindVane/WindVane.h>
// Implement JSBridge authentication.
@interface MyJSBridgeCheckerHandler : NSObject<WVBridgeCheckerProtocol>
@end
@implementation MyJSBridgeCheckerHandler
/**
Check if the specified WVBridge has execution permission.
* Always called on a background thread.
*
* @param apiName WVBridge name in "ClassName.MethodName" format.
* @param params WVBridge call parameters.
* @param context WVBridge execution context.
*
* @return Permission check result.
*/
- (WVBridgePermission*)checkPermission:(NSString *)apiName withParams:(NSDictionary *)params withContext:(id<WVBridgeContext>)context {
return [WVBridgePermission permissionNotSure];
}
@end
// Register the authentication handler.
[WVBridgeChecker registerChecker:[[MyJSBridgeCheckerHandler alloc] init]];
Use JSBridge in native code
WindVane on iOS supports calling JSBridge from native code. Two approaches are available:
Simple usage
For simple, UI-independent scenarios, you can call
[WVBridge callHandler:withParams:withCallback:]with the following parameters:name: The JSBridge name in the `@"ClassName.MethodName"` format.
params: The JSBridge parameters, which must match the JavaScript usage. Use `NSDictionary` for objects and `NSArray` for arrays.
callback: The callback for this JSBridge call. It may be invoked on any thread.
Advanced usage
For full-featured JSBridge integration in your own container:
You can implement the `WVBridgeDelegate` protocol to receive JSBridge callbacks in native code.
You can instantiate your own WVBridge by passing `env` and the delegate that you implemented.
You can set the `view` and `viewController` properties of the WVBridge instance to provide environment context. You can also optionally set the following properties:
opageId: The current page ID. If you change it, pending JSBridge calls from the previous page are invalidated.
oopenPermissionCheck: This enables whitelist validation. Only URLs in WindVane's domain configuration (Ali domains) can call the JSBridge. Other URLs are denied access.
Calling one of the
callHandlerWithRequest:,callHandlerWithURL:, orcallHandler:withParams:withReqId:methods invokes the JSBridge. The delegate returns all callbacks and events from the JSBridge.callHandlerWithRequest:callHandlerWithURL:callHandler:withParams:withReqId:
Standard event mechanism
WindVane's standard event mechanism provides a unified communication interface for sending events between native code and H5 pages. This facilitates consistent event-based communication across business scenarios.
Send events from native code to H5 pages
WindVane provides the `WVStandardEventCenter` class for sending events from native code to H5 pages:
In the event parameters, `webView` is the target WebView, `eventName` is the event name (string), and `eventData` is the event data (`NSDictionary*` on iOS and a JSON Object string on Android). On iOS, we recommend that you do not use this method in JSBridge. Instead, use the `dispatchEvent` method of the JSBridge context for compatibility across containers, such as WebView and Weex.
#import <WindVane-Basic/WVStandardEventCenter.h> // Send event to a specific WebView. [WVStandardEventCenter postNotificationToJS:eventName withEventData:eventData withWebView:webView]; // Send event to all WebViews. Use with caution. [WVStandardEventCenter postNotificationToJS:eventName withEventData:eventData];H5 usage:
document.addEventListener('eventName', function(data) { // Note: Event parameters from native are in data.param. alert(data.param); }, false);Send events from H5 pages to native code
H5 usage: You can use JSBridge to send events. Make sure that you include windvane.js. For more information, see the following example.
WVStandardEventCenter.postNotificationToNative: var params = { event: 'eventName', param: { // Event data to send. } }; window.WindVane.call('WVStandardEventCenter','postNotificationToNative',params, function(e) { alert('success'); }, function(e) { alert('failure: ' + JSON.stringify(e)); });iOS usage: You can listen for events using the standard notification center.
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(myEventListener) name:eventName object:nil] - (void)myEventListener:(NSNotification*)notification { // Event parameters are available in notification.userInfo. }