API Availability and Target Conditionals

Writing code for multiple Apple platforms can be tricky, this post aims to provide some guidance and explanations for ways to write code that works on different Apple platforms (iOS, macOS, watchOS, tvOS) and different versions of SDKs, and the OSes at runtime.

Target conditionals

First, let’s take a look at how to tell apart the OS you are compiling for at build time, using the preprocessor (in case of Objective-C) or compilation conditions (Swift).

Swift

In Swift checking the OS you are building for at comile time is done using compilation conditionals and specific platform condition functions:

The os() function can be used to check for the OS the code is compiled for, it takes one argument, which is the operating system name (not quoted). Possible operating system names at the time of writing are macOSiOStvOSwatchOS and Linux.

For example:

Of course sometimes you need to check if you are running the simualtor or not, to do that there is the targetEnvironment() function. It takes one argument, which is the target environment name (not quoted). Possible values are simulator and macCatalyst (since Swift 5.1).

Objective-C, C and C++

For Objective-C, C and C++ conditional compilation is handled by the preprocessor. Apple provides the TargetConditionals.h header which contains specific defines for this.

This header has a lot of defines, I will only list the most useful ones here:

TARGET_OS_OSX
Compiling for macOS
TARGET_OS_IOS
Compiling for iOS
TARGET_OS_TV
Compiling for tvOS
TARGET_OS_WATCH
Compiling for watchOS
TARGET_OS_MACCATALYST
Compiling for Catalyst
TARGET_OS_SIMULATOR
Compiling for Simulator

For example:

To check if compiling for the simulator, just use the TARGET_OS_SIMULATOR define:

Note that there is a TARGET_OS_MAC define, while this sounds like it will be true only for macOS, it is actually true for all Darwin OSes including iOS and tvOS. Another define which can be confusing is the TARGET_OS_IPHONE, which is actually true for all “mobile” platforms, so iOS, tvOS, watchOS and Catalyst.

Clang-specific preprocessor extensions

Since Clang 6.0 or Xcode 9.3 (r320734) Clang has preprocessor extensions similar to the Swift condition functions which can be used to achieve the same as with the target conditional defines above.

To check the OS code is compiled for, there is the __is_target_os() preprocessor macro, which takes a single argument, the operating system name. Possible values for Apple OSes are macOSiOStvOSwatchOS.

For example:

To check what environement the code is compiled for, similar to Swift there is the __is_target_environment() preprocessor macro, which takes as argument the environment name. Possible values are simulator and macabi (Catalyst).

API availability

Something that usually is closely related to above discussed conditional compilation is the need to handle API availability gracefully. There are various aspects to consider about API availability, one is API availability at runtime, another is API availability at compile time depending on the used SDK version.

What is API availability?

API availability, as the name suggests, means if a specific API (function, class, method, etc.) is actually available.

macOS, iOS, tvOS and watchOS handle API availability in the same way, when a new API is introduced it is annotate with a specific macro that indicates the availability of that API. The macro expands to annotations for the API that indicate how the linker is expected to handle linking to it and can provide additional warnings or errors during compilation when using a deprecated API or trying to use a “too new” API in an application set to run on older appleOS versions that lack this API.

This sounds all very abstract and complex, so let’s have a look at this using an example, the clock_gettime() function. If we look at the manpage for clock_gettime we can see that it was introduced in macOS 10.12:

So let’s have a look at how the header declares this function:

So these functions are annotate with __CLOCK_AVAILABILITY, which expands to:

So to be more precise than what the man page tells us, this API is available since macOS 10.12, iOS 10.0, tvOS 10.0 and watchOS 3.0, great!

Of course that still doesn’t provide the full story, to understand what exactly the availability macros do, let’s have a look at the header defining those, Availability.h. Checking this header, we can see that these macros actually expand to (indirectly using a other macros) use of the availability attribute. I recommend reading this for all the details about how exactly this works. The most important takeaway for the purpose of this article is the following:

A declaration can typically be used even when deploying back to a platform version prior to when the declaration was introduced. When this happens, the declaration is weakly linked, as if the weak_import attribute were added to the declaration. A weakly-linked declaration may or may not be present a run-time, and a program can determine whether the declaration is present by checking whether the address of that declaration is non-NULL.

Note that in addition to the Availability.h header, there is the AvailabilityMacros.h header which works similar to the Availability.h header. Depending on the Framework, it might use either the Availability.h or the older AvailabilityMacros.h header.

Checking API availability at runtime

Now let’s see how we can use such a “partially” avaialble function:

If we now compile this targeting macOS 10.14 like that, it just works as expected:

But if we were to try to compile targeting macOS 10.10, we would get a warning:

The -Wunguarded-availability flag is what causes the compiler to emit this warning. For APIs available since macOS 10.13, iOS 11, watchOS 4 and tvOS 11 you will get these warnings even without specifying this flag, as there is a new flag-Wunguarded-availability-new which is enabled by default in recent Clang/Xcode versions.

As the name of the warning already gives it away, it only warns about “unguarded” availability, which implies we can “guard” such API usage. There are two ways how this can be done.

Checking the symbols address

The “old” way to check if a partially available function is available would be to check its address:

Not only is this a bit weird to read, it has some downsides:

  • The compiler will still warn about this
  • Objective-C methods and classes can’t easily be checked this way
  • It is cumbersome to check all relevant symbols this way
Using the availability helper

Fortunately since some time there is a bette way to handle this! In fact, the compiler would already points this out in the partial availability warning:

note: enclose 'clock_gettime' in a __builtin_available check to silence this warning

So let’s do that:

And now it will compile without warning again! On macOS at least, that is. We can check multiple platform versions just by listing them all:

The star at the end is mandatory and means “all other platforms”. So the previous check that just listed macOS would still compile for iOS and crash at runtime when ran on iOS versions lower than iOS 10 which lack clock_gettime. So take care to cover all cases where the code will run in your availability check!

In Objective-C there is the @available helper which looks a bit nicer than the longer version from C but is used in the exact same way:

In Swift there is #available, again the usage is the same except for the different name:

Note that negating the check or using it together with any other condition is not supported and does not properly guards availability!

Additionally keep in mind that this is a runtime check, so using APIs inside a availability check that are missing in the current SDK version that is compiled with is still an error. To support multiple SDK versions, see the next section for how to check at compile-time!

Checking API availability at compile-time

Sometimes it is necessary to check the availability of a specific API at compile-time, for example when you want to remain compatible with multiple Apple SDKs, where some versions offer new API that you want to use and some versions lack this API.

In the previous section I already mentioned two headers, Availability.h and AvailabilityMacros.h. These headers define two important macros:

__<OS-VARIANT>_VERSION_MAX_ALLOWED
Indicates the maximum version that we are allowed to use APIs from.
__<OS-VARIANT>_VERSION_MIN_REQUIRED
Indicates the minimum required version that we are allowed to use APIs from.

The <OS-VARIANT> needs to be replaced with the OS variant we want to check for and can be MAC_OS_XIPHONE_OSTV_OS or WATCH_OS.

The above sounds quite abstract so lets illustrate it with a example. Suppose we have a new API introduced for macOS 10.12, so it is first present in the macOS 10.12 SDK. If we were to compile with that SDK, the __MAC_OS_X_VERSION_MAX_ALLOWED macro is automatically set to the version of the SDK, as that is the maximum macOS version that we can use APIs from, we cannot ever use any APIs newer than the SDK we are using because those are simply not declared. So in case of the 10.12 SDK, __MAC_OS_X_VERSION_MAX_ALLOWED will be 101200.

If we want to stay compatible with older SDKs, we can use the following preprocessor macros:

Note that there are defines for the specific appleOS versions in the availability headers, like __MAC_10_12 so it is tempting to write __MAC_OS_X_VERSION_MAX_ALLOWED >= __MAC_10_12 but this will not work because lower SDK versions, like for example the macOS 10.11 SDK will not have the define for higher macOS versions like macOS 10.12!

What is important to note is that the preprocessor checks are done at compile-time, so proper availability handling at runtime is still needed, see the previous section for details about that!

The second macro, __<OS-VARIANT>_VERSION_MIN_REQUIRED, is useful when you have legacy code that you want to disable when targeting recent enough appleOS versions. Suppose we have function needed for macOS <= 10.11, we can easily disable that when targeting macOS 10.12 or higher by using the __MAC_OS_X_VERSION_MIN_REQUIRED macro:

Of course a lot of other and more complex scenarios are possible with more complex checks, but I won’t cover all of the possibilities here.

Note that the AvailabilityMacros.h header defines MAC_OS_X_VERSION_MIN_REQUIRED without the two leading underscores, but the Availability.h header does not define those. Both define the version with the leading underscores so to prevent confusing code I would recommend to not use the version without the leading underscores.

Note that the above only works for C/C++/Objective-C code, in Swift there is currently no way to check the SDK at compile-time.

参考链接


联想T440在ubuntu 22.04系统上安装配置指纹

家里有一台旧的联想T440笔记本,某天来了兴致安装了ubuntu 22.04,默认情况下指纹识别是无法使用的。

查看硬件属于 Elan 指纹识别设备,Ubuntu 自带的 Libfprint 已经提供了支持。

安装前端工具

执行

将第一项,用空格勾选上,然后点“确定”。这样就授权指纹识别登录设备。

录入指纹

打开“设置---用户---指纹登录”,此时就可以打开了。点击“+”,录入第一个指纹吧

参考链接


联想小新Air2020锐龙版在Ubuntu下添加指纹识别

如何让Android Studio/IntelliJ IDEA不对自定义的NULL检查方法发出警告

在平时的开发过程中,我们一般会自定义函数对变量是否为 null 进行检查,当检查函数返回成功的时候,对象一定不是 null

但是,这个自定义的函数,对于 Android Studio/IntelliJ IDEA 来说,是无法感知到的,导致 Android Studio/IntelliJ IDEA 会发出警告,没有对变量是否为 null 进行检查。

出现上述问题的例子如下:

那么,有没有办法告知 Android Studio/IntelliJ IDEA ,我们已经对 null 进行过检查了呢?

网上搜索许久,尝试过 @CheckForNull / @EnsuresNonNullIf 注解,都不能解决问题。

最终发现使用 jetbrains@Contract 注解能解决此问题。

上述的例子增加以下注解,明确告知编译器,如果参数是 null 函数一定返回 false,就可以阻止编译器发出警告了。

如下:

参考链接


VirtualBox 7.0.4安装macOS High Sierra

硬件要求

Intel E3-1230 v3 可以成功安装, AMD Ryzen 5900 CPULinux 系统上,需要配置 CPU 模仿 Intel CPU 的特性。并且 CPU 部分,不要启用 嵌套VT-x/AMD-V

生成系统安装镜像

macOS (实验使用的系统是 macOS Big Sur (11.7.1))系统上,执行如下命令,生成系统安装镜像

继续阅读VirtualBox 7.0.4安装macOS High Sierra

Failed to download https://chrome-infra-packages.appspot.com/dl/flutter/web/canvaskit_bundle 超时 解决办法

flutter项目下执行 flutter run 提示信号灯超时时间已到

1、问题截图如下:

2、解决办法

终端输入

后回车

再次执行flutter run 启动项目即可成功。

注:

为设置使用国内镜像

参考链接


Failed to download https://chrome-infra-packages.appspot.com/dl/flutter/web/canvaskit_bundle 超时 解决办法

Flutter The Linux toolchain CMake build dependency (CMake 3.14 or higher is required. You are running version 3.10.2)

ubuntu 22.04 通过 snap 安装了 Flutter SDK(当前是Flutter 3.3.4),如果第三方的依赖了 CMake 3.10.2 更高的版本,会在编译的时候报错:

这个报错的原因是由于 snap 安装的 Flutter SDK 构建了一个沙箱环境,在这个环境中的 CMake3.10.2 版本,不管系统安装的是哪个版本的 CMake ,都是无效的。

要解决这个问题,或者等待 snapFlutter SDK 更新版本,或者参照 Linux install Flutter 的说明,手工安装并配置 Flutter SDK

可以参考如下代码:

参考链接


Android各大市场更改APP名称

公司上架了一款App,因为产品运营需要去修改App名称,在iOS应用市场提交新版本的时候可以改App名称。那么Android 各大市场如何更改APP名称呢?

目前上架的Android 应用市场有:360手机助手、腾讯应用宝、百度手机助手、阿里应用分发市场(豌豆荚)、安智市场、小米应用商店、华为应用市场、OPPO商店、魅族应用商店、vivo应用市场、搜狗手机助手等平台。

每家Android应用市场的规则都不同,小编整理了Android 各大市场更改APP名称的规则。

1、 百度 : 百度平台的最简单。直接更新app的版本即可(应用名称是系统从您提交的应用中解析的,如需修改请联系贵司技术修改apk包内信息。)

百度如何修改app名称
百度如何修改app名称

2、应用宝: 用上线后,开发者可通过工单系统提交应用名称修改需求 (入口1:如图点击“名称更改指引”即可直接跳转到修改应用名称的工单系统;入口2:管理中心-->点击需要修改名称的应用-->基础服务-->工单系统-->应用宝商务类-->移动应用名称修改-->填单提交)。修改应用名称需要提供软件著作权; 若暂无软著,可提供此款应用在其他外市场,改过名字后的前后台上线管理截图(其他证明文件均不接受);

腾讯开放平台应用改名
腾讯开放平台应用改名

重要提示:根据平台规则,应用上线后,应用名称最多可支持修改2次,超过修改次数将不再受理,请谨慎确定好需要修改的应用名称后,再提交工单申请。

腾讯移动应用名称修改
腾讯移动应用名称修改

3、华为: 在开发者后台在该应用下点击上架或者升级,上传更名后的APK包,并在应用信息处更改应用名称,如应用分类涉及软著要求,请在“版本信息”的“应用版权证书或代理证书”处更新应用软著及免责函,提交应用审核。

华为应用商店应用改名
华为应用商店应用改名

4、360移动开放平台和百度移动开放平台是一样的,直接更新app的版本即可。

5、 vivo开放平台:

1)应用类APP需要修改名称: 直接在后台编辑更新,保证apk包内和在后台填写的名称一致即可;若名称相差较大,请在版权证明栏补充软著;游戏类APP(网游/单机)需要修改名称:需联系对接商务处理。

2)若需要更改应用包名,请联系贵司技术人员;更改之后再在平台创建应用提交新包名应用,旧包名应用需申请下架(登录平台--管理中心--点击您的应用--“下架申请”即可)。

6、 OPPO开放平台:也是上传后将自动解析包名。

7、魅族开放平台:也是上传后将自动解析包名。

8、小米市场: 应用名称修改需于应用包内及应用信息一同更改。应用包内名称更改还请贵司技术开发人员自行更改。(可以试一下直接上传后将自动解析包名。)

9、豌豆荚(阿里云应用分发平台):也是上传后将自动解析包名。

参考链接


Android 各大市场更改APP名称

Flutter-从数据库中获取记录并使用ListView Builder显示出来

数据保存在数据库中,根据表名称获取所有记录的列表,并在"ListView.builder"中显示的代码如下:

参考链接


Flutter 3.0实现Windows本地化/国际化

参照 Flutter 2.8.1本地化/国际化应用程序名称 可以实现 Android/macOS/iOS/Web 的应用名称相关的国际化。Linux 参考 Flutter 3.0实现Linux本地化/国际化

那么在 Windows 应用上如何相同的功能呢?

下面我们探讨一下一个比较简单的解决方案,就是直接修改RC文件,这个方案 适用于涉及到的语言类型不太多的情况

Windows 代码编译,需要安装 Microsoft Visual Studio 2022 Community,但是不能直接使用 Microsoft Visual Studio 2022 Community 编辑 RC 文件,打开就会报错:

具体的操作方法如下:

我们假定工程的名字为 LanauageTest

首先在项目根目录下,执行

生成 build 目录,然后使用 Microsoft Visual Studio 2022 Community 打开 build\windows\LanauageTest.sln 文件。

接着在资源视图中找到字符串资源(如果不存在则新建),选择默认的 StringTable ,然后右键选择 “插入副本

虽然可以使用上面的操作来添加语言,但是只能作为参考,原因在于 Microsoft Visual Studio 2022 CommunityRC 文件编辑器在修改 LanauageTest.RC 文件的时候,把预定义的宏进行了展开,直接使用宏的实际值替代了宏本身,导致这些数据失去动态变化的能力。我们只能手工再编辑一次,恢复原来的宏才可以。

至于使用的话,可以通过 LoadString 加载定义的字符串资源,更详细参考 Using Resources

参考链接


Flutter 3.0实现Linux本地化/国际化

参照 Flutter 2.8.1本地化/国际化应用程序名称 可以实现 Android/macOS/iOS/Web 的应用名称相关的国际化。但是在 Linux 应用上如何相同的功能,目前暂时没有一个统一的标准。

研究了许久,终于基本上算是搞定,解决方案如下:

使用 gettext 来实现国际化相关的功能。

首先配置,调整工程的目录如下:

project/
project/linux
project/linux/flutter
project/linux/flutter/CMakeLists.txt
project/linux/locale/en_US/app.po
project/linux/locale/zh_CN/app.mo
project/linux/locale/CMakeLists.txt
project/linux/CMakeLists.txt
project/linux/main.cc
project/linux/my_application.cc
project/linux/my_application.h

对应语言 i18n 相关配置文件的内容如下:

接下来,修改 Linux 工程的配置文件,增加对 本地化(i18n) 文件的引用,在合适的位置增加如下代码:

完整的代码参考如下:

使用多语言的代码如下:

参考链接