简体   繁体   English

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

[英]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语句就收到了这个错误,所以我认为条件导入在这种情况下不会有帮助。

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

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.

相关问题 是否可以在 Catalyst 应用程序中打开另一个 MacOS 应用程序 - Is it possible to open another MacOS app in a Catalyst app 测试人员无法打开 SwiftUI Catalyst MacOS App - SwiftUI Catalyst MacOS App cannot be opened by Testers 为 Mac 的 UIKit 构建,但链接框架“XXX.framework”是为 macOS 构建的 - Building for UIKit for Mac, but the linked framework 'XXX.framework' was built for macOS macOS Catalyst configurationForConnecting UISceneSession 委托函数并不总是在应用程序启动时调用 - macOS Catalyst configurationForConnecting UISceneSession delegate function not always called on app launch 如何禁用 macOS Catalyst 应用程序中的“显示标签栏”选项 - How to disable the "Show Tab Bar" option in a macOS Catalyst App 在 Catalyst macOS 应用程序中访问类似 NSWindow 的属性 - Accessing NSWindow-like properties in Catalyst macOS app macOS Catalyst App 系统菜单问题帮助不可用 - macOS Catalyst App system menu issue Help is unavailable macOS 催化剂上钥匙串项的 PersistentRef - PersistentRef of keychain item on macOS catalyst SwiftUI 将图像保存到 macOS Catalyst - SwiftUI Saving Image to macOS Catalyst 通过 Catalyst 工具 Xcode 从 Bundle for MacOS App 加载 WKWebView 中的 PDF 11 - Loading PDF in WKWebView from Bundle for MacOS App through Catalyst Tool Xcode 11
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM