[英]Spawning a process in an app built with UIKit for macOS (Catalyst)
I'm building an application that shares most of the code between macOS and iOS versions (targeting macOS 11 and iOS 14).我正在构建一个在 macOS 和 iOS 版本之间共享大部分代码的应用程序(针对 macOS 11 和 iOS 14)。 UIKit for Mac seems like a natural choice to help with this.适用于 Mac 的 UIKit 似乎是解决此问题的自然选择。 Unfortunately, one of the libraries uses the Process
type under the hood.不幸的是,其中一个库在后台使用了Process
类型。 Building it produces "Cannot find type Process
in scope" error when a dependency on it is added and when targeting macOS.当添加对它的依赖项以及针对 macOS 时,构建它会产生“无法在范围内找到类型Process
”错误。 I'm fine with excluding this library for iOS, but I still need to link with it on macOS while keeping the ability to use UIKit on all platforms.我可以排除 iOS 的这个库,但我仍然需要在 macOS 上链接它,同时保持在所有平台上使用 UIKit 的能力。
I've selected this library to be linked only for macOS in Xcode, but this has no effect and the same build error persists.我已选择此库仅在 Xcode 中为 macOS 链接,但这没有任何效果,并且相同的构建错误仍然存在。 Also, I'm getting this error without adding a single import SwiftLSPClient
statement in the app, so I don't think conditional imports would help in this case.此外,我没有在应用程序中添加单个import SwiftLSPClient
语句就收到了这个错误,所以我认为条件导入在这种情况下不会有帮助。
What would be the best way to resolve this issue within the constraints listed above?在上面列出的限制范围内解决此问题的最佳方法是什么?
I created a LSPCatalyst class in my Mac Catalyst app to replace the MacOS LanguageServerProcessHost.我在我的 Mac Catalyst 应用程序中创建了一个 LSPCatalyst class 来替换 MacOS LanguageServerProcessHost。 To make that work, I replaced the process
property with a processProxy
that accesses the process instance in a MacOS bundle using the FoundationApp protocol as explained below.为了完成这项工作,我将process
属性替换为processProxy
,它使用 FoundationApp 协议访问 MacOS 包中的流程实例,如下所述。
Following @Adam's suggestion, I created a MacOS bundle to proxy for the process instance.按照@Adam 的建议,我创建了一个 MacOS 包来代理流程实例。 You follow the same idea as he pointed to for AppKit access from Catalyst apps, but you just need Foundation to get access to Process.您遵循与他指出的从 Catalyst 应用程序访问 AppKit 相同的想法,但您只需要 Foundation 即可访问 Process。 I called the bundle FoundationGlue and put everything in a FoundationGlue folder in my Xcode project.我调用了捆绑包 FoundationGlue 并将所有内容放在我的 Xcode 项目的 FoundationGlue 文件夹中。 The bundle needs an Info.plist that identifies the principal class as "FoundationGlue.MacApp", and the MacApp.swift looks like:该捆绑包需要一个 Info.plist,将主体 class 标识为“FoundationGlue.MacApp”,MacApp.swift 如下所示:
import Foundation
class MacApp: NSObject, FoundationApp {
var process: Process!
var terminationObserver: NSObjectProtocol!
func initProcess(_ launchPath: String!, _ arguments: [String]?, _ environment: [String : String]?) {
process = Process()
process.launchPath = launchPath
process.arguments = arguments
process.environment = environment
}
func setTerminationCompletion(_ completion: (()->Void)!) {
let terminationCompletion = {
NotificationCenter.default.removeObserver(self.terminationObserver!)
completion?()
}
terminationObserver =
NotificationCenter.default.addObserver(
forName: Process.didTerminateNotification,
object: process,
queue: nil) { notification -> Void in
terminationCompletion()
}
}
func setupProcessPipes(_ stdin: Pipe!, _ stdout: Pipe!, _ stderr: Pipe!) {
process.standardInput = stdin
process.standardOutput = stdout
process.standardError = stderr
}
func launchProcess() {
process.launch()
print("Launched process \(process.processIdentifier)")
}
func terminateProcess() {
process.terminate()
}
func isRunningProcess() -> Bool {
return process.isRunning
}
}
The corresponding header I called FoundationApp.h looks like:我调用FoundationApp.h对应的header看起来像:
#import <Foundation/Foundation.h>
@protocol FoundationApp <NSObject>
typedef void (^terminationCompletion) ();
- (void)initProcess: (NSString *) launchPath :(NSArray<NSString *> *) arguments :(NSDictionary<NSString *, NSString *> *) environment;
- (void)setTerminationCompletion: (terminationCompletion) completion;
- (void)setupProcessPipes: (NSPipe *) stdin :(NSPipe *) stdout :(NSPipe *) stderr;
- (void)launchProcess;
- (void)terminateProcess;
- (bool)isRunningProcess;
@end
And the FoundationAppGlue-Bridging-Header.h just contains: FoundationAppGlue-Bridging-Header.h 仅包含:
#import "FoundationApp.h"
Once you have the bundle built for MacOS, add it as a framework to your Mac Catalyst project.为 MacOS 构建包后,将其作为框架添加到 Mac Catalyst 项目中。 I created a Catalyst.swift in that project for access to the FoundationGlue bundle functionality::我在该项目中创建了一个 Catalyst.swift 用于访问 FoundationGlue 捆绑功能:
import Foundation
@available(macCatalyst 13, *)
struct Catalyst {
/// Catalyst.foundation gives access to the Foundation functionality identified in FoundationApp.h and implemented in FoundationGlue/MacApp.swift
static var foundation: FoundationApp! {
let url = Bundle.main.builtInPlugInsURL?.appendingPathComponent("FoundationGlue.bundle")
let bundle = Bundle(path: url!.path)!
bundle.load()
let cls = bundle.principalClass as! NSObject.Type
return cls.init() as? FoundationApp
}
}
Then, you use it from your app like:然后,您可以从您的应用程序中使用它,例如:
let foundationApp = Catalyst.foundation!
foundationApp.initProcess("/bin/sh", ["-c", "echo 1\nsleep 1\necho 2\nsleep 1\necho 3\nsleep 1\necho 4\nsleep 1\nexit\n"], nil)
foundationApp.setTerminationCompletion({print("terminated")})
foundationApp.launchProcess()
This is a messy solution but I know it works: Add a “Mac bundle” to your Catalyst app and import the MacOS-only framework with that.这是一个混乱的解决方案,但我知道它有效:将“Mac 捆绑包”添加到您的 Catalyst 应用程序,然后导入仅适用于 MacOS 的框架。
Here's a guide to creating and loading a Mac bundle: https://medium.com/better-programming/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5以下是创建和加载 Mac 包的指南: https://medium.com/better-programming/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5
Once you have the bundle, you can add Mac-only libraries and frameworks to it.获得捆绑包后,您可以向其中添加仅限 Mac 的库和框架。 You'll have to bridge data and method calls between the bundle and your iOS app, but it's manageable.您必须在包和您的 iOS 应用程序之间桥接数据和方法调用,但它是可管理的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.