Source code
Revision control
Copy as Markdown
Other Tools
/// Check if APIs from the given operating system versions are available.
///
/// Apple adds new APIs with new OS releases, and as a developer, you often
/// want to use those to give your users the best behaviour, while still
/// supporting older OS versions that don't have those APIs (instead of
/// crashing e.g. because of an undefined selector).
///
/// This macro allows you to conditionally execute code depending on if the
/// current OS version is higher than or equal to the version given in the
/// macro.
///
/// If no version is specified for a certain OS, the API will be assumed to be
/// unavailable there. This default can be changed by adding a trailing `..`
/// to the macro invocation.
///
/// This is very similar to `@available` in Objective-C and `#available` in
/// Swift, see [Apple's documentation][apple-doc]. Another great introduction
/// to availability can be found in [here][epir-availability].
///
/// [apple-doc]: https://developer.apple.com/documentation/xcode/running-code-on-a-specific-version#Require-a-minimum-operating-system-version-for-a-feature
///
///
/// # Operating systems
///
/// The operating system names this macro accepts, the standard environment
/// variables that you use to raise the deployment target (the minimum
/// supported OS version) and the current default versions are all summarized
/// in the table below.
///
/// | OS Value | Name | Environment Variable | Default |
/// | ---------- | ----------------------- | ---------------------------- | ------- |
/// | `ios` | iOS/iPadOS/Mac Catalyst | `IPHONEOS_DEPLOYMENT_TARGET` | 10.0 |
/// | `macos` | macOS | `MACOSX_DEPLOYMENT_TARGET` | 10.12 |
/// | `tvos` | tvOS | `TVOS_DEPLOYMENT_TARGET` | 10.0 |
/// | `visionos` | visionOS | `XROS_DEPLOYMENT_TARGET` | 1.0 |
/// | `watchos` | watchOS | `WATCHOS_DEPLOYMENT_TARGET` | 5.0 |
///
/// The default version is the same as that of `rustc` itself.
///
///
/// # Optimizations
///
/// This macro will statically be set to `true` when the deployment target is
/// high enough.
///
/// If a runtime check is deemed necessary, the version lookup will be cached.
///
///
/// # Alternatives
///
/// Instead of checking the version at runtime, you could do one of the
/// following instead:
///
/// 1. Check statically that you're compiling for a version where the API is
/// available, e.g. by checking the `*_DEPLOYMENT_TARGET` variables in a
/// build script or at `const` time.
///
/// 2. Check at runtime that a class, method or symbol is available, using
/// e.g. [`AnyClass::get`], [`respondsToSelector`] or [weak linking].
///
/// [`AnyClass::get`]: crate::runtime::AnyClass::get
/// [`respondsToSelector`]: crate::runtime::NSObjectProtocol::respondsToSelector
///
///
/// # Examples
///
/// Use the [`effectiveAppearance`] API that was added in macOS 10.14.
///
/// ```
/// # #[cfg(available_in_frameworks)]
/// use objc2_app_kit::{NSApplication, NSAppearance, NSAppearanceNameAqua};
/// use objc2::available;
///
/// let appearance = if available!(macos = 10.14) {
/// // Dark mode and `effectiveAppearance` was added in macOS 10.14.
/// # #[cfg(available_in_frameworks)]
/// NSApplication::sharedApplication(mtm).effectiveAppearance()
/// } else {
/// // Fall back to `NSAppearanceNameAqua` on macOS 10.13 and below.
/// # #[cfg(available_in_frameworks)]
/// NSAppearance::appearanceNamed(NSAppearanceNameAqua).unwrap()
/// };
/// ```
///
/// Use an API added in Xcode 16.0 SDKs.
///
/// We use `..` here in case Apple adds a new operating system in the future,
/// then we probably also want the branch to be taken there.
///
/// ```
/// use objc2::available;
///
/// if available!(ios = 18.0, macos = 15.0, tvos = 18.0, visionos = 2.0, watchos = 11.0, ..) {
/// // Use some recent API here.
/// }
/// ```
///
/// Set the [`wantsExtendedDynamicRangeContent`] property, which is available
/// since iOS 16.0, macOS 10.11 and visionOS 1.0, but is not available on tvOS
/// and watchOS.
///
/// ```
/// use objc2::available;
///
/// if available!(ios = 16.0, macos = 10.11, visionos = 1.0) {
/// # #[cfg(available_in_frameworks)]
/// layer.setWantsExtendedDynamicRangeContent(true);
/// }
/// ```
///
/// Work around problems in a specific range of versions (an example of this
/// in the real world can be seen in [#662]).
///
/// ```
/// use objc2::available;
///
/// if available!(macos = 15.0) && !available!(macos = 15.1) {
/// // Do something on macOS 15.0 and 15.0.1.
/// } else {
/// // Do something else on all other versions.
/// }
/// ```
///
/// [`effectiveAppearance`]: https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance?language=objc
/// [`wantsExtendedDynamicRangeContent`]: https://developer.apple.com/documentation/quartzcore/cametallayer/1478161-wantsextendeddynamicrangecontent
#[doc(alias = "@available")] // Objective-C
#[doc(alias = "#available")] // Swift
#[macro_export]
macro_rules! available {
(
// Returns `false` on unspecified platforms.
$(
$os:ident $(= $major:literal $(. $minor:literal $(. $patch:literal)?)?)?
),* $(,)?
) => {
$crate::__macro_helpers::is_available({
// TODO: Use inline const once in MSRV
#[allow(clippy::needless_update)]
const VERSION: $crate::__macro_helpers::AvailableVersion = $crate::__macro_helpers::AvailableVersion {
$(
// Doesn't actually parse versions this way, but is
// helpful to write it like this for documentation.
//
// We use optionality for the version here, to allow
// rust-analyzer to work with partially filled macros.
$os: $($crate::__available_version!($major $(. $minor $(. $patch)?)?))?,
)*
// A version this high will never be lower than the deployment
// target, and hence will always return `false` from
// `is_available`.
.. $crate::__macro_helpers::AvailableVersion::MAX
};
VERSION
})
};
(
// Returns `true` on unspecified platforms because of the trailing `..`.
$(
$os:ident $(= $major:literal $(. $minor:literal $(. $patch:literal)?)?)?,
)*
..
) => {
$crate::__macro_helpers::is_available({
#[allow(clippy::needless_update)]
const VERSION: $crate::__macro_helpers::AvailableVersion = $crate::__macro_helpers::AvailableVersion {
$(
$os: $($crate::__available_version!($major $(. $minor $(. $patch)?)?))?,
)*
// A version of 0.0.0 will always be lower than the deployment
// target, and hence will always return `true` from
// `is_available`.
//
// We do this when `..` is specified.
.. $crate::__macro_helpers::AvailableVersion::MIN
};
VERSION
})
};
}
/// Both `tt` and `literal` matches either `$major` as an integer, or
/// `$major.$minor` as a float.
///
/// As such, we cannot just take `$major:tt . $minor:tt . $patch:tt` and
/// convert that to `OSVersion` directly, we must convert it to a string
/// first, and then parse that.
///
/// We also _have_ to do string parsing, floating point parsing wouldn't be
/// enough (because e.g. `10.10` would result in the float `10.1` and parse
/// wrongly).
///
/// Note that we intentionally `stringify!` before passing to `concat!`, as
/// that seems to properly preserve all zeros in the literal.
#[doc(hidden)]
#[macro_export]
macro_rules! __available_version {
// Just in case rustc's parsing changes in the future, let's handle this
// generically, instead of trying to split each part into separate `tt`.
($($version_part_or_period:tt)*) => {
$crate::__macro_helpers::OSVersion::from_str($crate::__macro_helpers::concat!($(
$crate::__macro_helpers::stringify!($version_part_or_period),
)*))
};
}