[英]Xcode Swift MacOS App, C++ threads and progress bar
首先,我是 Stackoverflow 的新手,我要感谢大家提供的所有重要信息!
我在 Linux 和 Win 下对 C/C++ 有一些经验,最近我正在尝试为 MacOS 开发/移植一个应用程序,因为我刚刚购买了新的 MacBook Air M1。
I've create a new MacOS App in Xcode and I'm using some C++ Classes with extern "C" wrapper... From my Swift code (push button pressed), I'm calling a C function that takes an int* as参数是它执行的操作的百分比进度:
void doSomething(int *progress);
我正在考虑在按下按钮时在单独的线程中运行 function,并根据进度变量的值更新我的 Swift UI 上的进度条。
你能帮我么? 什么可能是最好的解决方案? 我应该为 function 或进度条 UI 更新使用单独的线程吗?
如您所见,使用int *
效率相当低。 这是因为对该值所做的分配不能触发任何其他代码运行(在 C 中没有等效于didSet
观察者)。 因此,您不得不创建一个单独的线程,其目的是定期轮询 int 值,这非常浪费。
要解决此问题,您需要使用回调。 这些在 Swift 中很容易,因为闭包是您可以轻松创建和传递的第一个 class 公民。 在 C 中,没有这样的东西。 最接近的是 function 指针,但它们是 static,并且缺少闭包可以提供的上下文存储。 这意味着您不能将 Swift 实例方法或闭包作为 c function 指针传递。 您需要一个标记为@convention(c)
的闭包,它禁止它捕获任何 state(包括方法中的self
)。
但是有一个解决方法。 要理解它,首先您应该了解 C 中使用的典型回调模式。
在 C 中,典型的 function 采用 function 指针(作为回调)也有第二个无类型( void *
)参数。 这个想法是你可以手动传递你自己的上下文,无论是什么类型,它都会与 function 指针一起存储。 当触发回调时,C function 将调用您的 function 指针,并传入您给它的上下文。 在您的回调 function (作为回调传递的指针)中,您可以访问此void *context
,然后您可以将其转换为适当的类型并访问您需要的任何内容。
此上下文参数有许多名称,例如context
、 ctx
、 userdata
、 userinfo
(以及带下划线的驼峰变体)等。
如果您将 C function 更新为:
typedef void (*ProgressChangedCallback)(const int newProgress, void *userinfo);
void doSomething(ProgressChangedCallback callback, void *userinfo) {
// when the progress updates:
callback(newProgress, userinfo);
}
在 Swift 中,您可以利用此userinfo
参数传递您需要实现回调的上下文。 有两种流行的方法来做到这一点:
userinfo
参数走私self
,然后在self
上调用方法,并完全访问 object 的实例变量。userinfo
参数在适当的 Swift 闭包中走私。 闭包本身可以捕获 state,包括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)
}
}
进一步阅读
(take|pass)(Retained|Unretained)
的更多信息,请参阅关于Unmanaged
的 NSHipster 文章: https://nshipster.com/unmanaged/
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.