Jer*_*ock 6 macos cocoa frameworks dyld nsbundle
我正在使用苹果Safari框架的macOS工具上工作。在macOS 10.13中运行时,该工具链接到并从中加载
/System/Library/PrivateFrameworks/Safari.framework
Run Code Online (Sandbox Code Playgroud)
一切正常。但是在macOS 10.12.6中运行时,缺少某些行为。基于对DTrace的一些调查,我认为这是因为我的工具需要加载最新的暂存框架,而不是在这里:
/System/Library/StagedFrameworks/Safari/Safari.framework
Run Code Online (Sandbox Code Playgroud)
这显然是Safari所做的,因为如果我使用lldb附加到Safari并运行image list
,则在10.13中,该列表仅包含前一个路径,而在10.12.6中,则仅包含后一个路径。
我尝试了以下方法:
NSBundle* stagedBundle = [NSBundle bundleWithPath:@"/System/Library/StagedFrameworks/Safari/Safari.framework"];
Run Code Online (Sandbox Code Playgroud)
在10.13中返回nil,因为此时没有这样的目录。但是,在10.12.6中,我得到了stagedBundle
,然后:
NSBundle* privateBundle = [NSBundle bundleForClass:[BookmarksController class]];
[privateBundle unload];
[stagedBundle load];
Run Code Online (Sandbox Code Playgroud)
卸载和加载显然有效,因为如果我记录-description
了这两个捆绑包,则在运行该代码之前,将(加载)私有捆绑包,并且(尚未加载)已登台的捆绑包,但是在运行该代码之后,根据需要交换了这些状态。
但这是无效的。(1)如果我再次调用-bundleForClass:
,传递一个已知在两个框架中都存在的类,它将给我Private捆绑包。(2)如果我调用-respondsToSelector:
,传递已知仅存在于暂存框架中的选择器,则不会。
我尝试_CFBundleFlushBundleCaches()
按此处的建议致电,但这没有帮助。
我也尝试过更改目标的FRAMEWORK_SEARCH_PATHS
,并在Mac上安装Staged框架并链接到它,但是由于这篇文章已经太久了,我只能说这导致的热量多于光。
在这种情况下,如何有选择地加载框架?
更新
我尝试了另一种方法。在重新阅读了《 Apple的框架编程指南》之后,即使它看起来确实过时,我还是认为该框架需要弱链接。做过这个:
-load
和-unload
调用-weak_framework Safari
对我来说,它在10.13和10.12.6中都可以构建和运行,但显然仍在10.12.6中加载不需要的Private框架。NSLog将此报告为捆绑包的路径,并且一个类不响应仅在暂存框架中的选择器。
还有其他想法吗?
首先,免责声明:我强烈建议您不要依赖于在交付给用户的任何应用程序中加载私有框架。它非常脆弱且不受支持。
这就是说,如果你真的想这样做,我的建议是使用相同的技术,Safari浏览器本身使用的框架,这是两个副本之间进行选择dyld
的DYLD_VERSIONED_FRAMEWORK_PATH
环境变量。
引用dyld
手册页:
这是用冒号分隔的目录列表,其中包含潜在的覆盖框架。动态链接器在这些目录中搜索框架。对于找到的每个框架,dyld都会查看其框架
LC_ID_DYLIB
并获取current_version
和安装名称。然后,Dyld在安装名称路径中查找框架。current_version
每当需要具有该安装名称的框架时,将在过程中使用值较大的那个。这类似于,DYLD_FRAMEWORK_PATH
而不是始终覆盖,只是覆盖了所提供的框架是较新的框架。注意:dyld不会检查框架的Info.plist来查找其版本。Dyld仅检查-current_version
创建框架时提供的编号。
简而言之,这导致在dyld
要加载的框架和版本化框架路径中的框架之间执行版本检查,并加载更高的版本。如果版本化的框架路径不存在或其中的框架不存在,则将使用原始框架路径。
Safari利用第二个dyld
功能简化DYLD_VERSIONED_FRAMEWORK_PATH
了LC_DYLD_ENVIRONMENT
load命令的使用。此加载命令允许DYLD_*
在链接时指定环境变量,在dyld
尝试加载任何相关库之前,链接时将在运行时应用环境变量。没有这个技巧,您需要DYLD_VERSIONED_FRAMEWORK_PATH
在启动应用程序之前将其设置为环境变量,这通常需要繁琐的重新执行才能实现。
将这两个构建块放在一起,最终会添加如下配置设置:
OTHER_LDFLAGS = -Wl,-dyld_env -Wl,DYLD_VERSIONED_FRAMEWORK_PATH=/System/Library/StagedFrameworks/Safari;
Run Code Online (Sandbox Code Playgroud)
然后,您可以与静态链接/S/L/PrivateFrameworks/Safari.framework
,也可以尝试在运行时动态加载。两者都应导致在运行时加载适当的框架。
为了解决您的问题所引起的一些误解:
卸载和加载显然有效,因为如果我记录了这两个捆绑软件的-description,则在运行该代码之前(已加载)私有捆绑软件和(尚未加载)暂存捆绑软件,但是在运行该代码之后,这些状态会被交换,如预期的。
不支持卸载包含Objective-C代码的共享库。我怀疑它唯一要做的就是导致在NSBundle
实例上切换“已加载”标志,因为在dyld
的级别它被忽略了。
在Build Settings> Framework Search Paths中,列出了两个框架的父目录的路径,在私有路径之前有暂存路径,因为我希望此路径可以在存在两者的macOS 10.12.6中加载。
框架搜索路径是仅在编译时使用的概念。在运行时,库的安装名称告诉您dyld
在哪里找到要加载的二进制文件。