扩展的应用范围 ios_使用插件扩展iOS应用

扩展的应用范围 ios

App extensions or plug-ins are very common for desktop apps. Web browsers offer a big selection of plug-ins, while many development environments like Visual Studio Code or Android Studio offer far more features in their plug-in marketplaces than in their original apps. Graphic design software also has plug-ins allowing you to extend the functionality of your apps.

应用扩展程序或插件在桌面应用程序中非常常见。 Web浏览器提供了大量插件,而许多开发环境(如Visual Studio Code或Android Studio)在其插件市场中提供的功能要比原始应用程序多得多。 图形设计软件还具有插件,可让您扩展应用程序的功能。

iOS apps, on the other hand, don’t have many extensions. Usually, you get a product with fixed functionality. Generally, Apple doesn’t seem to like plug-ins. Xcode is one of the few development environments that can’t be extended with plug-ins. You get what you get, and if something new appears, you need to update the whole app.

另一方面,iOS应用程序没有太多扩展。 通常,您会获得功能固定的产品。 通常,Apple似乎不喜欢插件。 Xcode是少数无法使用插件扩展的开发环境之一。 您将获得所得到的,如果出现新的事物,则需要更新整个应用程序。

I don’t know why Apple doesn’t like plug-ins, but for iOS apps, there’s an explanation. Before publishing an app in the App Store, which is the main distribution method for iOS apps, Apple reviews them. They have a long list of rules, and if they see that your app doesn’t fulfill at least one of their requirements, they reject it.

我不知道为什么苹果不喜欢插件,但是对于iOS应用,有一个解释。 在将应用程序(这是iOS应用程序的主要分发方法)发布到App Store中之前,Apple会对其进行审核。 他们有很长的规则,如果发现您的应用程序没有满足他们的至少一项要求,他们就会拒绝它。

“2.5.2 Apps should be self-contained in their bundles, and may not read or write data outside the designated container area, nor may they download, install, or execute code which introduces or changes features or functionality of the app, including other apps. Educational apps designed to teach, develop, or allow students to test executable code may, in limited circumstances, download code provided that such code is not used for other purposes. Such apps must make the source code provided by the Application completely viewable and editable by the user.” — Apple Developer

“ 2.5.2应用程序应独立包含在捆绑包中,不得在指定容器区域之外读取或写入数据,也不得下载,安装或执行介绍或更改应用程序功能的代码,包括其他应用。 在某些情况下,旨在教,发展或允许学生测试可执行代码的教育应用可以下载代码,前提是该代码不用于其他目的。 此类应用必须使用户可以完全查看和编辑应用程序提供的源代码。” - 苹果开发商

You may ask, “What about web browsers?” Web browsers on iOS devices are also very limited in functionality. They can show web content, but they can’t go beyond that. Even more, showing web content is limited by the same guidelines:

您可能会问:“网络浏览器呢?” iOS设备上的Web浏览器的功能也非常有限。 他们可以显示Web内容,但不能超出此范围。 甚至更多,显示Web内容受到相同准则的限制:

“2.5.6 Apps that browse the web must use the appropriate WebKit framework and WebKit Javascript.” — Apple Developer

“ 2.5.6浏览Web的应用程序必须使用适当的WebKit框架和WebKit Javascript。” - 苹果开发商

OK, but what if we want to add plug-ins that don’t change the app’s functionality? Or what if we don’t want an app for enterprise distribution? Is it possible to extend an app’s functionality dynamically? The short answer is “Yes.” And there are several different ways of doing it.

好的,但是如果我们要添加不更改应用程序功能的插件怎么办? 或者,如果我们不希望将应用程序用于企业发行该怎么办? 是否可以动态扩展应用程序的功能? 简短的回答是“是”。 并且有几种不同的方法可以做到这一点。

我们的目标 (Our Goal)

For demonstration purposes, let’s make an app with an empty screen, one container, and one button. The button will load our plug-in, run it, and show the result in the container.

出于演示目的,让我们制作一个具有空屏幕,一个容器和一个按钮的应用程序。 该按钮将加载我们的插件,运行它,并将结果显示在容器中。

All plug-ins will do the same thing: Draw a traffic light and switch between red, green, and blue in a loop.

所有插件都将执行相同的操作:绘制交通信号灯,并在循环中在红色,绿色和蓝色之间切换。

Image for post
Three stages of a traffic light (screenshots from the app).
交通信号灯的三个阶段(来自应用的屏幕截图)。

We need a protocol of communication with our plug-in. This protocol should include the following features:

我们需要与插件进行通信的协议。 该协议应包括以下功能:

  1. Running plug-in from Swift code

    从Swift代码运行插件
  2. Timer functionality

    计时器功能
  3. Creating and changing iOS views from the script

    通过脚本创建和更改iOS视图

So, we need a two-way interaction. Let’s see how it can be done.

因此,我们需要双向交互。 让我们看看如何完成它。

制备 (Preparation)

Before we start adding plug-ins, let’s create a base app. Create a new project with only one view controller. This view controller should have only two views:

在开始添加插件之前,让我们创建一个基本应用程序。 仅使用一个视图控制器创建一个新项目。 该视图控制器应该只有两个视图:

  1. Container for plug-in

    插件容器
  2. Button to start

    按钮启动

In more complex versions, you can load plug-ins from the web, scan from QR code(s), receive from Bluetooth, or even decode from sound waves. It won’t change anything, which is why I’ll use the easiest option — adding a script to the app bundle.

在更复杂的版本中,您可以从Web加载插件,从QR码扫描,从蓝牙接收甚至从声波解码。 它不会改变任何东西,这就是为什么我将使用最简单的选项-将脚本添加到应用程序包的原因。

Image for post
App storyboard.
应用故事板。

For each type of plug-in, the Swift code will be different. I’ll attach it to the end of each section.

对于每种类型的插件,Swift代码将有所不同。 我将其附加到每个部分的末尾。

JavaScript (JavaScript)

The easiest way to extend any app is by using JavaScript. First of all, JavaScript is a popular language. Second, there are other languages (e.g. TypeScript) that compile their code into JavaScript. And third, iOS has an integrated JavaScript interpreter.

扩展任何应用程序的最简单方法是使用JavaScript。 首先,JavaScript是一种流行的语言。 其次,还有其他语言(例如TypeScript)将其代码编译为JavaScript。 第三,iOS具有集成JavaScript解释器。

To run JavaScript code, use the JavaScriptCore framework:

要运行JavaScript代码,请使用JavaScriptCore框架:

import JavaScriptCore

JavaScript requires a context:

JavaScript需要上下文:

let jsContext = JSContext()

This gives an optional context. It’s a good practice to check if the context was created or not, and if not, to show an alert. But to shorten the future code, I’ll assume that the context will be always created:

这提供了一个可选的上下文。 最好检查上下文是否已创建,如果不是,则显示警报。 但是为了缩短将来的代码,我将假定始终创建上下文:

let jsContext = JSContext()!

Let’s include the JavaScript file to our project. We pass a text string to JSContext. The text can come from any source. It can be hardcoded, a file from an app bundle, or the web.

让我们将JavaScript文件包含到我们的项目中。 我们将文本字符串传递给JSContext 。 文本可以来自任何来源。 它可以是硬编码的,来自应用程序捆绑包的文件或网络上的文件。

Add jsPluginCode.js to your project. Here’s the JavaScript code I wrote for our plug-in:

jsPluginCode.js添加到您的项目中。 这是我为插件编写JavaScript代码:

To avoid conflicts with UIKit functions, I called views “widgets.”

为了避免与UIKit函数发生冲突,我将视图称为“小部件”。

There are two functions that will be called from Swift:

Swift将调用两个函数:

  1. start(width, height) creates widgets and sets initial values. It gets two arguments: the width and height of the plug-in container.

    start(width, height)创建小部件并设置初始值。 它有两个参数:插件容器的宽度和高度。

  2. tick(delta) tells our script that some delta time passed and allows it to take some action. In in-browser JavaScript, it wouldn’t be necessary. We could use async functions, but JavaScriptCore stops the execution when the script is over without waiting while all async functions are done (which would be an eternal loop, so we wouldn’t like to go there anyway).

    tick(delta)告诉我们的脚本,过去了一些delta时间,并允许其采取某些措施。 在浏览器内JavaScript中,这不是必需的。 我们可以使用异步函数,但是JavaScriptCore在脚本结束时停止执行,而无需等待所有异步函数完成(这将是一个永恒的循环,因此我们无论如何都不想去那里)。

And two functions that our script can call:

我们的脚本可以调用两个函数:

  1. addWidget(name, args) creates a widget with a given name (widget type) and arguments (widget properties).

    addWidget(name, args)创建一个具有给定名称(小部件类型)和参数(小部件属性)的小部件。

  2. updateProperty(widgetTag, property, newValue) updates a property of an existing widget with a given tag.

    updateProperty(widgetTag, property, newValue)使用给定标签更新现有窗口小部件的属性。

For this demo, there’s only one widget (Circle) and only one changeable property (backgroundColor). Of course, you can add as many widgets and properties as you like.

在此演示中,只有一个小部件( Circle )和一个可更改的属性( backgroundColor )。 当然,您可以根据需要添加任意数量的小部件和属性。

Our Circle widget has the following properties:

我们的Circle小部件具有以下属性:

  1. x — x coordinate of the center (number)

    x —中心的x坐标(数字)

  2. y — y coordinate of the center (number)

    y —中心的y坐标(数字)

  3. radius — radius of a circle (number)

    radius —圆的半径(数字)

  4. color — hex color of a circle (hex string)

    color —圆圈的十六进制颜色(十六进制字符串)

  5. tag — tag of a widget/view to reference it later (int number)

    tag —小部件/视图的标签,以供以后引用( int号)

The provided JavaScript code creates three circles: red, yellow, and green. One of them has full opacity, whereas the other two have more or less 1/3 of the opacity.

提供JavaScript代码创建三个圆圈:红色,黄色和绿色。 其中一个具有完全的不透明度,而另两个具有或多或少的1/3的不透明度。

The tick function counts in milliseconds. When it reaches one second, it runs loopStep, which changes the state of our traffic light, giving full opacity to another color (another light).

tick功能以毫秒为单位。 当达到一秒时,它将运行loopStep ,它会更改交通信号灯的状态,从而使另一种颜色(另一种灯光)完全不透明。

If you know JavaScript, this code won’t raise many questions. If not, just read it carefully. You’ll see that it’s not so different from Swift.

如果您了解JavaScript,那么这段代码不会引发很多问题。 如果没有,请仔细阅读。 您会发现它与Swift没有什么不同。

The iOS code is more complicated. We need to add four items:

iOS代码更加复杂。 我们需要添加四个项目:

  • Run the code when the user taps the button.

    当用户点击按钮时运行代码。
  • Add a timer that will call the tick function with a known interval.

    添加一个计时器,该计时器将以已知间隔调用tick功能。

  • A callable from JavaScript’s addWidget function.

    可从JavaScript的addWidget函数调用。

  • A callable from JavaScript’s updateProperty function.

    可从JavaScript的updateProperty函数调用。

Separately, we need to add a JavaScript exception handler to log our errors to the console. It will make the debug process much easier.

另外,我们需要添加一个JavaScript异常处理程序以将错误记录到控制台。 这将使调试过程更加容易。

Here’s the full source code of the view controller using JavaScript:

这是使用JavaScript的视图控制器的完整源代码:

a (Lua)

Another way to extend an app is with Lua. Lua is a simple programming language that was originally created to extend apps.

扩展应用程序的另一种方法是使用Lua。 Lua是一种简单的编程语言,最初是为了扩展应用程序而创建的。

“Lua is a powerful, efficient, lightweight, embeddable scripting language.” — Lua

“ Lua是一种功能强大,高效,轻巧,可嵌入的脚本语言。” — 卢阿

Lua is very popular in game development. Some game engines use Lua as their primary language (e.g. LÖVE). Other frameworks allow you to write part of a game, like the game logic or the game extensions, in Lua (e.g. Polycode).

Lua在游戏开发中非常受欢迎。 一些游戏引擎将Lua用作主要语言(例如LÖVE )。 其他框架允许您使用Lua(例如Polycode )编写游戏的一部分,例如游戏逻辑或游戏扩展。

Lua is truly a cross-platform language. Its official framework is written in plain C and can be integrated basically anywhere. Lua is free and distributed under the MIT license.

Lua是真正的跨平台语言。 它的正式框架是用纯C语言编写的,基本上可以在任何地方集成。 Lua是免费的,并根据MIT许可证进行分发。

“Lua may be used for any purpose, including commercial purposes, at absolutely no cost.” — Lua

“ Lua可以完全免费用于任何目的,包括商业目的。” — 卢阿

The original Lua framework can be easily integrated in Objective-C, but it’s more complicated in Swift. Fortunately, there are libraries to make it easier. For example, I found one that I’m going to use for our examples in this section.

原始的Lua框架可以轻松地集成到Objective-C中,但是在Swift中则更为复杂。 幸运的是,有一些库可以简化它。 例如,我在本节中找到了将用于我们的示例的示例。

Note: If you prefer to use the original Lua framework, you can download it and connect it to your project using a bridging header.

注意:如果您喜欢使用原始的Lua框架,则可以下载它,并使用桥接头将其连接到您的项目。

I won’t explain Lua’s syntax. There are many tutorials, manuals, and video courses for that. The logic of the script is 100% identical to the JavaScript code from the previous section:

我不会解释Lua的语法。 有很多教程,手册和视频课程。 该脚本的逻辑与上一部分中JavaScript代码100%相同:

A couple of details:

一些细节:

  • In JavaScript, the objects are very similar to Swift dictionaries, but Lua has tables. Their internal structure is different, so we can’t pass them as arguments. There are ways to pass custom structures, but we won’t go so deep. Instead, I changed the addWidget function to accept a list of simple arguments — numbers and strings. It removes some flexibility but makes the interface rather simple.

    在JavaScript中,对象与Swift字典非常相似,但是Lua有表。 它们的内部结构不同,因此我们不能将它们作为参数传递。 有很多方法可以传递自定义结构,但是我们不会深入探讨。 相反,我更改了addWidget函数以接受简单参数的列表-数字和字符串。 它消除了一些灵活性,但使界面相当简单。

  • The script returns two values: start and tick. They’re functions that we know from the previous section. When we read a file, we get a Lua function in Swift. We call it, get two functions as a result, save them in variables, and call them when we need them.

    该脚本返回两个值: starttick 。 它们是我们在上一节中了解的功能。 当我们读取文件时,我们在Swift中获得了Lua函数。 我们调用它,得到两个函数,将它们保存在变量中,并在需要时调用它们。

When Lua script is written and included into an app bundle (with the name luaPluginCode.lua), we integrate a library to parse and execute it. It can be done with Cocoapods:

当编写Lua脚本并将其包含在应用程序捆绑包中(名称为luaPluginCode.lua )时,我们将集成一个库来解析和执行它。 可以用Cocoapods完成:

platform :ios, '13.0'
use_frameworks!target 'Modules_iOS' do
pod 'lua4swift', :git => 'https://github.com/weyhan/lua4swift'end

Don’t forget to install pods. If you don’t know how pods work, you need to install cocoapods:

不要忘记安装Pod。 如果您不知道Pod的工作原理,则需要安装cocoapods:

sudo gem install cocoapods

Then, add the file Podfile to a project folder and run:

然后,将文件Podfile添加到项目文件夹并运行:

pod install

Great! Now, let’s switch to the Swift code.

大! 现在,让我们切换到Swift代码。

Import the Lua library:

导入Lua库:

import Lua

Create a virtual machine:

创建一个虚拟机:

let vm = Lua.VirtualMachine()

For simplicity’s sake, we’ll add all the functions to a global namespace. There are two functions, like in our previous example:

为了简单起见,我们将所有功能添加到全局名称空间中。 有两个功能,如我们之前的示例中所示:

This function has access to the view controller, so all logic can be done inside. We get all the arguments in one args variable, which we decompose to six variables with understandable names.

该功能可以访问视图控制器,因此所有逻辑都可以在内部完成。 我们将所有参数存储在一个args变量中,然后将其分解为名称可理解的六个变量。

The rest is pretty straightforward. We use this argument to set up a view and add it to our container.

其余的非常简单。 我们使用此参数来设置视图并将其添加到我们的容器中。

As Lua can get the return value, we need to specify it directly. In this case, we return .nothing, but if you need to return a value, you can use .value.

由于Lua可以获取返回值,因此我们需要直接指定它。 在这种情况下,我们不会返回.nothing ,但是如果您需要返回一个值,则可以使用.value

A second function to update the widget property:

第二个更新小部件属性的函数:

Now, when the functions are done, let’s read our file and save the start and tick functions in view controller properties startFunc and tickFunc. Both have the type Function — or Lua.Function to be precise:

现在,当函数完成后,让我们阅读文件并将starttick函数保存在视图控制器属性startFunctickFunc 。 两者都具有类型Function —或Lua.FunctionLua.Function

All errors will be printed in the console. This includes the errors inside the Lua script. As the lua4swift library outputs all the errors to the console automatically, we can ignore the call value when we don’t need it.

所有错误将打印在控制台中。 这包括Lua脚本中的错误。 由于lua4swift库自动将所有错误输出到控制台,因此我们可以在不需要时忽略调用值。

Our function calls will look like this:

我们的函数调用将如下所示:

_ = startFunc?.call([Double(vPluginContainer.bounds.width), Double(vPluginContainer.bounds.height)])

And:

和:

_ = tickFunc?.call([10.0])

The first function is called only once, and the second every 0.01 seconds. Visually, the app looks exactly like the one written in JavaScript.

第一个函数仅被调用一次,第二个函数每0.01秒调用一次。 在外观上,该应用程序看起来完全像是用JavaScript编写的应用程序。

Here’s the full source code of the view controller using Lua:

这是使用Lua的视图控制器的完整源代码:

动态图书馆 (Dynamic Libraries)

Finally, we reached the fun part.

最后,我们到达了有趣的部分。

What I’ve shown before is actively used in many apps. Some apps in the App Store are actually written in JavaScript. I’m talking about frameworks like React Native. Other apps actively use Lua. But what if we want to add a binary library to the app?

我之前显示的内容已在许多应用程序中积极使用。 App Store中的某些应用实际上是用JavaScript编写的。 我说的是像React Native这样的框架。 其他应用积极使用Lua。 但是,如果我们想向应用程序添加二进制库怎么办?

Let’s assume that we download a “fat” library built for armv7 and arm64. We know that this library has two functions: start and tick. The first one accepts two Double arguments, while the other accepts one Double argument. Can we call it? Is it even possible?

假设我们下载了一个为armv7和arm64构建的“胖”库。 我们知道该库具有两个功能: starttick 。 第一个接受两个Double参数,而另一个接受一个Double参数。 我们可以打电话吗? 可能吗

I want to point out that I’m not talking about Apple requirements. They’ll most likely reject such an app. I’m talking about the technical possibility to “inject” binary code into an existing iOS app.

我想指出的是,我并不是在谈论苹果的要求。 他们很可能会拒绝这样的应用程序。 我说的是将二进制代码“注入”到现有iOS应用程序中的技术可能性。

Let’s split it into two steps:

让我们将其分为两个步骤:

  1. Build an iOS framework and include it in the app bundle.

    构建一个iOS框架,并将其包含在应用程序捆绑包中。
  2. Separate this framework and load it dynamically.

    分离该框架并动态加载它。

iOS框架 (iOS framework)

Since the old days, there have been two types of libraries: static and dynamic. Static libraries are linked with the main binary during the compilation. As a result, they become a part of the output binary. Obviously, this one is not our choice.

从过去开始,就有两种类型的库:静态库和动态库。 静态库在编译过程中与主二进制文件链接。 结果,它们成为输出二进制文件的一部分。 显然,这不是我们的选择。

Dynamic libraries are located in separate files. They’re loaded after the app starts (usually in the beginning), but they’re not included in the executable binary. If you delete a dynamic library from the app folder or the app bundle, the app usually crashes or shows an error message.

动态库位于单独的文件中。 在应用启动后(通常是在开始时)加载它们,但它们不包含在可执行二进制文件中。 如果从应用程序文件夹或应用程序捆绑包中删除动态库,则该应用程序通常会崩溃或显示错误消息。

In Apple iOS platforms, dynamic libraries are presented as frameworks.

在Apple iOS平台中,动态库显示为框架。

I will use this existing project below and add a third view controller. All UI elements will be inside the main part (the same as in the previous examples):

我将在下面使用此现有项目并添加第三个视图控制器。 所有UI元素都将位于主体部分内(与前面的示例相同):

Image for post
Creating a framework target.
创建框架目标。

I called my framework “DynamicLights.” The code for both parts is very short. It doesn’t require any additional knowledge. It’s a simple iOS app:

我称我的框架为“ DynamicLights”。 这两部分的代码都很短。 它不需要任何其他知识。 这是一个简单的iOS应用程序:

As you can see, in the dynamic module, I used only the Foundation framework. This is to avoid any references to the UI. Actually, it’s an exact copy of the JavaScript and Lua code from previous sections.

如您所见,在动态模块中,我仅使用了Foundation框架。 这是为了避免对UI的任何引用。 实际上,它是前几节中JavaScript和Lua代码的精确副本。

FrameworkViewController.swift is also very simple:

FrameworkViewController.swift也非常简单:

让我们动态加载它 (Let’s load it dynamically)

First of all, let’s remove DymanicLights from the project dependencies:

首先,让我们从项目依赖项中删除DymanicLights:

Image for post
No dependency from DynamicLights.
不依赖DynamicLights。

Clean the “build” folder and build the project again. You’ll get an error message:

清理“ build”文件夹,然后再次生成项目。 您会收到一条错误消息:

No such module 'DynamicLights'

That makes sense. Our main app doesn’t know anything about our dynamic framework anymore.

这就说得通了。 我们的主应用程序对动态框架一无所知。

Choose DynamicLights as a build target and build it. In the Project navigator, you’ll see DynamicLights.framework in the Products folder:

选择DynamicLights作为构建目标并进行构建。 在项目导航器中,您将在Products文件夹中看到DynamicLights.framework

Image for post
DynamicLights framework.
DynamicLights框架。

Open this framework in Finder. In the DynamicLights.framework folder, you’ll find the DynamicLights file (the file name is different if you chose another name for your framework). This file is our dynamic library.

在Finder中打开此框架。 在DynamicLights.framework文件夹中,您会找到DynamicLights文件(如果您为框架选择其他名称,则文件名将不同)。 该文件是我们的动态库。

Xcode is smart, so if you copy it to an app bundle, it will link it automatically — even if you don’t choose this option explicitly. DymanicLights.framework and any parts of it shouldn’t be in the project bundle.

Xcode很聪明,因此,如果您将其复制到应用程序捆绑包中,它将自动链接它-即使您未明确选择此选项。 DymanicLights.framework及其任何部分都不应放在项目包中。

I tested it on the iOS Simulator, which has access to the file system of your Mac, so I just read the library from the file. If you want to test it on a real device, you’ll have to download the file, save it to the App Sandbox at runtime, and load it from there.

我在iOS模拟器上对其进行了测试,该模拟器可以访问Mac的文件系统,因此我只是从文件中读取了库。 如果要在真实设备上进行测试,则必须下载文件,在运行时将其保存到“应用程序沙箱”中,然后从那里加载它。

As I mentioned before, Apple has something against plug-ins and you’ll hardly find extendable iOS apps in the App Store, so Apple doesn’t have an official, documented way to interact with frameworks dynamically. It gets even more complicated if you think about different architectures, Swift versions, signing, and other environmental conditions. That’s why I kind of agree with Apple. This way is really not good.

正如我之前提到的,Apple有一些针对插件的应用程序,您在App Store中几乎找不到可扩展的iOS应用程序,因此Apple没有正式的,已记录的动态与框架进行交互的方式。 如果您考虑不同的体系结构,Swift版本,签名和其他环境条件,它将变得更加复杂。 这就是为什么我有点同意苹果的原因。 这种方式真的不好。

Fortunately for us, Swift is compatible with C. You can’t mix them in the same file, like Objective-C with C, but you can use C functions in Swift code. Now we’re talking about functions such as dlopen, dlsym, and dlclose. To export functions from a framework, you need to add the keyword @_cdecl.

对我们来说幸运的是,Swift与C兼容。您不能将它们混合在同一文件中,例如Objective-C与C,但可以在Swift代码中使用C函数。 现在我们在谈论诸如dlopendlsymdlclose函数。 要从框架导出函数,您需要添加关键字@_cdecl

In order not to deal with changing global variables (not even sure it’s possible), I added functions to set callbacks. Here’s the full code:

为了不处理更改全局变量(甚至无法确定),我添加了一些函数来设置回调。 这是完整的代码:

Rebuild your framework and copy the DynamicLights file to some known location. Theoretically, you can use the existing path from Xcode’s Derived Data folder, but I wouldn’t recommend it in order to avoid confusion. My path is /Volumes/Extra/Work/DynamicLights.

重建您的框架,然后将DynamicLights文件复制到某个已知位置。 从理论上讲,您可以使用Xcode的“ Derived Data文件夹中的现有路径,但是为了避免混淆,我不建议您使用它。 我的路径是/Volumes/Extra/Work/DynamicLights

Going back to our view controller, replace import DynamicLights with import Darwin. Darwin is a core element of Apple’s operating systems. As a framework, it gives access to core C functions, including dlopen, dlsym, and dlclose.

回到我们的视图控制器,将import DynamicLights替换为import Darwin 。 达尔文是Apple操作系统的核心元素。 作为框架,它可以访问核心C函数,包括dlopendlsymdlclose

We have four functions. Two of them set callbacks, while the other two give commands:

我们有四个功能。 其中两个设置回调,而另两个给出命令:

And two class members:

还有两个班级成员:

var handle: UnsafeMutableRawPointer?var tickFunc: tickFuncPrototype?

handle has a pointer to a dynamically loaded library. tickFunc is a function that we call 100 times per second (that’s why it’s worth keeping the pointer instead of getting it every time).

handle具有指向动态加载的库的指针。 tickFunc是我们每秒调用100次的函数(这就是为什么值得保留指针而不是每次都获取它的原因)。

This is how we load the dynamic library:

这是我们加载动态库的方式:

handle = dlopen("/Volumes/Extra/Work/DynamicLights", RTLD_LAZY)

Considering that the updateProperty and addWidget constants contain the code of our callbacks, this is our initialization code:

考虑到updatePropertyaddWidget常量包含我们的回调代码,这是我们的初始化代码:

And this is the timer callback:

这是计时器回调:

if let tickFunc = tickFunc {
tickFunc(0.01)
}

As you can see, we use some unsafe casts here. If the function prototype doesn’t match in the main and library code, the app will crash. This makes this method much less safe than using our JavaScript or Lua code.

如您所见,我们在这里使用了一些不安全的强制类型转换。 如果函数原型在主代码和库代码中不匹配,则该应用程序将崩溃。 这使得此方法比使用我们JavaScript或Lua代码安全得多。

Here’s the final version:

这是最终版本:

其他方法 (Other Ways)

JavaScript, Lua, and native libraries are not the only ways to extend iOS apps. There are many other scripting languages.

JavaScript,Lua和本机库不是扩展iOS应用程序的唯一方法。 还有许多其他脚本语言。

For example, there’s the fast and simple Gravity language that looks like Swift. It offers a C library that can be integrated into Objective-C and Swift apps.

例如,有一种快速且简单的Gravity语言,看起来像Swift。 它提供了一个C库,可以集成到Objective-C和Swift应用程序中。

More complicated languages like Python, Perl, or Ruby can also be used to extend existing apps, but it’s usually not worth it.

更复杂的语言(如Python,Perl或Ruby)也可以用于扩展现有应用程序,但这通常不值得。

If you need dynamic layouts, consider using XML or another markup language. You can write a simple engine yourself or use existing solutions.

如果需要动态布局,请考虑使用XML或其他标记语言。 您可以自己编写一个简单的引擎,也可以使用现有的解决方案

结论 (Conclusion)

Even though Apple doesn’t encourage the use of app extensions, it’s technically possible to use them.

即使Apple不鼓励使用应用程序扩展,但从技术上讲也可以使用它们。

The safest way to extend an app is with JavaScript. Apple has a native way to run JavaScript code, you can use different JavaScript flavors, and you can easily transfer the code to another platform.

扩展应用程序最安全的方法是使用JavaScript。 Apple有运行JavaScript代码的本机方法,您可以使用不同JavaScript风格,并且可以轻松地将代码传输到另一个平台。

If you’re not comfortable with JavaScript for some reason, you can use dozens of other scripting languages like Lua or the more exotic Gravity.

如果由于某种原因对JavaScript不满意,则可以使用其他数十种脚本语言,例如Lua或更具异国情调的Gravity。

Layouts can also be read from external files. Using an external layout engine, you can import the layout from an XML or other markup file. HTML can also be parsed and rendered in iOS apps.

也可以从外部文件读取布局。 使用外部布局引擎,可以从XML或其他标记文件导入布局。 HTML也可以在iOS应用中解析和呈现。

See you next time. Happy coding!

下次见。 编码愉快!

翻译自: https://medium.com/better-programming/extending-ios-apps-with-plug-ins-e4119d064f2d

扩展的应用范围 ios

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值