注意:Weex 所有暴露给 JS 的内置 module 或 component API 都是安全和可控的,它们不会去访问系统的私有 API,也不会去做任何 runtime 上的 hack 更不会去改变应用原有的功能定位。如果需要扩展自定义的 module 或者 component ,一定注意不要将 OC 的 runtime 暴露给 JS ,不要将一些诸如 dlopen()dlsym()respondsToSelector:performSelector:method_exchangeImplementations() 的动态和不可控的方法暴露给JS,也不要将系统的私有API暴露给JS。否则将可能面临苹果上架审核问题。

在这篇文档里,我们使用 Objective-C 来扩展 Weex 功能,如果想使用 Swift,可以参考 使用 Swift 扩展

JSEnv 扩展

接口

  1. NSDictionary *options = @{@"testVlaue":@"hello"};
  2. [_instance renderWithURL:url options:options data:nil];

使用

  1. var value = weex.config.testValue;
  2. console.log(value);

自定义 module

自定义 module,需要让自己的 class 遵循 WXModuleProtocol 这个protocol,通过 WX_EXPORT_METHOD 这个宏暴露出需要透出到 JavaScript 调用的方法,再向 WeexSDK 注册 module,就可以完成一个简单 module 的自定义。

  • 新建一个基类为 NSObject 的 class WXCustomEventModule,让该类遵循 WXModuleProtocol 的协议。扩展iOS能力 - 图1
  • 添加打印的方法,通过 WX_EXPORT_METHOD 暴露该方法。扩展iOS能力 - 图2
  • 在初始化完成 Weex SDK 之后注册该 module。扩展iOS能力 - 图3到此,我们已经完成了一个简单的 module 方法的封装,在 JS 里使用方法如下:
  1. weex.requireModule("event").showParams("hello Weex)

Module 进阶

关于 Module 和 Module 方法的执行特性(同步、异步;执行线程),需要了解:

  • weexInstance在一个 Weex 页面中,默认 WXSDKInstance 的实例持有多个 module 的实例, 而 Module 的实例是是没有对 WXSDKInstance 做持有的,在自定义的 module 中添加 @synthesize weexInstance,module 实例可以对持有它本身的 WXSDKInstance 实例做一个弱引用,通过 weexInstance 可以拿到调用该 module 的页面的一些信息。
  • targetExecuteThreadModule 方法默认会在 UI 线程(iOS 主线程)中被调用,建议不要在这做太多耗时的任务。如果你的任务不需要在 UI 线程执行或需要在特定线程执行,需要实现 WXModuleProtocol 中的 - (NSThread *) 的方法,并返回你希望方法执行所在的线程。
  • WXModuleKeepAliveCallbackModule 支持返回值给 JavaScript 中的回调,回调的类型是 WXModuleKeepAliveCallback。回调的参数可以是 String 或者 Map。该 block 第一个参数为回调给 JavaScript 的数据,第二参数是一个 BOOL 值,表示该回调执行完成之后是否要被清除。JavaScript 每次调用都会产生一个回调,但是对于单独一次调用,是否要在完成该调用之后清除掉回调函数 id 就由这个选项控制,如非特殊场景,建议传 NO。
  • WX_EXPORT_METHOD_SYNC使用 WX_EXPORT_METHOD 暴露到前端的方法都是异步方法(获得结果需要通过回调函数获得)。如果期望获得同步调用结果,可以使用 WX_EXPORT_METHOD_SYNC 声明同步的 Module 方法。

扩展组件

  • 新建一个基类为 WXComponent 的类。如果这个类里什么代码也不写,它和默认的的 div 组件能力是一致的。
  • 覆盖 WXComponent 中的生命周期方法
  • loadView一个组件默认对应于一个 view,如果未覆盖 loadView 提供自定义 view,会让 WXComponent 基类创建 WXViewWXView 继承自 UIView。比如我们要实现一个组件支持地图功能,我们可以返回系统的 MKMapView
  1. - (UIView *)loadView {
  2. return [MKMapView new];
  3. }
  • viewDidLoad对组件 view 需要做一些配置,比如设置 delegate,可以在 viewDidLoad 生命周期做。如果当前 view 没有添加 subview 的话,不要设置 view 的 frame,WeexSDK 会根据 style 进行排版后设置。
  1. - (void)viewDidLoad {
  2. ((MKMapView*)self.view).delegate = self;
  3. }
  • 注册组件
  1. [WXSDKEngine registerComponent:@"map" withClass:[WXMapComponent class]];
  • 在前端代码中使用新组件
  1. <template>
  2. <div>
  3. <map style="width:200px;height:200px"></map>
  4. </div>
  5. </template>

自定义事件

  • 对于每个组件,WeexSDK 默认提供了一些事件能力,如点击等。假如想给我们的地图组件提供 mapLoaded 事件。
  1. <template>
  2. <div>
  3. <map style="width:200px;height:200px" @mapLoaded="onMapLoaded"></map>
  4. </div>
  5. </template>
  6. <script>
  7. export default {
  8. methods: {
  9. onMapLoaded:function(e) {
  10. console.log("map loaded"+JSON.stringify(e))
  11. }
  12. }
  13. }
  14. </script>
  • 覆盖组件生命周期方法,记录事件是否需要处理我们需要额外添加一个 BOOL 成员 mapLoaded 用来记录该事件是否生效。
  1. - (void)addEvent:(NSString *)eventName {
  2. if ([eventName isEqualToString:@"mapLoaded"]) {
  3. _mapLoaded = YES;
  4. }
  5. }
  6. - (void)removeEvent:(NSString *)eventName {
  7. if ([eventName isEqualToString:@"mapLoaded"]) {
  8. _mapLoaded = NO;
  9. }
  10. }
  • 给前端发送事件
  1. - (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView {
  2. if (_mapLoaded) {
  3. [self fireEvent:@"mapLoaded" params:@{@"customKey":@"customValue"} domChanges:nil];
  4. }
  5. }

自定义属性

给我们的地图组件添加一个新的属性 showTraffic。在前端代码里可以控制组件是否显示车流情况。

  1. <template>
  2. <div>
  3. <map style="width:200px;height:200px" showTraffic="true"></map>
  4. </div>
  5. </template>
  • 覆盖组件初始化方法 initWithRef…给组件添加一个成员变量记录 showTraffic 属性的值,并在 init 方法中初始化。
  1. - (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance {
  2. if(self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
  3. if (attributes[@"showsTraffic"]) {
  4. _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];
  5. }
  6. }
  7. return self;
  8. }
  • 在生命期事件中记得将属性值同步给地图控件
  1. - (void)viewDidLoad {
  2. ((MKMapView*)self.view).showsTraffic = _showsTraffic;
  3. }
  • 当属性更新时,同步给地图控件
  1. - (void)updateAttributes:(NSDictionary *)attributes {
  2. if (attributes[@"showsTraffic"]) {
  3. _showsTraffic = [WXConvert BOOL: attributes[@"showsTraffic"]];
  4. ((MKMapView*)self.view).showsTraffic = _showsTraffic;
  5. }
  6. }

更多的组件生命期方法

组件是由 Weex 管理的,比如创建、布局、渲染、销毁。Weex 组件的生命周期方法都是可以重写的,你可以在这些生命周期中去做自己的事情。

方法描述
initWithRef:type:…用给定的属性初始化一个component.
layoutDidFinish在component完成布局时候会调用.
loadView创建component管理的view.
viewWillLoad在component的view加载之前会调用.
viewDidLoad在component的view加载完之后调用.
viewWillUnload在component的view被释放之前调用.
viewDidUnload在component的view被释放之后调用.
updateStyles:在component的style更新时候调用.
updateAttributes:在component的attribute更新时候调用.
addEvent:给component添加event的时候调用.
removeEvent:在event移除的时候调用.

给组件添加方法

在组件代码中使用宏 WX_EXPORT_METHOD 声明组件方法供前端调用。

  1. @implementation WXMyComponent
  2. WX_EXPORT_METHOD(@selector(focus))
  3. - (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance {
  4. if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
  5. // handle your attributes
  6. // handle your styles
  7. }
  8. return self;
  9. }
  10. - (void)focus {
  11. NSLog(@"you got it");
  12. }
  13. @end

在 JS 中调用 focus 方法。

  1. <template>
  2. <mycomponent ref='mycomponent'></mycomponent>
  3. </template>
  4. <script>
  5. module.exports = {
  6. created: function() {
  7. this.$refs.mycomponent.focus();
  8. }
  9. }
  10. </script>

获取组件的 CSS 样式

  • 在 0.19 版本之前,Weex 使用 Yoga 排版引擎,可以通过访问 WXComponent 的 cssNode 属性获取。例如:
  1. self.cssNode->style.flex = 1.0;
  2. float height = self.cssNode->style.dimensions[CSS_HEIGHT];
  • 从 0.19 版本开始,Weex 使用自已的排版引擎,是 C++ 代码。获取 CSS 属性可以从 WXComponent 的 styles 字典(NSDictionary)里获取,也可以访问 WXComponent 的 flexCssNode 属性,其类型为 C++ 类 WeexCore::WXCoreLayoutNode。注意,需要源文件为 .mm 类型。

  • 从 0.20 版本开始,iOS 接入 WeexCore,排版引擎也沉入 WeexCore层,并且 CSS 相关属性不再上传给 WXComponent 对象,WXComponent 对象的 styles 字典里只有非 CSS 样式。我们的目的是上层 UI 组件只需要关心排版引擎生成的最终坐标即可,不需要关心前端标记的 CSS 属性。如果仍然需要获取 CSS 样式,可以在 .mm 文件中直接访问 flexCssNode,或通过 WXComponent+Layout.h 文件中提供的扩展方法。

  1. /**
  2. * @abstract Get css style value for key. The key should be of CSS standard form.
  3. * This method is for convenience use in C/ObjC environment. And if you want to
  4. * retrieve all style values or in C++, you could use flexCssNode directly.
  5. *
  6. * Thread usage:
  7. * This method should be invoked in component thread by WXPerformBlockOnComponentThread.
  8. * Note that all initWithRef methods of WXComponent and its subclasses are performed in
  9. * component thread by default. Therefore you can call this method directly in initWithRef.
  10. *
  11. * Supported keys:
  12. * width, height, min-width, min-height, max-width, max-height,
  13. * margin-(left/right/top/bottom)
  14. * padding-(left/right/top/bottom)
  15. * border-(left/right/top/bottom)-width
  16. * left, right, top, bottom
  17. * flex-grow
  18. */
  19. - (float)getCssStyleValueForKey:(NSString *)key;
  20. // Other methods which should also be used in component thread.
  21. - (WXCoreFlexDirection)getCssStyleFlexDirection;
  22. - (WXCoreFlexWrap)getCssStyleFlexWrap;
  23. - (WXCoreJustifyContent)getCssStyleJustifyContent;
  24. - (WXCoreAlignItems)getCssStyleAlignItems;
  25. - (WXCoreAlignSelf)getCssStyleAlignSelf;
  26. - (WXCorePositionType)getCssStylePositionType;
  27. - (WXCoreDirection)getCssDirection;

自定义 handler

Handler 的作用是什么?比如 WeexSDK 目前没有提供图片下载的能力,而是在 WXImgLoaderProtocol 定义了一些获取图片的接口。Image 组件正是通过 WXImgLoaderProtocol 获得并展示图片,开发者可以实现该 protocol 中的接口方法,这样 image 标签才能正常展示图片。

开发者也可以定义自己的 protocol 和对应的实现来使用 handler 机制。常见的一个场景就是在每个 App 中需要特定的 Handler 来实现一些公共的能力。

  • 新建基类为 NSObject 的类,并实现 WXImgLoaderProtocol 协议,实现 WXImgLoaderProtocol 的方法。下面加载图片的逻辑需要依赖 SDWebImage,你也可以不依赖 SDWebimage 使用自己的方式加载对应 URL 图片。
  1. @implementation WXImgLoaderDefaultImpl
  2. - (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)userInfo completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock {
  3. if ([url hasPrefix:@"//"]) {
  4. url = [@"http:" stringByAppendingString:url];
  5. }
  6. return (id<WXImageOperationProtocol>)[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
  7. } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
  8. if (completedBlock) {
  9. completedBlock(image, error, finished);
  10. }
  11. }];
  12. }
  13. @end
  • 注册
  1. [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)];
  • 使用
  1. id<WXImgLoaderProtocol> imageLoader = [WXSDKEngine handlerForProtocol:@protocol(WXImgLoaderProtocol)];
  2. [iamgeLoader downloadImageWithURL:imageURl imageFrame:frame userInfo:customParam completed:^(UIImage *image, NSError *error, BOOL finished) {
  3. }];