[英]Efficiently refactoring piece of Swift code to be less redundant
在下面的代碼中, A
鍵被重新映射到B
鍵,反之亦然。 重新映射通過SwiftUI
撥動開關激活。
在此處提供的示例中,同一代碼塊用於三個不同的函數。
此外,遍歷 function 調用的循環也用在所有這三個函數中。
一天多來,我一直在努力簡化這段代碼,讓它不那么冗余。 任何幫助將不勝感激。
let aKey: UInt64 = 0x700000004
let bKey: UInt64 = 0x700000005
func isKeyboardServiceClientForUsagePage(_ serviceClient: IOHIDServiceClient, _ usagePage: UInt32, _ usage: UInt32) -> Bool {
return IOHIDServiceClientConformsTo(serviceClient, usagePage, usage) == 1
}
func updateKeyboardKeyMapping(_ keyMap: [[String: UInt64]]) {
let eventSystemClient = IOHIDEventSystemClientCreateSimpleClient(kCFAllocatorDefault)
guard let serviceClients = IOHIDEventSystemClientCopyServices(eventSystemClient) as? [IOHIDServiceClient] else {
return
}
for serviceClient in serviceClients {
let usagePage = UInt32(kHIDPage_GenericDesktop)
let usage = UInt32(kHIDUsage_GD_Keyboard)
if isKeyboardServiceClientForUsagePage(serviceClient, usagePage, usage) {
IOHIDServiceClientSetProperty(serviceClient, kIOHIDUserKeyUsageMapKey as CFString, keyMap as CFArray)
}
}
}
func areKeysMappedOnAnyServiceClient() -> Bool {
let eventSystemClient = IOHIDEventSystemClientCreateSimpleClient(kCFAllocatorDefault)
guard let serviceClients = IOHIDEventSystemClientCopyServices(eventSystemClient) as? [IOHIDServiceClient] else {
return false
}
for serviceClient in serviceClients {
let usagePage = UInt32(kHIDPage_GenericDesktop)
let usage = UInt32(kHIDUsage_GD_Keyboard)
if isKeyboardServiceClientForUsagePage(serviceClient, usagePage, usage) {
guard let keyMapping = IOHIDServiceClientCopyProperty(serviceClient, kIOHIDUserKeyUsageMapKey as CFString) as? [[String: UInt64]] else {
return false
}
if keyMapping.contains(where: { $0[kIOHIDKeyboardModifierMappingSrcKey] == aKey && $0[kIOHIDKeyboardModifierMappingDstKey] == bKey }) &&
keyMapping.contains(where: { $0[kIOHIDKeyboardModifierMappingSrcKey] == bKey && $0[kIOHIDKeyboardModifierMappingDstKey] == aKey })
{
return true
}
}
}
return false
}
func remapABBA() {
let keyMap: [[String: UInt64]] = [
[
kIOHIDKeyboardModifierMappingSrcKey: aKey,
kIOHIDKeyboardModifierMappingDstKey: bKey,
],
[
kIOHIDKeyboardModifierMappingSrcKey: bKey,
kIOHIDKeyboardModifierMappingDstKey: aKey,
],
]
updateKeyboardKeyMapping(keyMap)
}
func resetKeyMapping() {
updateKeyboardKeyMapping([])
}
如果您想試用該應用程序,這是SwiftUI
部分:
import SwiftUI
struct ContentView: View {
@State private var remapKeys = areKeysMappedOnAnyServiceClient()
var body: some View {
HStack {
Spacer()
Toggle(isOn: $remapKeys, label: { Text("Remap A → B and B → A.") })
.toggleStyle(SwitchToggleStyle())
.onChange(of: remapKeys, perform: toggleKeyboardRemapping)
Spacer()
}
}
}
private func toggleKeyboardRemapping(_ remapKeys: Bool) {
if remapKeys {
remapABBA()
} else {
resetKeyMapping()
}
}
好的...這需要一些時間來回答。
看來你缺少存放東西的地方。 這就是為什么您必須一遍又一遍地使用相同的代碼塊。 我們可以通過視圖 model 來解決這個問題...
在這里,我將隱藏視圖中發生的事情的邏輯,只公開視圖需要訪問的內容才能顯示自己。
// we make it observable so the view can subscribe to it.
class KeyMappingViewModel: ObservableObject {
private let aKey: UInt64 = 0x700000004
private let bKey: UInt64 = 0x700000005
private let keyMap: [[String: UInt64]] = [
[
srcKey: aKey,
dstKey: bKey,
],
[
srcKey: bKey,
dstKey: aKey,
],
]
// A more concise way to get hold of the client ref
private var client: IOHIDEventSystemClientRef {
IOHIDEventSystemClientCreateSimpleClient(kCFAllocatorDefault)
}
// Making this published means the view can use it as state in the Toggle
@Published var toggleState: Bool {
didSet {
if toggleState {
client.updateKeyMapping(keyMap)
} else {
client.updateKeyMapping([])
}
}
}
init() {
// set the initial value by asking the client if it has any keys mapped
toggleState = client.areKeysMappedOnAnyServiceClient(aKey: aKey, bKey: bKey)
}
}
我將擴展IOHIDServiceClient
和IOHIDEventSystemClientRef
來封裝你的邏輯......
extension IOHIDEventSystemClientRef {
private let srcKey = kIOHIDKeyboardModifierMappingSrcKey
private let dstKey = kIOHIDKeyboardModifierMappingDstKey
// Make this an optional var on the client ref itself.
private var serviceClients: [IOHIDServiceClient]? {
IOHIDEventSystemClientCopyServices(eventSystemClient) as? [IOHIDServiceClient]
}
func areKeysMappedOnAnyServiceClient(aKey: UInt64, bKey: UInt64) -> Bool {
// Nice Swift 5.7 syntax with the optional var
guard let serviceClients else {
return false
}
// I made this more concise with a filter and map.
// Also, using the extension we can make use of keyPaths to get the values.
return serviceClients.filter(\.isForGDKeyboard)
.compactMap(\.keyMapping)
.map { keyMapping in
keyMapping.contains(where: { $0[srcKey] == aKey && $0[dstKey] == bKey }) &&
keyMapping.contains(where: { $0[srcKey] == bKey && $0[dstKey] == aKey })
}
.contains(true)
}
func updateKeyMapping(_ keyMap: [[String: UInt64]]) {
// serviceClients is optional so we can just ? it.
// if it's nil, nothing after the ? happens.
serviceClients?.filter(\.isForGDKeyboard)
.forEach {
IOHIDServiceClientSetProperty($0, kIOHIDUserKeyUsageMapKey as CFString, keyMap as CFArray)
}
}
}
extension IOHIDServiceClient {
var isForGDKeyboard: Bool {
let usagePage = UInt32(kHIDPage_GenericDesktop)
let usage = UInt32(kHIDUsage_GD_Keyboard)
return IOHIDServiceClientConformsTo(serviceClient, usagePage, usage) == 1
}
var keyMapping: [[String: UInt64]]? {
IOHIDServiceClientCopyProperty(self, kIOHIDUserKeyUsageMapKey as CFString) as? [[String: UInt64]]
}
}
完成所有這些意味着您的視圖看起來像這樣......
import SwiftUI
struct ContentView: View {
@ObservedObject var viewModel: KeyMappingViewModel = .init()
var body: some View {
HStack {
Spacer()
Toggle(isOn: $viewModel.toggleState, label: { Text("Remap A → B and B → A.") })
.toggleStyle(SwitchToggleStyle())
Spacer()
}
}
}
這包含您所有相同的邏輯,TBH 已經不是太糟糕了。
我的主要更改是采用自由函數和變量並將它們添加到各自的類型中。
因此, update
和areKeysMapped...
函數現在屬於IOHIDEventSystemClientRef
類型。
isForGDKeyboard
和keyMapping
變量現在屬於IOHIDServiceClient
類型。
這樣做會刪除很多重復的代碼,因為您不再需要連續調用自由函數。 這也意味着我們解鎖了一些非常 Swifty 的keyPath
用法,這有助於使一些邏輯更加簡潔。
然后我們制作了一個視圖 model。這使我們能夠將視圖的所有移動部分都放在一個地方。 它有一個地方可以很容易地抓住客戶。 這也意味着我們可以通過將其設為私有來隱藏視圖 model 中的很多內容。
這意味着視圖只能做一件事。 這是使用綁定到toggleState
。 其他一切都在閉門造車的情況下。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.