在使用 UIKit for macOS (Catalyst) 构建的应用程序中生成进程

问题描述

我正在构建一个在 macOS 和 iOS 版本(面向 macOS 11 和 iOS 14)之间共享大部分代码的应用程序。适用于 Mac 的 UIKit 似乎是帮助解决此问题的自然选择。不幸的是,one of the libraries 在幕后使用了 the Process type添加对它的依赖项以及面向 macOS 时,构建它会产生“在范围内找不到类型 Process错误。我可以为 iOS 排除这个库,但我仍然需要在 macOS 上链接它,同时保持在所有平台上使用 UIKit 的能力。

enter image description here

我已选择此库仅在 Xcode 中为 macOS 进行链接,但这没有任何效果并且相同的构建错误仍然存​​在。另外,我没有在应用中添加单个 import SwiftLSPClient 语句就收到了这个错误,所以我认为条件导入在这种情况下没有帮助。

Frameworks,Libraries,and Embedded Content settings in Xcode

在上述限制范围内解决此问题的最佳方法是什么?

解决方法

我在我的 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 应用之间桥接数据和方法调用,但它是可管理的。