模块开发指南_iOS

1 模块开发介绍

2 模块开发示例

3 上传模块包

4 其它SDK说明

5 同步方法

6 Swift支持

7 模块审核规范

参考视频:docs.apicloud.com/APICloud/videos-and-codes

1 模块开发介绍

1.1 简介

APICloud引擎通过系统Webkit浏览器,实现了HTML+CSS+Javascript开发语言和Objective-C/Java/C/C++等Native开发语言之间的桥接,极大的丰富和增强了标准Javascript的能力。令前端开发者通过JS即可调用移动设备的底层功能,如:电话、短信、定位、多媒体、跨域http请求等,并能将如百度地图、支付宝等第三方厂商的SDK很容易的集成到自己的App中来。

为满足广大开发者自定义扩展Native module的需求,APICloud推出模块扩展SDK,本SDK开放桥接机制,方便具有一定iOS基础的开发者自由开发定义Native扩展模块,丰富JS的能力,提升App的用户体验。

1.2 阅读对象

本文档面向所有使用该SDK的iOS开发人员、测试人员、合作伙伴以及对此感兴趣的其他用户。阅读该文档要求用户熟悉iOS应用开发,并且对Html、CSS、Javascript有一定了解。APICloud引擎强调传输数据的简洁和统一性,因此选择轻量级的JSON作为Javascript和Native语言之间通讯的数据载体,所以要求开发者同时要熟悉Objective-C和Javascript中JSON格式数据的操作。

1.3 开发环境

  • Xcode9.0或更高版本
  • macOS 10.12.6以上

1.4 下载SDK

前往 docs.apicloud.com/APICloud/download 下载最新版本的模块开发SDK,找到里面的ModulesDevProject_iOS.zip,这里面包含ModuleDemo、ModulesDevProject和说明文件,进行模块开发之前一定要先阅读read me.txt,了解各个目录里面的内容和功能。

2 模块开发示例

2.1 配置module.json

module.json定义了模块的基本信息,我们在开发模块之前最好先定义好模块名称、模块对应的类的名称、开放给JS的方法等。

字段解释:

  • name:对应值为模块的名称,JS中通过该名称来使用模块。

  • class:对应值为模块对应的类的名称。

  • methods:开放给JS的异步实例方法,多个方法以英文逗号隔开,此方法实现时需带一个参数。

  • syncMethods:开放给JS的同步实例方法,多个方法以英文逗号隔开,此方法实现时需带一个参数。

  • launchClassMethod:为可选项配置,若配置,引擎将在应用启动的时候调用该方法,注意该方法需是类方法,没有参数。

如图,在UZApp工程中找到uz目录下的module.json文件,在里面添加模块的配置信息:

图片说明

2.2 创建和配置静态库工程

打开Xcode,在菜单中选择File-New-Project...,在Framework & Library中选择Cocoa Touch Static Library,创建一个名为ModuleDemo的工程。

这里我们将ModuleDemo工程作为UZApp工程的一个依赖工程,这样做的好处是运行UZApp工程时会自动编译ModuleDemo工程,并且可以方便地在ModuleDemo工程中打断点进行调试。

先关闭打开的静态库工程,然后打开UZApp工程,将ModuleDemo.xcodeproj直接拖到UZModules下,如图:

图片说明

然后再按照下图在UZApp工程中的Linked Frameworks and Libraries处将libModuleDemo添加上:

图片说明

图片说明

将静态库工程的Build Active Architecture Only设置为No,如图:

图片说明

将静态库工程的iOS Deployment Target设置为7.0,如图:

图片说明

2.3 创建模块类

在ModuleDemo静态库工程中引入必要的UZModule.h头文件,UZAppDelegate.h和UZAppUtils.h等头文件根据需要引入,这些头文件可以在下载的SDK包里面找到。

在ModuleDemo静态库工程中新建一个UZModuleDemo类,继承于UZModule类,其中UZModule类为模块的基类。模块开发过程中文件命名时提倡加前缀,以避免和其它模块冲突。

模块生命周期:

  • 当在前端JS中首次调用模块的方法时,引擎会调用模块类的 - (id)initWithUZWebView:(UZWebView *)webView 方法进行初始化;

  • 当模块所在的页面被销毁时,模块类也会被销毁,引擎会主动调用其 - (void)dispose 方法。

2.4 模块方法实现

在UZModuleDemo类中实现module.json里面配置的showAlert方法,该方法必须带一个参数,参数类型为字典。

如果前端调用该方法时传入了一个function,那么在这里可以通过cbId字段获取该function对应的id,然后在需要的时候把数据通过该function回调给JS。

- (void)showAlert:(NSDictionary *)paramDict {
    _cbId = [paramDict integerValueForKey:@"cbId" defaultValue:0];
    NSString *message = [paramDict stringValueForKey:@"msg" defaultValue:nil];
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:nil message:message delegate:self cancelButtonTitle:@"取消" otherButtonTitles:@"确定", nil];
    [alert show];
}

2.5 回传数据给JS

我们在这里将用户点击的按钮的index回调给JS端,注意sendResultEventWithCallbackId:dataDict:errDict:doDelete:方法的最后一个参数,如果需要多次进行回调,那么就必须传NO。

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    if (_cbId > 0) {
        NSDictionary *ret = @{@"index":@(buttonIndex)};
        [self sendResultEventWithCallbackId:_cbId dataDict:ret errDict:nil doDelete:YES];
    }
}

2.6 JS调用模块方法

前端JS必须使用JSON格式数据作为JS与Native之间交换数据的传参,APICloud引擎会对JS传入的参数进行解析并封装,前端JS使用模块之前需要require模块对象。

找到UZApp工程中widget目录下的index.html,添加调用moduleDemo模块的showAlert方法的代码:

function showAlert() {
    var demo = api.require('moduleDemo');
    demo.showAlert({
        msg: 'Hello App!'
    },function(ret, err){
        var msg = "点击了第" + ret.index + "个按钮";
        api.toast({
            msg: msg
        });
    });
}

3 上传模块包

3.1 模块包介绍

模块包根目录必须以该模块的JS对象名命名,这里以moduleDemo为例,模块包内包含res_moduleDemo、target、framework等文件夹以及module.json。

目录解释:

  • res_moduleDemo目录:(可选配置项)放置资源文件等,此文件夹会以Create folder references方式加入工程,读取资源文件时路径需要加上res_moduleDemo。建议将资源文件放置在此文件夹下读取,以防止资源文件名冲突。读取该文件夹下资源示例代码:

      NSString *path = [[NSBundle mainBundle].bundlePath stringByAppendingPathComponent:@"res_moduleDemo/1.png"];
    
  • target目录:存放编译生成的静态库文件、第三方Static类型的framework库(直接添加到工程就能正常使用的是Static类型的,而需要在工程Embedded Binaries处添加才能正常使用的是Dynamic类型的)、以及其它需要以Create groups方式添加到应用工程中使用的文件,如bundle束、图片等。若该目录下存在其它的文件夹,这些文件夹会以Create folder references的方式被加入到应用工程。

  • framework目录:(可选配置项)存放第三方Dynamic类型的framework库(需要在工程Embedded Binaries处添加才能正常使用的是Dynamic类型的)。

  • module.json文件:内容为JSON格式,定义了模块的类名称、JS对象名称、方法等。如:

       {
          "name":"moduleDemo",
          "class":"UZModuleDemo",
          "methods":["showAlert"]
       }
    

    一个模块包里面也可以配置多个模块,各个模块用英文逗号隔开,如:

       {
          "name":"moduleDemo",
          "class":"UZModuleDemo",
          "methods":["showAlert"]
       },
       {
           "name":"moduleDemo1",
           "class":"UZModuleDemo1",
           "methods":["showAlert"]
       }
    

3.2 制作moduleDemo模块包

1、新建一个moduleDemo文件夹。

2、在moduleDemo文件夹里面创建一个module.json的文本文件,拷贝以下内容到module.json中,注意双引号一定要是英文状态下的。

 {
    "name":"moduleDemo",
    "class":"UZModuleDemo",
    "methods":["showAlert"]
 }

3、在moduleDemo文件夹里面创建target文件夹,将静态库编译出来的libModuleDemo.a库拷贝到target目录下。注意.a库必须是真机环境的,并且需要支持armv7和arm64架构,可以在终端用以下命令查看.a库支持的架构:

lipo -info libModuleDemo.a

4、将moduleDemo文件夹压缩成moduleDemo.zip。

3.3 上传模块包到云端

登录到APICloud网站控制台,进入你的应用里面,在模块栏里面找到自定义模块选项卡,选择上传自定义模块,输入模块信息并上传moduleDemo.zip文件然后保存。

图片说明

保存成功后将会显示出该模块,然后点击模块右上角的加号添加模块。

图片说明

4 其它SDK说明

4.1 显示UI视图

对于需要添加UIView类视图的接口,需要提供fixedOn参数,让前端JS传入frame的名字,然后将视图添加到该frame上面,同时还应该提供fixed参数,控制视图是否随着frame内容的移动而跟着移动。

UZModule类提供 - (BOOL)addSubview:(UIView )view fixedOn:(NSString )fixedOn fixed:(BOOL)fixed 方法,用于往指定的frame上面添加视图。

- (void)show:(NSDictionary *)paramDict {
    NSString *fixedOn = [paramDict stringValueForKey:@"fixedOn" defaultValue:nil];
    BOOL fixed = [paramDict boolValueForKey:@"fixed" defaultValue:YES];
    [self addSubview:yourView fixedOn:fixedOn fixed:fixed];
}

同时UZModule提供属性controller,可通过该控制器对目标控制器进行push或者present操作。

// push
[self.viewController.navigationController pushViewController:controller animated:YES];

// present
[self.viewController presentViewController:controller animated:YES completion:nil];

4.2 文件路径转换

为消除iOS和Android平台系统间文件路径的差异,APICloud为前端JS提供了fs://、widget://和cache://等虚拟文件路径协议,因此,模块在使用JS端传入的路径时需要调用UZModule里面的 - (NSString )getPathWithUZSchemeURL:(NSString )url 方法来转换成正确的绝对路径。

NSString *path = [paramDict stringValueForKey:@"path" defaultValue:nil];
if (path) {
    NSString *fullPath = [self getPathWithUZSchemeURL:path];
}

4.3 获取模块配置信息

部分模块可能要求开发者在config.xml里面配置信息,如第三方平台申请的key之类,配置如下:

<feature name="moduleDemo">
    <param name="apiKey" value="123456" />
</feature>

那么在模块中通过UZModule中的 - (NSDictionary )getFeatureByName:(NSString )name方法获取配置信息。

NSDictionary *feature = [self getFeatureByName:@"moduleDemo"];
NSString *apiKey = [feature stringValueForKey:@"apiKey" defaultValue:nil];

4.4 启动方法

如果模块需要在应用启动的时候就执行一些操作,那么可以在module.json里面配置launchClassMethod,例如配置的方法为launch,然后在模块里面实现该方法,当应用启动时该方法就会被执行。该启动方法为类方法。

module.json配置:

{
    "name":"moduleDemo",
    "class":"UZModuleDemo",
    "methods":["showAlert"],
    "launchClassMethod":"launch"
}

模块类里面实现该类方法:

+ (void)launch {
    //
}

4.5 应用程序代理方法

一些功能需要通过应用程序代理方法才能实现,如获取推送信息、处理第三方应用回调等。

我们在UZAppDelegate中提供了 - (void)addAppHandle:(id )handle 方法,该方法的handle参数为实现了UIApplicationDelegate协议的对象,引擎会对应用程序代理方法做一次分发。

注意一定要在- (void)dispose方法里面调用 - (void)removeAppHandle:(id )handle方法移除对象。

如处理应用被第三方应用调起:

- (id)initWithUZWebView:(UZWebView *)webView_ {
    if (self = [super initWithUZWebView:webView_]) {
        [theApp addAppHandle:self];
    }
    return self;
}

- (void)dispose {
    [theApp removeAppHandle:self];
}

#pragma mark - UIApplicationDelegate
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    //处理应用被三方应用调起
    return YES;
}

5 同步方法

5.1 说明

同步方法是指在js中调用模块方法时直接返回结果,而不使用callback的方式返回结果。方法返回的类型包括NSDictionary、NSArray、NSString、NSNumber等。

5.2 实现

要使得模块定义的一个方法能够同步返回执行结果,首先需要在module.json里面进行配置,用syncMethods字段定义同步方法,如定义名字叫systemVersion和applicationIconBadgeNumber的同步方法:

{
    "name":"moduleDemo",
    "class":"UZModuleDemo",
    "methods":["showAlert"],
    "syncMethods":["systemVersion", "applicationIconBadgeNumber"]
}

然后在UZModuleDemo中实现方法,注意如果是BOOL、int等基本数据类型,需要用NSNumber对象进行包装。

- (NSString *)systemVersion:(NSDictionary *)param {
    NSString *version = [UIDevice currentDevice].systemVersion;
    return version;
}

- (NSNumber *)applicationIconBadgeNumber:(NSDictionary *)param {
    NSInteger badgeNumber = [UIApplication sharedApplication].applicationIconBadgeNumber;
    return @(badgeNumber);
}

js中调用示例:

var demo = api.require('moduleDemo');
var systemVersion = demo.systemVersion();
var badgeNumber = demo.applicationIconBadgeNumber();

6 Swift支持

6.1 声明

使用Swift开发模块时,模块类需要继承自UZModule类,同时需要在类的前面加上@objc声明,如下:

@objc(UZModuleDemoSwift)
class UZModuleDemoSwift: UZModule {

}

6.2 集成

目前静态库不支持使用swift,上传模块包的时候需要将所有的swift源文件放到target目录下面。

7 模块审核规范

开发者开发的模块务必遵守《模块审核规范-iOS》