iOS 插件开发指南

本节详细介绍了如何在 iOS 平台上实现原生插件代码。

在阅读本文之前,请参阅插件开发指南,了解插件的结构及其常用的 JavaScript 接口。本节将继续演示示例的 echo 插件,该插件可以从 Cordova webview 与原生平台进行通信。

iOS 插件被实现为一个 Objective-C 类,该类扩展了 CDVPlugin 类。为了使 JavaScript 的 exec 方法的 service 参数映射到 Objective-C 类,每个插件类必须在命名应用程序目录的 config.xml 文件中注册为 <feature> 标签。

插件类映射

插件的 JavaScript 部分使用 cordova.exec 方法,如下所示:

exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);

这将把来自 UIWebView 的请求编组到 iOS 原生端,有效地调用 service 类上的 action 方法,并将参数传递到 args 数组中。

在您的 Cordova-iOS 应用程序项目的 config.xml 文件中将插件指定为 <feature> 标签,并使用 plugin.xml 文件自动注入此标记,如插件开发指南中所述。

<feature name="LocalStorage">
    <param name="ios-package" value="CDVLocalStorage" />
</feature>

该功能的 name 属性应与您指定为 JavaScript exec 调用的 service 参数相匹配。value 属性应与插件的 Objective-C 类的名称相匹配。 <param> 元素的 name 应该始终为 ios-package。如果您不遵循这些准则,该插件可能会编译,但 Cordova 仍然可能无法访问它。

插件初始化和生命周期

每个 UIWebView 的生命周期都会创建一个插件对象实例。除非在 config.xml 中将具有 onload name 属性的 <param> 设置为 "true",否则插件在第一次被 JavaScript 调用引用之前不会被实例化。例如:

<feature name="Echo">
    <param name="ios-package" value="Echo" />
    <param name="onload" value="true" />
</feature>

插件应该使用 pluginInitialize 方法来实现其启动逻辑。

具有长时间运行的请求或后台活动(例如媒体播放、侦听器或维护内部状态)的插件应实现 onReset 方法,以取消那些长时间运行的请求或在这些活动之后进行清理。当 UIWebView 导航到新页面或刷新时(这将重新加载 JavaScript),此方法会运行。

编写 iOS Cordova 插件

JavaScript 调用会向原生端触发一个插件请求,并且相应的 iOS Objective-C 插件在 config.xml 文件中正确映射,但是最终的 iOS Objective-C 插件类是什么样子的呢?使用 JavaScript 的 exec 函数分派到插件的任何内容都将传递到相应插件类的 action 方法中。插件方法具有以下签名:

- (void)myMethod:(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = nil;
    NSString* myarg = [command.arguments objectAtIndex:0];

    if (myarg != nil) {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK];
    } else {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsString:@"Arg was null"];
    }
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

有关更多详细信息,请参见CDVInvokedUrlCommand.hCDVPluginResult.hCDVCommandDelegate.h

iOS CDVPluginResult 消息类型

您可以使用 CDVPluginResult 将各种结果类型返回到 JavaScript 回调,使用遵循此模式的类方法:

+ (CDVPluginResult*)resultWithStatus:(CDVCommandStatus)statusOrdinal messageAs...

您可以创建 StringIntDoubleBoolArrayDictionaryArrayBufferMultipart 类型。您也可以省略任何参数来发送状态、返回错误,甚至选择不发送任何插件结果,在这种情况下,都不会触发任何回调。

请注意以下复杂的返回值:

  • messageAsArrayBuffer 需要 NSData*,并在 JavaScript 回调中转换为 ArrayBuffer。同样,JavaScript 发送到插件的任何 ArrayBuffer 都会转换为 NSData*

  • messageAsMultipart 需要一个 NSArray*,其中包含任何其他支持的类型,并将整个数组作为 arguments 发送到您的 JavaScript 回调。这样,所有参数都会根据需要进行序列化或反序列化,因此可以安全地将 NSData* 作为 multipart 返回,但不能作为 Array/Dictionary 返回。

Echo iOS 插件示例

要匹配应用程序插件中描述的 JavaScript 接口的 echo 功能,请使用 plugin.xmlfeature 规范注入到本地平台的 config.xml 文件中:

<platform name="ios">
    <config-file target="config.xml" parent="/*">
        <feature name="Echo">
            <param name="ios-package" value="Echo" />
        </feature>
    </config-file>
</platform>

然后,我们将以下 Echo.hEcho.m 文件添加到 Cordova-iOS 应用程序目录中的 Plugins 文件夹中:

/********* Echo.h Cordova Plugin Header *******/

#import <Cordova/CDVPlugin.h>

@interface Echo : CDVPlugin

- (void)echo:(CDVInvokedUrlCommand*)command;

@end

/********* Echo.m Cordova Plugin Implementation *******/

#import "Echo.h"
#import <Cordova/CDVPlugin.h>

@implementation Echo

- (void)echo:(CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = nil;
    NSString* echo = [command.arguments objectAtIndex:0];

    if (echo != nil && [echo length] > 0) {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:echo];
    } else {
        pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
    }

    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

@end

文件顶部的必要导入从 CDVPlugin 扩展了该类。在这种情况下,该插件仅支持一个 echo 操作。它通过调用 objectAtIndex 方法来获取 echo 字符串,以获取 arguments 数组的第一个参数,该参数对应于 JavaScript exec() 函数传递的参数。

它检查该参数以确保其不为 nil 或空字符串,如果为 nil 或空字符串,则返回具有 ERROR 状态的 PluginResult。如果该参数通过检查,则它返回具有 OK 状态的 PluginResult,并传入原始的 echo 字符串。最后,它将结果发送到 self.commandDelegate,后者会在 JavaScript 端执行 exec 方法的成功或失败回调。如果调用了成功回调,它将传入 echo 参数。

iOS 集成

CDVPlugin 类具有其他可以覆盖的方法。例如,您可以捕获pauseresume、应用终止和 handleOpenURL 事件。有关指导,请参见CDVPlugin.hCDVPlugin.m类。

WKURLSchemeTask 钩子

WKURLSchemeTask 是 Cordova 的主 WKWebView 使用的接口,用于从您的应用程序包加载文件。您可以通过在插件中实现 - (BOOL) overrideSchemeTask: (id <WKURLSchemeTask>)urlSchemeTask 方法,为 webview 创建自己的自定义方案或自定义加载代码。

线程

插件方法通常在与主接口相同的线程中执行。如果您的插件需要大量的处理或需要阻塞调用,则应使用后台线程。例如:

- (void)myPluginMethod:(CDVInvokedUrlCommand*)command
{
    // Check command.arguments here.
    [self.commandDelegate runInBackground:^{
        NSString* payload = nil;
        // Some blocking logic...
        CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:payload];
        // The sendPluginResult method is thread-safe.
        [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
    }];
}

调试 iOS 插件

要在 Objective-C 端进行调试,您需要 Xcode 的内置调试器。对于 JavaScript,您可以将 Safari 连接到在 iOS 模拟器/设备中运行的应用程序。

常见陷阱

  • 不要忘记将您的插件的映射添加到 config.xml。如果您忘记了,则会在 Xcode 控制台中记录错误。

  • 不要忘记在允许列表中添加您连接到的任何主机,如域名允许列表指南中所述。如果您忘记了,则会在 Xcode 控制台中记录错误。