处理私有框架 Xcode ≥ 7.3

Handling private frameworks in Xcode ≥ 7.3

With Xcode 7.3 / iOS 9.3 Apple removed all private frameworks 来自 iOS SDK。出于研究目的(不是 App Store!),我需要使用私有框架(即 BluetoothManager.framework,但这也是任何其他 private 框架的问题)。

因为这些框架不再在 iOS SDK 中提供,如果我的项目试图 link 显式地使用这个框架,我会得到一个构建(linker)错误。

对长期(呃)长期解决方案有什么想法吗?

您可以通过动态链接到私有框架来解决这个问题,而不是在构建时链接更常见的方式。在构建时,BluetoothManager.framework 需要存在于您的开发 Mac 中,以便链接器能够使用它。使用动态链接,您可以将过程推迟到运行时。在设备上,iOS 9.3 仍然存在该框架(当然还有其他框架)。

以下是如何修改 Github 上的项目:

1) 在 Xcode 的项目导航器中,在框架下,删除对 BluetoothManager.framework 的引用。无论如何它可能显示为红色(未找到)。

2) 在项目 Build Settings 下,您将旧的私有框架目录明确列为框架搜索路径。删除它。如果找不到它,请在构建设置中搜索 "PrivateFrameworks"。

3) 确保添加您需要的实际 header,以便编译器理解这些私有 class。我相信您可以在设备上获取当前 headers here for example. Even if the frameworks are removed from the Mac SDKs, I believe this person has used a tool like Runtime Browser 以生成 header 文件。在您的情况下,将 BluetoothManager.h 和 BluetoothDevice.h header 添加到 Xcode 项目。

3a) 注意:生成的 header 有时无法编译。我不得不在上面 Runtime Browser headers 中注释掉几个 struct typedef,以便构建项目。 Hattip @Alan_s 下面。

4) 从以下位置更改导入:

#import <BluetoothManager/BluetoothManager.h>

#import "BluetoothManager.h"

5) 在使用私有 class 的地方,您需要先动态打开框架。为此,请使用 (in 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 classes.

我添加了一个方便的方法 [MDBluetoothManager bluetoothManagerSharedInstance] 因为你会反复调用它。当然,我相信您可以找到替代实现。重要的细节是这个新方法使用 NSClassFromString().

动态实例化私有 class

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"];