检查一个功能是否在Swift中可用?

时间:2022-01-23 08:56:36

I would like to detect if the user has enabled Reduce Transparency. It's simple you just call the func UIAccessibilityIsReduceMotionEnabled() and it returns a Bool. But my app targets iOS 7 and 8 and this function isn't available on iOS 7.

我想检测用户是否启用了Reduce透明性。它很简单,只需调用func UIAccessibilityIsReduceMotionEnabled(),它返回一个Bool。但我的应用程序针对的是iOS 7和8,iOS 7上没有这个功能。

In Objective-C, this is how I checked to see if that function exists:

在Objective-C中,我检查这个函数是否存在:

if (UIAccessibilityIsReduceMotionEnabled != NULL) { }

In Swift, I can't figure out how to check if it exists or not. According to this answer, you can simply use optional chaining and if it's nil then it doesn't exist, but that is restricted to Obj-C protocols apparently. Xcode 6.1 doesn't like this:

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

let reduceMotionDetectionIsAvailable = UIAccessibilityIsReduceMotionEnabled?()

It wants you to remove the ?. And of course if you do so it will crash on iOS 7 because that function doesn't exist.

它想让你移除?。当然,如果你这样做,它会在ios7上崩溃,因为那个函数不存在。

What is the proper way to check if these types of functions exist?

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

4 个解决方案

#1


8  

A proper check for availability has been added in Swift 2. This is recommended over other options mentioned here.

在Swift 2中添加了适当的可用性检查。与这里提到的其他选项相比,这是推荐的。

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

#2


5  

If you're okay with being a little bit cheeky, you can always open the UIKit binary using the library loader and see if it can resolve the symbol:

如果你觉得有点厚颜无耻,你可以用库加载程序打开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!")
}

The dlopen and dlsym calls can be kinda expensive though so I would recommend keeping the dlopen handle open for the life of the application and storing somewhere the result of trying to dlsym. If you don't, make sure you dlclose it.

dlopen和dlsym调用可能有点昂贵,因此我建议在应用程序的生命周期中保持dlopen句柄,并将尝试dlsym的结果存储在某个地方。如果没有,请确保关闭它。

As far as I know this is AppStore safe, since UIAccessibilityIsReduceMotionEnabled is a public API.

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

#3


2  

You could check to see if you're running in iOS 8 or higher --

你可以检查一下你是否在运行iOS 8或者更高的版本

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

I don't think there's another way to tell. So in theory, if you were able to check, trying to access the function name without the () would give you nil in iOS 7 and the () -> Bool function in iOS 8. However, in order for that to happen, UIAccessibilityIsReduceMotionEnabled would need to be defined as (() -> Bool)?, which it isn't. Testing it out yields a function instance in both versions of iOS that crashes if called in iOS 7:

我不认为有其他的方法来判断。理论上,如果你能检查,不带()访问函数名会在ios7中得到nil ios8中的()-> Bool函数。但是,为了实现这一点,需要将uiaccessibilityisemotionenabled()定义为()-> Bool)?,它不是。如果在iOS 7中调用,测试它就会产生一个在两个版本的iOS中崩溃的函数实例:

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

The only way I can see to do it without testing the version is simply to define your own C function to check in your bridging header file, and call that:

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

// ObjC
static inline BOOL reduceMotionDetectionIsAvailable() {
    return (UIAccessibilityIsReduceMotionEnabled != NULL);
}

// Swift
var reduceMotionEnabled = false
if reduceMotionDetectionIsAvailable() {
    reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}

#4


0  

From the Apple Developer docs (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):

来自Apple Developer docs(使用Swift与Cocoa, Objective-C (Swift 3) >互操作性>采用Cocoa设计模式> API可用性):

Swift code can use the availability of APIs as a condition at run-time. Availability checks can be used in place of a condition in a control flow statement, such as an if, guard, or while statement.

Swift代码可以将api的可用性作为运行时的条件。可用性检查可用于替换控制流语句中的条件,如if、guard或while语句。

Taking the previous example, you can check availability in an if statement to call requestWhenInUseAuthorization() only if the method is available at runtime:

以前面的示例为例,只有当方法在运行时可用时,才能检查if语句中的可用性来调用requestWhenInUseAuthorization():

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

Alternatively, you can check availability in a guard statement, which exits out of scope unless the current target satisfies the specified requirements. This approach simplifies the logic of handling different platform capabilities.

或者,您可以检查一个保护语句中的可用性,该语句退出范围,除非当前目标满足指定的需求。这种方法简化了处理不同平台功能的逻辑。

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

Each platform argument consists of one of platform names listed below, followed by corresponding version number. The last argument is an asterisk (*), which is used to handle potential future platforms.

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

Platform Names:

平台名称:

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

#1


8  

A proper check for availability has been added in Swift 2. This is recommended over other options mentioned here.

在Swift 2中添加了适当的可用性检查。与这里提到的其他选项相比,这是推荐的。

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

#2


5  

If you're okay with being a little bit cheeky, you can always open the UIKit binary using the library loader and see if it can resolve the symbol:

如果你觉得有点厚颜无耻,你可以用库加载程序打开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!")
}

The dlopen and dlsym calls can be kinda expensive though so I would recommend keeping the dlopen handle open for the life of the application and storing somewhere the result of trying to dlsym. If you don't, make sure you dlclose it.

dlopen和dlsym调用可能有点昂贵,因此我建议在应用程序的生命周期中保持dlopen句柄,并将尝试dlsym的结果存储在某个地方。如果没有,请确保关闭它。

As far as I know this is AppStore safe, since UIAccessibilityIsReduceMotionEnabled is a public API.

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

#3


2  

You could check to see if you're running in iOS 8 or higher --

你可以检查一下你是否在运行iOS 8或者更高的版本

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

I don't think there's another way to tell. So in theory, if you were able to check, trying to access the function name without the () would give you nil in iOS 7 and the () -> Bool function in iOS 8. However, in order for that to happen, UIAccessibilityIsReduceMotionEnabled would need to be defined as (() -> Bool)?, which it isn't. Testing it out yields a function instance in both versions of iOS that crashes if called in iOS 7:

我不认为有其他的方法来判断。理论上,如果你能检查,不带()访问函数名会在ios7中得到nil ios8中的()-> Bool函数。但是,为了实现这一点,需要将uiaccessibilityisemotionenabled()定义为()-> Bool)?,它不是。如果在iOS 7中调用,测试它就会产生一个在两个版本的iOS中崩溃的函数实例:

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

The only way I can see to do it without testing the version is simply to define your own C function to check in your bridging header file, and call that:

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

// ObjC
static inline BOOL reduceMotionDetectionIsAvailable() {
    return (UIAccessibilityIsReduceMotionEnabled != NULL);
}

// Swift
var reduceMotionEnabled = false
if reduceMotionDetectionIsAvailable() {
    reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled()
}

#4


0  

From the Apple Developer docs (Using Swift with Cocoa and Objective-C (Swift 3) > Interoperability > Adopting Cocoa Design Patterns > API Availability):

来自Apple Developer docs(使用Swift与Cocoa, Objective-C (Swift 3) >互操作性>采用Cocoa设计模式> API可用性):

Swift code can use the availability of APIs as a condition at run-time. Availability checks can be used in place of a condition in a control flow statement, such as an if, guard, or while statement.

Swift代码可以将api的可用性作为运行时的条件。可用性检查可用于替换控制流语句中的条件,如if、guard或while语句。

Taking the previous example, you can check availability in an if statement to call requestWhenInUseAuthorization() only if the method is available at runtime:

以前面的示例为例,只有当方法在运行时可用时,才能检查if语句中的可用性来调用requestWhenInUseAuthorization():

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

Alternatively, you can check availability in a guard statement, which exits out of scope unless the current target satisfies the specified requirements. This approach simplifies the logic of handling different platform capabilities.

或者,您可以检查一个保护语句中的可用性,该语句退出范围,除非当前目标满足指定的需求。这种方法简化了处理不同平台功能的逻辑。

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

Each platform argument consists of one of platform names listed below, followed by corresponding version number. The last argument is an asterisk (*), which is used to handle potential future platforms.

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

Platform Names:

平台名称:

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