[英]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.