簡體   English   中英

有效重構 Swift 代碼片段以減少冗余

[英]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)
  }
}

我將擴展IOHIDServiceClientIOHIDEventSystemClientRef來封裝你的邏輯......

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 已經不是太糟糕了。

我的主要更改是采用自由函數和變量並將它們添加到各自的類型中。

因此, updateareKeysMapped...函數現在屬於IOHIDEventSystemClientRef類型。

isForGDKeyboardkeyMapping變量現在屬於IOHIDServiceClient類型。

這樣做會刪除很多重復的代碼,因為您不再需要連續調用自由函數。 這也意味着我們解鎖了一些非常 Swifty 的keyPath用法,這有助於使一些邏輯更加簡潔。

然后我們制作了一個視圖 model。這使我們能夠將視圖的所有移動部分都放在一個地方。 它有一個地方可以很容易地抓住客戶。 這也意味着我們可以通過將其設為私有來隱藏視圖 model 中的很多內容。

這意味着視圖只能做一件事。 這是使用綁定到toggleState 其他一切都在閉門造車的情況下。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM