处理私有框架 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"];
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()
.
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"];