iOS 扩展

注意

Weex 所有暴露给 JS 的内置 module 或 component API 都是安全和可控的, 它们不会去访问系统的私有 API ,也不会去做任何 runtime 上的 hack 更不会去改变应用原有的功能定位。

如果需要扩展自定义的 module 或者 component ,一定注意不要将 OC 的 runtime 暴露给 JS , 不要将一些诸如 dlopen()dlsym()respondsToSelector:performSelector:method_exchangeImplementations() 的动态和不可控的方法暴露给JS, 也不要将系统的私有API暴露给JS

Module 扩展

swift 扩展 module

Weex SDK 只提供渲染,而不是其他的能力,如果你需要 像网络,图片,URL跳转这些特性,需要自己动手实现他们
例如,如果你想实现一个url地址跳转函数,你可以按照如下步骤实现一个 Module

自定义module的步骤

  1. 自定义的module类 必须实现 WXModuleProtocol
  2. 必须添加宏WX_EXPORT_METHOD, 它可以被weex识别,它的参数是 JavaScript调用 module指定方法的参数
  3. 添加@synthesized weexInstance,每个moudle对象被绑定到一个指定的实例上
  4. Module 方法会在UI线程中被调用,所以不要做太多耗时的任务在这里,如果要在其他线程执行整个module 方法,需要实现WXModuleProtocol- (NSThread *)targetExecuteThread的方法,这样,分发到这个module的任务会在指定的线程中运行
  5. Weex 的参数可以是 String 或者Map
  6. Module 支持返回值给 JavaScript中的回调,回调的类型是WXModuleCallback,回调的参数可以是String或者Map

    1. @implementation WXEventModule
    2. @synthesize weexInstance;
    3. WX_EXPORT_METHOD(@selector(openURL:callback:))
    4. - (void)openURL:(NSString *)url callback:(WXModuleCallback)callback
    5. {
    6. NSString *newURL = url;
    7. if ([url hasPrefix:@"//"]) {
    8. newURL = [NSString stringWithFormat:@"http:%@", url];
    9. } else if (![url hasPrefix:@"http"]) {
    10. newURL = [NSURL URLWithString:url relativeToURL:weexInstance.scriptURL].absoluteString;
    11. }
    12. UIViewController *controller = [[WXDemoViewController alloc] init];
    13. ((WXDemoViewController *)controller).url = [NSURL URLWithString:newURL];
    14. [[weexInstance.viewController navigationController] pushViewController:controller animated:YES];
    15. callback(@{@"result":@"success"});
    16. }
    17. @end

v0.10+" class="reference-link">暴露同步方法v0.10+

如果你想要暴露同步的native方法给JS, 即JS可以直接拿到Native的返回值。 你可以使用WX_EXPORT_METHOD_SYNC 宏。

native 代码:

  1. @implementation WXEventModule
  2. WX_EXPORT_METHOD_SYNC(@selector(getString))
  3. - (NSString *)getString
  4. {
  5. return @"testString";
  6. }
  7. @end

js 代码:

  1. const eventModule = weex.requireModule('event')
  2. const returnString = syncTest.getString() // return "testString"

除了string, 你也可以返回 number/array/dictionary 类型.

注意: 暴露的同步方法只能在 JS 线程执行,请不要做太多同步的工作导致JS执行阻塞。

注意: Vue 2.0 还未支持这个特性,最早会在 0.12 版本支持

注册 module

通过调用 WXSDKEngine 中的 registerModule:withClass方法来注册自己的module

  1. WXSDKEngine.h
  2. /**
  3. * @abstract Registers a module for a given name
  4. * @param name The module name to register
  5. * @param clazz The module class to register
  6. **/
  7. + (void)registerModule:(NSString *)name withClass:(Class)clazz;
  8. [WXSDKEngine registerModule:@"event" withClass:[WXEventModule class]];

使用自己的 module

这里的 require 里面的 event 就是在 上一步调用 registerModule: 注册 module 时候的 name

  1. var eventModule = weex.requireModule('event');
  2. eventModule.openURL('url',function(ret) {
  3. nativeLog(ret);
  4. });

Weex SDK 没有 图片下载,navigation 操作的能力,请大家自己实现这些 protocol

WXImgLoaderProtocol

weexSDK 没有图片下载的能力,需要实现 WXImgLoaderProtocol, 参考下面的例子

  1. WXImageLoaderProtocol.h
  2. @protocol WXImgLoaderProtocol <WXModuleProtocol>
  3. /**
  4. * @abstract Creates a image download handler with a given URL
  5. * @param imageUrl The URL of the image to download
  6. * @param imageFrame The frame of the image you want to set
  7. * @param options : The options to be used for this download
  8. * @param completedBlock : A block called once the download is completed.
  9. image : the image which has been download to local.
  10. error : the error which has happened in download.
  11. finished : a Boolean value indicating whether download action has finished.
  12. */
  13. -(id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)options completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock;
  14. @end

实现上述协议

  1. @implementation WXImgLoaderDefaultImpl
  2. #pragma mark -
  3. #pragma mark WXImgLoaderProtocol
  4. - (id<WXImageOperationProtocol>)downloadImageWithURL:(NSString *)url imageFrame:(CGRect)imageFrame userInfo:(NSDictionary *)userInfo completed:(void(^)(UIImage *image, NSError *error, BOOL finished))completedBlock
  5. {
  6. if ([url hasPrefix:@"//"]) {
  7. url = [@"http:" stringByAppendingString:url];
  8. }
  9. return (id<WXImageOperationProtocol>)[[SDWebImageManager sharedManager] downloadImageWithURL:[NSURL URLWithString:url] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
  10. } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
  11. if (completedBlock) {
  12. completedBlock(image, error, finished);
  13. }
  14. }];
  15. }
  16. @end

handler注册

你可以通过WXSDKEngine 中的 registerHandler:withProtocol注册handler

  1. WXSDKEngine.h
  2. /**
  3. * @abstract Registers a handler for a given handler instance and specific protocol
  4. * @param handler The handler instance to register
  5. * @param protocol The protocol to confirm
  6. */
  7. + (void)registerHandler:(id)handler withProtocol:(Protocol *)protocol;
  8. [WXSDKEngine registerHandler:[WXImgLoaderDefaultImpl new] withProtocol:@protocol(WXImgLoaderProtocol)]

Components 扩展

虽然 WeexSDK 中有很多的 native 的 Component,但这有可能并不能满足你的需求。在之前你可能已经写了一些很酷炫 native 的组件,想包装一下,导入到 Weex 中,因此我们提供了让开发者实现自己的 native Component。下面将以 WeexSDK 中已经存在的 Component:image 为例子,介绍一下如何构建一个 native Component。假设你已经了解 iOS 开发

注册 Component

注册一个 component 比较简单,调用 WXSDKEngine 中的 registerComponent:withClass: 方法,传入组件的标签名称,还有对应的 class 然后你可以创建一个 WXImageComponent 表示 image 组件的实现。在 .we 文件中,只需要写 <image></image>

添加属性

现在我们要做一些让 image component 更加强大的事情。既然作为一个图片的 component,那它应该要有源,给他加上一个 src 的属性,同时给它加上一个 resize 的属性(可以配置的有 contain/cover/stretch

  1. @interface WXImageComponent ()
  2. @property (nonatomic, strong) NSString *imageSrc;
  3. @property (nonatomic, assign) UIViewContentMode resizeMode;
  4. @end

component 中所有的 style,attribute,events 都会被传递到 Component 的初始化方法中,所以,你可以在初始化方法中存储你感兴趣的一些属性值

  1. @implementation WXImageComponent
  2. - (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
  3. {
  4. if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
  5. _imageSrc = [WXConvert NSString:attributes[@"src"]];
  6. _resizeMode = [WXConvert UIViewContentMode:attributes[@"resize"]];
  7. }
  8. return self;
  9. }
  10. @end

attribute 中拿到的值的类型都是 id,我们可以用转换方法把它转换到任何值。Weex SDK 提供了一些基础的转换方法,可以参考 WXConvert 类,或者你可以添加自己的转换函数。

Hooking 渲染生命周期

native 的 component 是由 Weex 管理的,Weex 创建,布局,渲染,销毁。Weex 的 component 生命周期都是可以 hook 的,你可以在这些生命周期中去做自己的事情。

方法 描述
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移除的时候调用.

在 image component 的例子里面,如果我们需要我们自己的 image view 的话,可以复写 loadView这个方法.

  1. - (UIView *)loadView
  2. {
  3. return [[WXImageView alloc] init];
  4. }

现在我们使用 WXImageView 渲染 image component。
作为一个 image component,我们需要拿到服务器图片,而且把它设置进 image view 里. 这个操作可以在 viewDidLoad 方法中做,这个方法是在 view 已经被创建而且加载了时候 Weex SDK 会调用到,而且 viewDidLoad 这个方法是你做额外初始化工作比如改变 content mode(也就是设置resize) 的最好时间.

  1. - (void)viewDidLoad
  2. {
  3. UIImageView *imageView = (UIImageView *)self.view;
  4. imageView.contentMode = _resizeMode;
  5. imageView.userInteractionEnabled = YES;
  6. imageView.clipsToBounds = YES;
  7. imageView.exclusiveTouch = YES;
  8. // Do your image fetching and updating logic
  9. }

如果可以改变 image 的 src,也可以 hook updateAttributes: 方法来做属性更新操作,当 updateAttributes: 或者 updateStyles: 被调用的时候, component 的 view 已经加载完成

  1. - (void)updateAttributes:(NSDictionary *)attributes
  2. {
  3. if (attributes[@"src"]) {
  4. _imageSrc = [WXConvert NSString:attributes[@"src"]];
  5. // Do your image updating logic
  6. }
  7. if (attributes[@"resize"]) {
  8. _resizeMode = [WXConvert UIViewContentMode:attributes[@"resize"]];
  9. self.view.contentMode = _resizeMode;
  10. }
  11. }

或许你需要考虑更多的生命周期方法去 Hook,当布局完成时候,像 layoutDidFinish,如果你想了解更多,可以参考一下WXComponent.h 声明的方法。

现在你可以用在任何 .we 文件里面使用 <image>,而且可以加上 image 的属性。

  1. <image style="your-custom-style" src="image-remote-source" resize="contain/cover/stretch"></image>
component 方法

WeexSDK 0.9.5 之后支持了在 js 中直接调用 component 的方法,这里提供一个例子

  • 自定义一个 WXMyCompoenent 的组件

    1. @implementation WXMyComponent
    2. WX_EXPORT_METHOD(@selector(focus)) // 暴露该方法给js
    3. - (instancetype)initWithRef:(NSString *)ref type:(NSString *)type styles:(NSDictionary *)styles attributes:(NSDictionary *)attributes events:(NSArray *)events weexInstance:(WXSDKInstance *)weexInstance
    4. {
    5. if (self = [super initWithRef:ref type:type styles:styles attributes:attributes events:events weexInstance:weexInstance]) {
    6. // handle your attributes
    7. // handle your styles
    8. }
    9. return self;
    10. }
    11. - (void)focus
    12. {
    13. NSLog(@"you got it");
    14. }
    15. @end
  • 注册组件 [WXSDKEngine registerComponent:@"mycomponent" withClass:[WXMyComponent class]]

  • 在 weex 文件中调用

    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>