1

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

在此处输入图像描述

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

Xcode 中的框架、库和嵌入式内容设置

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

4

2 回答 2

1

我在我的 Mac Catalyst 应用程序中创建了一个 LSPCatalyst 类来替换 MacOS LanguageServerProcessHost。为了完成这项工作,我将process属性替换为processProxy使用 FoundationApp 协议访问 MacOS 包中的流程实例的属性,如下所述。

按照@Adam 的建议,我创建了一个 MacOS 包来代理流程实例。您遵循与他指出的从 Catalyst 应用程序访问 AppKit 相同的想法,但您只需要 Foundation 即可访问 Process。我调用了捆绑包 FoundationGlue 并将所有内容放在我的 Xcode 项目中的 FoundationGlue 文件夹中。捆绑包需要一个 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()
于 2021-01-05T23:57:52.913 回答
0

这是一个混乱的解决方案,但我知道它有效:将“Mac 捆绑包”添加到您的 Catalyst 应用程序,然后导入仅适用于 MacOS 的框架。

以下是创建和加载 Mac 包的指南:https ://medium.com/better-programming/how-to-access-the-appkit-api-from-mac-catalyst-apps-2184527020b5

获得捆绑包后,您可以向其中添加仅限 Mac 的库和框架。您必须在包和您的 iOS 应用程序之间桥接数据和方法调用,但它是可管理的。

于 2020-12-31T03:11:35.963 回答