【问题标题】:Check if a function is available in Swift?检查函数在 Swift 中是否可用?
【发布时间】:2022-11-18 11:17:19
【问题描述】:

我想检测用户是否启用了降低透明度。很简单,您只需调用 func UIAccessibilityIsReduceMotionEnabled(),它就会返回 Bool。但是我的应用程序针对 iOS 7 和 8,并且此功能在 iOS 7 上不可用。

在 Objective-C 中,这是我检查该函数是否存在的方式:

if (UIAccessibilityIsReduceMotionEnabled != NULL) { }

在 Swift 中,我不知道如何检查它是否存在。根据this answer,您可以简单地使用可选链接,如果它是nil,那么它就不存在,但这显然仅限于 Obj-C 协议。 Xcode 6.1 不喜欢这样:

let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled?()

它要你删除?。当然,如果您这样做,它会在 iOS 7 上崩溃,因为该功能不存在。

检查这些类型的功能是否存在的正确方法是什么?

【问题讨论】:

  • 函数表达式后的括号调用函数。如果你离开它们会发生什么?
  • 如果你离开 () 它说同样的事情 - 删除?:Operand of postfix '?' should have optional type; type is '() -> Bool'。另外,如果你移动?在 () 之后,都是相同的错误信息。
  • 我怀疑它确实还不可用。据我了解,可选链接用于类变量。 UIAccessibilityIsReduceMotionEnabled 更像是静态函数
  • 如果将函数分配给具有可选闭包类型的变量会发生什么? let reduceMotionDetectionIsAvailable : (() -> Bool)? = UIAccessibilityIsReduceMotionEnabled

标签: ios swift uiaccessibility


【解决方案1】:

Swift 2 中添加了对可用性的适当检查。与此处提到的其他选项相比,建议这样做。

var shouldApplyMotionEffects = true
if #available(iOS 8.0, *) {
    shouldApplyMotionEffects = !UIAccessibilityIsReduceMotionEnabled()
}

【讨论】:

    【解决方案2】:

    如果你不介意有点厚脸皮,你可以随时使用库加载器打开 UIKit 二进制文件,看看它是否可以解析符号:

    let uikitbundle = NSBundle(forClass: UIView.self)
    let uikit = dlopen(uikitbundle.executablePath!, RTLD_LAZY)
    let handle = dlsym(uikit, "UIAccessibilityIsReduceMotionEnabled")
    if handle == nil {
        println("Not available!")
    } else {
        println("Available!")
    }
    

    dlopendlsym 调用可能有点昂贵,所以我建议在应用程序的生命周期内保持 dlopen 句柄打开并将尝试 dlsym 的结果存储在某处。如果你不这样做,请确保你dlclose它。

    据我所知,这是 AppStore 安全的,因为 UIAccessibilityIsReduceMotionEnabled 是一个公共 API。

    【讨论】:

      【解决方案3】:

      您可以检查您是否在 iOS 8 或更高版本中运行——

      var reduceMotionEnabled = false
      if NSProcessInfo().isOperatingSystemAtLeastVersion(NSOperatingSystemVersion(majorVersion: 8, minorVersion: 0, patchVersion: 0)) {
          reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
      }
      

      我不思考还有另一种说法。所以理论上,如果您能够检查,尝试访问不带 () 的函数名称将在 iOS 7 中为您提供 nil,在 iOS 8 中为您提供 () -> Bool 函数。但是,为了实现这一点, UIAccessibilityIsReduceMotionEnabled 需要定义为 (() -> Bool)?,但事实并非如此。测试它会在两个版本的 iOS 中产生一个函数实例,如果在 iOS 7 中调用它会崩溃:

      let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled
      // reduceMotionDetectionIsAvailable is now a () -> Bool
      reduceMotionDetectionIsAvailable()
      // crashes in iOS7, fine in iOS8
      

      在不测试版本的情况下,我能看到的唯一方法就是简单地定义你自己的 C 函数来检查你的桥接头文件,并调用它:

      // ObjC
      static inline BOOL reduceMotionDetectionIsAvailable() {
          return (UIAccessibilityIsReduceMotionEnabled != NULL);
      }
      
      // Swift
      var reduceMotionEnabled = false
      if reduceMotionDetectionIsAvailable() {
          reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
      }
      

      【讨论】:

      • 是的,这是一种方法。 Apple 会反对这种做法,并且过去曾在 WWDC 会议期间提出过。很多时候您需要检测某个功能是否存在,尤其是在支持多个操作系统时。这是 Swift 中不存在的东西吗?这听起来很疯狂。
      • 谢谢,天啊,太可怕了。 :P 是时候提交增强请求了!我不会使用 Obj-C,这个应用程序是纯 Swift 的。
      【解决方案4】:

      来自 Apple 开发者文档 (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):

      Swift 代码可以使用 API 的可用性作为条件 运行。可用性检查可以用来代替条件 控制流语句,例如 ifguardwhile 陈述。

      以前面的例子为例,您可以在 if 中查看可用性 仅当方法调用requestWhenInUseAuthorization()的语句 在运行时可用:

      let locationManager = CLLocationManager()
      if #available(iOS 8.0, macOS 10.10, *) {
          locationManager.requestWhenInUseAuthorization()
      }
      

      或者,您可以在 guard 声明中检查可用性, 退出范围,除非当前目标满足 指定的要求。这种方法简化了处理逻辑 不同的平台能力。

      let locationManager = CLLocationManager()
      guard #available(iOS 8.0, macOS 10.10, *) else { return }
      locationManager.requestWhenInUseAuthorization()
      

      每个平台参数由下面列出的平台名称之一组成, 后跟相应的版本号。最后一个参数是 asterisk (*),用于处理潜在的未来平台。

      平台名称:

      • iOS
      • iOSApplicationExtension
      • macOS
      • macOSApplicationExtension
      • watchOS
      • watchOSApplicationExtension
      • tvOS
      • tvOSApplicationExtension

      【讨论】: