【问题标题】:Spawning a process in an app built with UIKit for macOS (Catalyst)在使用 UIKit for macOS (Catalyst) 构建的应用程序中生成进程
【发布时间】:2020-12-30 15:27:19
【问题描述】:

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

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

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

【问题讨论】:

    标签: swift xcode macos mac-catalyst uikitformac


    【解决方案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()
    

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        猜你喜欢
        • 2020-02-24
        • 1970-01-01
        • 2020-07-09
        • 2020-07-30
        • 2012-07-21
        • 2019-12-16
        • 1970-01-01
        • 2020-05-11
        • 2022-11-17
        相关资源
        最近更新 更多