探索 Availability Checking 内部实现

这篇文章我们一步一步探索@avaliable的本质。

WWDC 2017: What’s New in LLVM 中苹果介绍了一种新的API可用性检查方法,使用@avaliable等类似的语法。详细可见这篇文档 Marking API Availability in Objective-C

其中 @available()可用于判断语句中,如下:

if (@available(iOS 11, *)) {
    // Use iOS 11 APIs.
} else {
    // Alternative code for earlier versions of iOS.
}

相信这也是大家最熟悉的,那么@avaliable到底是什么呢?

最简单的例子

我们新建一个iOS工程,然后AppDelegate里调用下如下代码:

void test_available() {
    if (@available(iOS 11, *)) {
        // Use iOS 11 APIs.
        NSLog(@"ios11");
    } else {
        // Alternative code for earlier versions of iOS.
        NSLog(@"ios other");
    }
}

例子代码见这里

逆向看看

编译(模拟器即可)后,使用Hopper打开生成的可执行文件,找到这个 test_available,如图:

看下反编译的伪代码:

可知调用了 ___isOSVersionAtLeast 这个函数,0xb就是iOS 11,估计后面两个0x0就是第二位和第三位版本了。

然后Hopper找到这个函数,

一眼看去,猜测大概就是dispatch_once里获取了当前系统的版本,然后赋值给_GlobalMajor_GlobalMinor_GlobalSubminor三个全局变量。

那么dispatch_once里是怎么获取的系统版本呢?Hopper的反汇编出的伪代码似乎看不出block中执行了什么了。切换到汇编代码视图,可见:

这里调用了 _parseSystemVersionPList函数,继续看这个函数:

看到有对这个文件的操作/System/Library/CoreServices/SystemVersion.plist,而且有个fopen。那我们运行Xcode,断点到这个fopen。

这个文件路径如下:

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/CoreServices/SystemVersion.plist

我们打开看看,

那就明白咯,最终是访问这个文件,获取其中的ProductVersion,并通过sscanf解析出三个_GlobalMajor_GlobalMinor_GlobalSubminor三个数值。

sscanf 好古老呀,看到他,好像回到了刚学C语言的那一年。

阶段总结

从目前的分析来看,@available(iOS 11, *)最终将变为如下伪代码:

_GlobalMajor
_GlobalMinor
_GlobalSubminor

void _parseSystemVersionPList() {
    char *path = ".../System/Library/CoreServices/SystemVersion.plist";
    fp = fopen(path)
    read from fp
    parse ProductVersion
    sscanf into _GlobalMajor,_GlobalMinor,_GlobalSubminor
}

BOOL ___isOSVersionAtLeast(major,minor,subminor) {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _parseSystemVersionPList();     
    });
    return compare major,minor,subminor with _GlobalMajor,_GlobalMinor,_GlobalSubminor
}

Clang 怎么处理

相关代码我们从这里搜https://github.com/llvm-mirror/clang/

用于表示avaliable的AST:AvailabilitySpec

https://github.com/llvm-mirror/clang/blob/master/include/clang/AST/Availability.h

还搜到了创建函数的代码,

具体我对llvm也不是太熟,就不细说了(也说不出来哈),感兴趣自行搜索啦。

再总结

了解了这个本质,以后用起来就更自信了哈。很有趣。


欢迎订阅 :)