处理Xcode≥ 7.3中的私有框架

18
在 Xcode 7.3 / iOS 9.3 中,苹果已经从 iOS SDK 中删除了所有私有框架。出于研究目的(而不是 App Store!),我需要使用一个私有框架(即 BluetoothManager.framework,但这对于任何其他私有框架也是个问题)。

由于这些框架不再包含在 iOS SDK 中,如果我的项目明确地链接到该框架,我会遇到构建(链接器)错误。

有没有长期解决方案的想法?


3
就我所知,为了等待更多的运气,我通过在XCode 7.3中安装iOS9.2 SDK来暂时解决了这个问题:从那里可以使用私有框架。 - JBA
1
相关问题:https://dev59.com/EJXfa4cB1Zd3GeqPdlqJ - newenglander
1个回答

26
您可以通过动态链接到私有框架来解决此问题,而不是常见的构建时链接方式。在构建时,链接器需要在开发 Mac 上存在 BluetoothManager.framework 才能使用它。使用动态链接,您可以将此过程推迟到运行时。在设备上,iOS 9.3 仍然具有该框架(当然还有其他框架)。
以下是您可以在 Github 上修改项目的方法:
1)在 Xcode 的项目导航器中,在 Frameworks 下,删除对 BluetoothManager.framework 的引用。它可能已经以红色显示(未找到)。
2)在项目的 Build Settings 下,您将旧的私有框架目录明确列为框架搜索路径。删除它。如果您无法找到它,请在构建设置中搜索“PrivateFrameworks”。

3) 确保添加所需的实际头文件,以便编译器理解这些私有类。我相信你可以在这里找到当前的头文件。即使 Mac SDK 中的框架被移除,我认为此人已经在设备上使用了类似 Runtime Browser 的工具来生成头文件。在你的情况下,请将 BluetoothManager.h 和 BluetoothDevice.h 头文件添加到 Xcode 项目中。

3a) 注意:生成的头文件有时无法编译。我不得不注释掉上面的 Runtime Browser headers 中的一些 struct typedefs 才能使项目构建成功。感谢下面的 @Alan_s。

4) 将你的导入从:

#import <BluetoothManager/BluetoothManager.h>

#import "BluetoothManager.h"

5) 当您使用私有类时,您需要首先动态打开框架。为此,请在MDBluetoothManager.m中使用以下代码:

#import <dlfcn.h>

static void *libHandle;

// A CONVENIENCE FUNCTION FOR INSTANTIATING THIS CLASS DYNAMICALLY
+ (BluetoothManager*) bluetoothManagerSharedInstance {
   Class bm = NSClassFromString(@"BluetoothManager");
   return [bm sharedInstance];
}

+ (MDBluetoothManager*)sharedInstance
{
   static MDBluetoothManager* bluetoothManager = nil;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
      // ADDED CODE BELOW
      libHandle = dlopen("/System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager", RTLD_NOW);
      BluetoothManager* bm = [MDBluetoothManager bluetoothManagerSharedInstance];
      // ADDED CODE ABOVE
      bluetoothManager = [[MDBluetoothManager alloc] init];
   });
   return bluetoothManager;
}

我在单例方法中调用了dlopen,但你可以把它放在其他地方。它只需要在任何代码使用私有API类之前被调用即可
我添加了一个方便的方法[MDBluetoothManager bluetoothManagerSharedInstance],因为你将会多次调用它。当然,你也可以找到替代实现。关键细节在于这个新方法使用NSClassFromString()动态实例化私有类。
6) 无论你在哪里直接调用 [BluetoothManager sharedInstance],都将其替换为新的[MDBluetoothManager bluetoothManagerSharedInstance]调用。
我用Xcode 7.3/iOS 9.3 SDK测试了这个项目,在我的iPhone上运行良好。

更新

由于似乎存在一些混淆,(截至本文)这个技术和完整代码仍可在iOS 10.0-11.1中使用。
此外,强制加载框架的另一种选择是使用[NSBundle bundleWithPath:]而不是dlopen()。不过,请注意路径的轻微差异:
handle = dlopen("/System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager", RTLD_NOW);
NSBundle *bt = [NSBundle bundleWithPath: @"/System/Library/PrivateFrameworks/BluetoothManager.framework"];

1
我认为那会起作用,但当然这是一个非常脆弱的解决方案。任何API更改都将是灾难性的。 - trojanfoe
1
@trojanfoe,每次发布新的iOS版本时,您需要获取更新的私有标头(从我链接的网站...它们会在每个版本中重新生成)。如果您及时更新新的标头,那么就没问题了。根据定义,使用私有API是脆弱的。如果您需要的功能在公共API中不存在(正如在这里的情况),则可以这样做。 - Nate
1
非常好的清晰解决方案,谢谢!! @trojanfoe: 一旦你开始玩弄被禁止的Holly私有框架,那么你就必须自认倒霉——这就是要付出的代价 :) - JBA
1
感谢发布这个问题 - 我在 BluetoothDevice.h 文件中遇到了编译器错误 BTDeviceImpl 的重新定义。看起来结构体已经在文件的顶部和接口内部进行了定义。只需稍作调整,问题就能解决。你可以 fork 并提交一个 PR - 或者如果你愿意,我可以帮你提交(但是你应该得到这份功劳!) - Alan Scarpa
1
@newenglander,这个方法仍然有效。私有框架目录在Mac上已经在9.3中删除了二进制文件。这种技术只会在设备上打开它们,因为它们仍然存在,并且永远不会被删除。注意:我听说过使用dlopen()的应用程序已被拒绝,因此我已经在我的应用程序中将其替换为通过[NSBundle bundleWithPath:]调用加载库的方法,这基本上是相同的。 - Nate
显示剩余15条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接