【问题标题】:Xcode Swift MacOS App, C++ threads and progress barXcode Swift MacOS App、C++ 线程和进度条
【发布时间】:2021-02-15 13:15:26
【问题描述】:

首先,我是 Stackoverflow 的新手,我要感谢大家提供的所有重要信息!

我对 Linux 和 Win 下的 C/C++ 有一些经验,最近我正在尝试为 MacOS 开发/移植一个应用程序,因为我刚刚购买了新的 MacBook Air M1。

我在 Xcode 中创建了一个新的 MacOS 应用程序,并且正在使用一些带有外部“C”包装器的 C++ 类... 从我的 Swift 代码(按下按钮)中,我调用了一个 C 函数,该函数以 int* 作为参数,这是它执行的操作的百分比进度:

void doSomething(int *progress);

我正在考虑在按下按钮时在单独的线程中运行该函数,并根据进度变量的值更新我的 Swift UI 上的进度条。

你能帮帮我吗?什么可能是最好的解决方案?我应该为函数或进度条 UI 更新使用单独的线程吗?

【问题讨论】:

  • 我认为传递int * 是行不通的。每次您的 C 函数取得进展并更新整数指针时,都必须有一些“在另一边”来注意到这种变化。这意味着您将需要另一个线程来重复轮询并检查该值。这可以工作,但我认为你将有一个更轻松的时间,而不是接受一个回调函数指针作为参数,并在每次取得进展时调用它。然后,您的调用者代码可以提供一个函数来更新进度条 UI 以响应每个回调调用。
  • 感谢您的回复,我刚刚添加了一个线程,它只是使用传递给 C 函数的 int * 的值更新进度条,它似乎有效,但真的不喜欢有一个无限循环,即使没有必要也只会更新进度条。不幸的是不知道如何在 Swift 中实现某种观察者。

标签: c++ swift macos swiftui


【解决方案1】:

如您所见,使用int * 效率相当低。那是因为对该值所做的分配不能触发任何其他代码运行(在 C 中没有与 didSet 观察者等效的代码)。所以你不得不创建一个单独的线程,其目的是定期轮询 int 值,这非常浪费。

要解决此问题,您需要使用回调。这些在 Swift 中很容易,因为闭包是一等公民,您可以轻松地创建和传递它们。在 C 中,没有这样的东西。最接近的是函数指针,但它们是静态的,并且缺少闭包可以提供的上下文存储。这意味着您不能将 Swift 实例方法或闭包作为 c 函数指针传递。您需要一个标记为@convention(c) 的闭包,它禁止它捕获任何状态(包括方法中的self)。

但是有一个解决方法。要理解它,首先你应该了解 C 中使用的典型回调模式。

在 C 中,接受函数指针(作为回调)的函数通常还有第二个无类型 (void *)“上下文”参数。这个想法是你可以手动传入你自己的上下文,无论是什么类型,它都会与函数指针一起存储。当触发回调时,C 函数将调用你的函数指针,并传入你给它的上下文。在您的回调函数(您将其指针作为回调传递的那个)中,您可以访问此void *context,然后您可以将其转换为您的适当类型并访问您需要的任何内容。

此上下文参数有许多名称,例如contextctxuserdatauserinfo(以及下划线、驼峰式变体)等。

如果您将 C 函数更新为:

typedef void (*ProgressChangedCallback)(const int newProgress, void *userinfo);

void doSomething(ProgressChangedCallback callback, void *userinfo) {
   // when the progress updates:
   callback(newProgress, userinfo);
}

在 Swift 中,您可以利用这个 userinfo 参数来传递实现回调所需的上下文。有两种流行的方法:

  1. 您可以使用userinfo 参数偷运self,然后调用self 上的方法,并完全访问对象的实例变量。
  2. 您可以使用userinfo 参数在适当的Swift 闭包中走私。闭包本身可以捕获状态,包括 self 或您可能需要的任何其他内容。

看起来像这样:


// ProgressChangedCallback would have type `@convention(c) (Int, UnsafeMutableRawPointer) -> Void`

class MyClass {
    var someInstanceState = 0
    
    func someFunctionThatHasAccessToInstanceState() {
        defer { someInstanceState += 1 }
        print(someInstanceState)
        // 5. This can update your progress bar UI or whatever.
    }
    
    func registerCallbackForDoSomething() {
        let myProgressChangedCallback: ProgressChangedCallback = { newProgress, userInfo in
            // 3. Unpack `userinfo` to get access to our instance again
            let instance = Unmanaged<MyClass>.fromOpaque(userInfo).takeUnretainedValue()
            
            // 4. Do whatever we might need with the instance
            instance.someFunctionThatHasAccessToInstanceState()
        }
        
        // 1. Retain `self`, and get an opaque pointer to it
        let retainedPointerToSelf = UnsafeMutableRawPointer(Unmanaged.passRetained(self).toOpaque())
        
        // 2. Register our callback, and pass the pointer to self as `userInfo`
        doSomething(myProgressChangedCallback, retainedPointerToSelf)
    }
}
进一步阅读

【讨论】:

  • 非常感谢您的精彩解释,我认为这是要走的路!我试图将您的代码放在 ViewController 类的 MacOS 应用程序中,这样当我按下按钮时,我可以调用 registerCallbackForDoSomething 但我在这一行遇到错误:let instance = Unmanaged.fromOpaque(userInfo) .takeUnretainedValue()。错误报告:可选类型 'UnsafeMutableRawPointer?' 的值必须解包为“UnsafeMutableRawPointer”类型的值。你能帮帮我吗?
  • 这只是一个基本的可选性错误,这个网站上已经有数千个问题可以帮助你。在这种情况下,我认为强制打开它可能是安全的
  • 非常感谢 Alexander 的精彩解释。您的解决方案运行良好!
猜你喜欢
  • 1970-01-01
  • 2018-10-30
  • 2011-06-21
  • 1970-01-01
  • 2021-05-22
  • 2014-01-06
  • 2020-10-29
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多