高效地重构Swift代码片段以减少冗余

qyzbxkaa  于 2022-12-28  发布在  Swift
关注(0)|答案(1)|浏览(187)

在下面的代码中,A键被重新Map到B键,反之亦然。重新Map是通过SwiftUI切换开关激活的。
在这里给出的示例中,同一代码块用于三个不同的功能。
此外,在所有这三个函数中还使用了迭代函数调用的循环。

一天多以来,我一直在努力简化这段代码,减少冗余。如有任何帮助,我将不胜感激。

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()
    }
}
tzdcorbm

tzdcorbm1#

好吧......这需要一些时间来回答。
看起来你缺少一个地方来存储东西。这就是为什么你必须一遍又一遍地使用相同的代码块。我们可以用视图模型来解决这个问题...
在这里,我将隐藏视图中正在发生的逻辑,只暴露视图需要访问的内容,以便显示自己。

// 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 srcKey = kIOHIDKeyboardModifierMappingSrcKey
  private let dstKey = kIOHIDKeyboardModifierMappingDstKey

  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) {
        Text("Remap A → B and B → A.")
      }
      .toggleStyle(SwitchToggleStyle())
      Spacer()
    }
  }
}

这包含了你所有相同的逻辑和TBH已经不是太坏了。
我的主要修改是将自由函数和变量添加到各自的类型中。
因此,updateareKeysMapped...函数现在属于IOHIDEventSystemClientRef类型。
isForGDKeyboardkeyMapping变量现在属于IOHIDServiceClient类型。
这样做删除了很多重复的代码,因为你不再需要不断地调用免费函数。这也意味着我们解锁了一些非常快速的keyPath用法,这有助于使一些逻辑更加简洁。
然后我们做了一个视图模型,这样我们就可以把视图的所有移动部分都放在一个地方,这样就有了一个地方来方便客户端,也意味着我们可以通过私有化来隐藏视图模型中的很多东西。
这意味着视图只能做一件事,那就是使用绑定到toggleState,其他的一切都是关起门来的。

相关问题