问题描述
我正在构建一个在 macOS 和 iOS 版本(面向 macOS 11 和 iOS 14)之间共享大部分代码的应用程序。适用于 Mac 的 UIKit 似乎是帮助解决此问题的自然选择。不幸的是,one of the libraries 在幕后使用了 the Process
type。添加对它的依赖项以及面向 macOS 时,构建它会产生“在范围内找不到类型 Process
”错误。我可以为 iOS 排除这个库,但我仍然需要在 macOS 上链接它,同时保持在所有平台上使用 UIKit 的能力。
我已选择此库仅在 Xcode 中为 macOS 进行链接,但这没有任何效果并且相同的构建错误仍然存在。另外,我没有在应用中添加单个 import SwiftLSPClient
语句就收到了这个错误,所以我认为条件导入在这种情况下没有帮助。
解决方法
我在我的 Mac Catalyst 应用程序中创建了一个 LSPCatalyst 类来替换 MacOS LanguageServerProcessHost。为了实现这一点,我将 process
属性替换为 processProxy
,该属性使用 FoundationApp 协议访问 MacOS 包中的流程实例,如下所述。
按照@Adam 的建议,我创建了一个 MacOS 包来代理流程实例。您遵循他所指出的从 Catalyst 应用程序访问 AppKit 的相同想法,但您只需要 Foundation 即可访问 Process。我调用了 FoundationGlue 包,并将所有内容放在我的 Xcode 项目中的 FoundationGlue 文件夹中。 bundle 需要一个 Info.plist 来标识主体类为“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
}
}
我称之为FoundationApp.h的对应头文件如下:
#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
FoundationAppGlue-Bridging-Header.h 只包含:
#import "FoundationApp.h"
为 MacOS 构建包后,将其作为框架添加到 Mac Catalyst 项目中。我在该项目中创建了一个 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
}
}
然后,您可以在您的应用中使用它,例如:
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()
,
这是一个混乱的解决方案,但我知道它有效:将“Mac 包”添加到您的 Catalyst 应用程序,然后导入仅适用于 MacOS 的框架。
以下是创建和加载 Mac 包的指南:https://medium.com/better-programming/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5
获得捆绑包后,您可以向其中添加仅限 Mac 的库和框架。您必须在 bundle 和 iOS 应用之间桥接数据和方法调用,但它是可管理的。