在我之前关于苹果在 iOS 14 中使用了 Swift 和 SwiftUI 的文章中,我统计了 iOS 中使用 Swift 和 SwiftUI 的内置应用程序的数量。一些读者问我是否可以提供一个百分比而不是一个绝对数字。
在这篇新文章中,我将通过测量 iOS 中二进制文件的总数来回答这个问题。我将更进一步,并计算使用其他编程语言的二进制文件的数量:Objective-C、C 和 C。
最后,为了尽可能完整,我对所有主要的 iOS 版本进行了分析,从 iPhone OS 1.0 到 iOS 14。这将详细概述不同编程语言在十多年的 iOS 开发中的演变。
范围
在开始分析之前,让我澄清一下范围。首先我我只分析了通常意义上的 'iOS',即内核上方的所有内容。不包括 XNU 内核和低级固件(SecureROM、基带、...)。另一方面,我计算了位于 dyld 共享缓存中的框架,这是 iPhone OS 3.1 中引入的缓存机制,并且仍在 iOS 14 中使用。
另一点是您可能会注意到没有 Objective-C 。我使用的方法不能真正检测使用 Objective-C 的二进制文件。所以我把这些二进制文件算作同时使用了 Objective-C 和 C 。
最后,您应该对这种分析持保留态度。虽然我相信整体情况是准确的,但我的方法有一些局限性,因为我无法访问 iOS 源代码并且依赖于不精确的编译工件。
检测和统计二进制文件数量
要解决的第一个问题是计算 iOS 中二进制文件的数量。因为我在设备外运行分析,所以我不能依赖正确设置的权限。
相反,我使用了二进制文件必须是 Mach-O 文件才能执行的事实。通过file
在 Bash 脚本中使用命令行工具来检查文件是否是 Mach-O 二进制文件是微不足道的:
#---------------------------------------------------------------------
# Function to check if a file (passed as argument $1) is an executable
# It returns the number of occurrences of the string 'Mach-O' returned by
# the output of the file command. File would generally return a line like
# - Mach-O 64-bit executable arm64e (/usr/bin/sysdiagnose)
# - Mach-O 64-bit dynamically linked shared library arm64e (/usr/lib/libobjc-trampolines.dylib)
# - Mach-O 64-bit bundle arm64e (/usr/lib/xpc/support.bundle/support)
# - Mach-O 64-bit dynamic linker arm64e (/usr/lib/dyld)
# - Mach-O 64-bit kext bundle arm64e (/System/Library/Extensions/AppleIDV.kext/AppleIDV)
# - Mach-O 64-bit dynamically linked shared library stub arm64e (/Applications/FTMInternal-4.app/libMobileGestalt.dylib)
#---------------------------------------------------------------------
isExecutable ()
{
file $1 2>/dev/null | grep -o "Mach-O" | wc -l
}
我在每个主要 iOS 版本的所有文件中运行此脚本,以计算二进制文件的数量并获得随时间的演变:
如您所见,iPhone OS 1.0 包含相当少量的二进制文件。但 iOS 变得越来越复杂,iOS 14.0 现在包含的二进制文件是 iPhone OS 1.0 的 20 倍。
检测不同的编程语言
在我之前关于苹果在 iOS 14 中使用 Swift 和 SwiftUI 的文章中,我统计了使用 Swift 和 SwiftUI 的内置应用程序。在本文中,我决定更进一步,并计算使用 Objective-C、C 和 C 的二进制文件的数量。
请记住,您可以混合不同的编程语言来创建二进制文件。一个示例是创建一个应用程序,该应用程序使用 C 作为低级引擎,使用 Objective-C 作为 UI。另一个很好的例子是Clatters,我使用了一些用 Swift、SwiftUI、Objective-C 和 C 编写的代码,这些代码是用来解决每个特定问题的最合适的编程语言。
检测 SwiftUI
在我之前的帖子中,我解释了如何通过检查二进制文件是否动态链接到/System/Library/Frameworks/SwiftUI.framework/Versions/A/SwiftUI
. 这可以在 Bash 脚本中实现,例如:
isSwiftUI=$( echo "${otoolOutput}" | grep -o "/System/Library/Frameworks/SwiftUI.framework/SwiftUI" | wc -l )
尽管 SwiftUI 并不是真正的编程语言,而是一种用户界面工具包,但我决定将其包含在本文中。
检测 Swift
检测 Swift 的使用可以使用类似的方法来完成,如2016 年的一篇旧帖子所述。然而,由于 Swift 库在 iOS 历史上已被移动到不同的位置,因此该脚本稍微复杂一些:
代码语言:javascript复制isSwift=$( echo "${otoolOutput}" | grep -o "/usr/lib/swift/" | wc -l )
if [ ${isSwift} == 0 ]
then
# On iOS 11, the Swift dylib were inside "/System/Library/PrivateFrameworks/Swift/"
isSwift=$( echo "${otoolOutput}" | grep -o "/System/Library/PrivateFrameworks/Swift/" | wc -l )
if [ ${isSwift} == 0 ] && [[ $1 != */libswiftUIKit.dylib ]]
then
# On iOS 9, the Swift dylib were built into the app
isSwift=$( echo "${otoolOutput}" | grep -o "@rpath/libswiftUIKit.dylib" | wc -l )
fi
fi
检测 Objective-C
检测二进制文件是否使用 Objective-C 非常简单,如Apple 文档中所述:
Objective-C 运行时是一个运行时库,它为 Objective-C 语言的动态属性提供支持,因此所有 Objective-C 应用程序都链接到它。Objective-C 运行时库支持函数在位于 /usr/lib/libobjc.A.dylib 的共享库中实现。
因此,一条简单的线就可以使用 Objective-C 检测所有应用程序:
代码语言:javascript复制isObjectiveC=$( echo "${otoolOutput}" | grep -o "/usr/lib/libobjc." | wc -l )
检测 C
与 Objective-C 类似,我们可以依赖这样一个事实,即使用 C 的应用程序必须使用 C 标准库。在现代 iOS 版本中,C 标准库 libc 位于/usr/lib/libc .1.dylib
. 在 iOS 7 及更早版本上,使用了 gcc 的 libstdc 并位于/usr/lib/libstdc .6.dylib
.
如果您想知道为什么在下面的脚本中没有使用确切的路径,原因是标记libc abi.dylib
为使用 C 。libc abi是对标准 C 库的低级支持,似乎是用 C 编写的。
isCPP=$( echo "${otoolOutput}" | grep -o "/usr/lib/libc " | wc -l )
if [ ${isCPP} == 0 ]
then
isCPP=$( echo "${otoolOutput}" | grep -o "/usr/lib/libstdc " | wc -l )
fi
检测 C
在不涉及复杂细节的情况下,Objective-C 和 C 是 C 的超集。这意味着 Objective-C 或 C 应用程序正在使用 C 代码。所以我们无法知道 Objective-C 或 C 应用程序是否使用了一些纯 C 代码。但是,有些应用程序完全是用 C 编写的。这些通常是低级命令行工具,例如/usr/bin/zprint
.
如何检测这样的二进制文件?如果二进制文件既不链接到 Objective-C 运行时库也不链接到 C 标准库,而是使用libc
位于的标准 C 库/usr/lib/libSystem.B.dylib
,那么我们可以假设这个二进制文件完全用 C 编写。检测完全用 C 编写的应用程序C,我最终得到了这些行:
isC=$( echo "${otoolOutput}" | grep -o "/usr/lib/libSystem." | wc -l )
if [ ${isC} == 0 ]
then
isC=$( echo "${otoolOutput}" | grep -o "/usr/lib/system/" | wc -l )
fi
您还会注意到,我将位于内部的所有动态库硬编码/usr/lib/system/
为用 C 编写。这些低级库没有链接到,/usr/lib/libSystem.B.dylib
但很可能是用 C 编写的。
特别案例
在最终脚本中,您将看到一些特殊情况:
/usr/lib/libobjc-trampolines.dylib
: 这个库完全是用汇编写的,你可以在这个文件objc-blocktramps-arm64.s 中阅读它的 arm64 源代码/usr/lib/dyld
: dyld 是动态链接器,是一个非常特殊的二进制文件。由于它是开源的,我们可以很容易地确认它包含 C 代码。/System/Library/Caches/com.apple.xpc/sdk.dylib
和/System/Library/Caches/com.apple.xpcd/xpcd_cache.dylib
:这两个库似乎是自动生成的库和 dyld 的一部分。/System/Library/Extensions/*
:有几个(内核?)iOS 使用的扩展,比如/System/Library/Extensions/AppleIDV.kext
. 所有这些二进制文件似乎都使用 C 。
最终脚本
您可以在此处下载循环遍历文件夹中所有文件的完整脚本。此脚本打印找到的所有二进制文件的路径,并告诉您使用的编程语言,例如:
代码语言:javascript复制/Applications/AccountAuthenticationDialog.app/AccountAuthenticationDialog |Objective-C
/Applications/ActivityMessagesApp.app/ActivityMessagesApp |Objective-C
/Applications/ActivityMessagesApp.app/PlugIns/ActivityMessagesExtension.appex/ActivityMessagesExtension |Objective-C|Swift
/Applications/AnimojiStickers.app/AnimojiStickers |Objective-C
/Applications/AnimojiStickers.app/PlugIns/AnimojiStickersExtension.appex/AnimojiStickersExtension |Objective-C
/Applications/AppSSOUIService.app/AppSSOUIService |Objective-C
/Applications/AppStore.app/AppStore |Objective-C|Swift
/Applications/AppStore.app/PlugIns/ProductPageExtension.appex/ProductPageExtension |Objective-C|Swift
/Applications/AppStore.app/PlugIns/SubscribePageExtension.appex/SubscribePageExtension |Objective-C|Swift
[...]
原始结果
我在从 iPhone OS 1.0 到 iOS 14.0 的所有主要 iOS 版本上运行了这个脚本。如果您对原始数据感兴趣,可以在此处下载:
版本 | 设备 | 原始数据 |
---|---|---|
iOS 14.0 (18A373) | iPhone X | iOS14.txt |
iOS 13.1 (17A844) | iPhone X | iOS13.txt |
iOS 12.0 (16A366) | iPhone X | iOS12.txt |
iOS 11.1 (15B93) | iPhone X | iOS11.txt |
iOS 10.1 (14B72) | iPhone 5S | iOS10.txt |
iOS 9.0 (13A344) | iPhone 5S | iOS9.txt |
iOS 8.0 (12A365) | iPhone 5S | iOS8.txt |
iOS 7.0.1 (11A470a) | iPhone 5S | iOS7.txt |
iOS 6.0 (10A403) | iPhone 3GS | iOS6.txt |
iOS 5.0 (9A334) | iPhone 3GS | iOS5.txt |
iOS 4.0 (8A293) | iPhone 3GS | iOS4.txt |
iPhone 操作系统 3.0 (7A341) | iPhone 3GS | iOS3.txt |
iPhone 操作系统 2.0 (5A347) | iPhone 2G | iOS2.txt |
iPhone 操作系统 1.0 (1A543a) | iPhone 2G | iOS1.txt |
iOS 14 中的编程语言分布
现在我们知道二进制文件的总数和它们的编程语言,我们可以回答关于在 iOS 14 中使用 Swift 和 SwiftUI 的二进制文件百分比的问题。请注意,一个二进制文件可以使用多种编程语言。因此,可以多次计算二进制文件,例如在 Swift 和 Objective-C 类别中。二进制文件的大小和重要性也没有考虑在内。
在 iOS 14 中的所有二进制文件中:
- 88% 使用 Objective-C
- 17% 使用 C
- 8% 使用 Swift
- 8% 完全用 C
- 1% 使用 SwiftUI
一些有趣的点:
- Objective-C 仍然是 iOS 14 中的关键组件。
- C 和 C 也发挥着重要作用。这些语言通常由与音频、视频、电话、Web 和其他底层框架相关的二进制文件使用。
- Swift 已经很快被采用,并且已经被 iOS 14 中 8% 的二进制文件使用。
编程语言的演变
通过在从 iPhone OS 1.0 到 iOS 14.0 的所有 iOS 主要版本上运行该脚本,我们可以看到 iOS 使用的编程语言的演变。再次请注意,单个二进制文件可以计算多次,因此二进制文件的总和大于二进制文件的总数:
二进制文件数量
它告诉我们什么?
- 正如我们已经看到的,iOS 的每个版本都变得越来越复杂。
- iPhone OS 1.0 包含的二进制文件少于使用 Swift 的 iOS 14.0 中的二进制文件数量。
- Swift 的使用在 Apple 正在取得进展,现在使用 Swift 的二进制文件比完全用 C 编写的二进制文件多。但采用需要时间。
- 随着每个 iOS 版本的发布,使用 Objective-C 的二进制文件的数量仍在增长。
- 多年来,C 的使用也在不断增长。
- 另一方面,完全用 C 编写的二进制文件的数量现在停滞不前。
从这张图中可以更容易地看出每种编程语言的演变:
使用各种编程语言的二进制文件数量
WidgetKit 小部件和 Objective-C
在查看数据以确保它们有意义时,我惊讶地发现所有使用 WidgetKit(第一个公共 SwiftUI 专用框架)构建的新 iOS 14 小部件都使用了 Objective-C。
事实证明,如果您使用 Configuration Intent 创建一个 iOS 14 小部件,Xcode 将自动生成一些包含 Objective-C 类的文件,例如:
代码语言:javascript复制@objc(ConfigurationIntent)
public class ConfigurationIntent: INIntent {
}
因此,iOS 14 中的所有 SwiftUI 小部件都至少间接地使用了一些 Objective-C 代码。
结论
即使我们无法访问 iOS 源代码,我们也可以确定 iOS 中使用的编程语言。这种方法显然有一些限制,虽然我相信整体情况是准确的,但我不能保证结果是完全准确的。如果您认为我遗漏了什么,请在 Twitter 上联系我。
然而,我们可以得出几个结论。首先,iOS 变得越来越复杂,iOS 14.0 现在包含的二进制文件是 iPhone OS 1.0 的 20 倍。
有趣的一点是,iOS 14 中 88% 的二进制文件直接或间接依赖于 Objective-C。Objective-C 仍然是 iOS 中的关键编程语言。
还值得注意的是,C 的使用也在这些年来不断增长,iOS 14 中有 17% 的二进制文件使用 C 。
最后,Swift 的使用在 Apple 正在取得进展,但采用需要时间。在 iOS 14 中,大约 8% 的二进制文件使用 Swift。
译自 Evolution of the programming languages from iPhone OS 1.0 to iOS 14