在 Xcode ≥ 7.3 中处理私有框架

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/37000053/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 08:47:12  来源:igfitidea点击:

Handling private frameworks in Xcode ≥ 7.3

iosxcodeiphone-privateapiios9.3

提问by Michael Dorner

With Xcode 7.3 / iOS 9.3 Apple removed all private frameworksfrom the iOS SDKs. For research purposes (not App Store!) I need to work with a private framework (namely BluetoothManager.framework, but this is also an issue for any other privateframeworks).

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

Because these frameworks are no longer delivered in the iOS SDKs, I get a build (linker) error if my project attempts to link to this framework explicitly.

由于 iOS SDK 中不再提供这些框架,因此如果我的项目尝试显式链接到此框架,则会出现构建(链接器)错误。

Any ideas for a long(er)-term solution?

关于长期(er)长期解决方案的任何想法?

回答by Nate

You can solve this problem by linking to the private framework dynamically, instead of the more common way of linking at build time. At build time, the BluetoothManager.framework would need to exist on your development Mac for the linker to be able to use it. With dynamic linking, you defer the process until runtime. On the device, iOS 9.3 still has that framework present (and the other ones, too, of course).

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

Here is how you can modify your project on Github:

以下是在Github上修改项目的方法:

1) In Xcode's Project Navigator, under the Frameworks, remove the reference to BluetoothManager.framework. It was probably showing in red (not found) anyway.

1) 在 Xcode 的 Project Navigator 中,在 Frameworks 下,删除对 BluetoothManager.framework 的引用。无论如何,它可能显示为红色(未找到)。

2) Under the project Build Settings, you have the old private framework directory explicitly listed as a framework search path. Remove that. Search for "PrivateFrameworks" in the build settings if you have trouble finding it.

2) 在项目Build Settings 下,您将旧的私有框架目录明确列为框架搜索路径。去掉那个。如果找不到,请在构建设置中搜索“PrivateFrameworks”。

3) Make sure to add the actual headers you need, so the compiler understands these private classes. I believe you can get current headers here for example. Even if the frameworks are removed from the Mac SDKs, I believe this person has used a tool like Runtime Browseron the device to generate the header files. In your case, add BluetoothManager.h and BluetoothDevice.h headers to the Xcode project.

3) 确保添加您需要的实际头文件,以便编译器理解这些私有类。例如,我相信您可以在此处获取当前标题。即使从 Mac SDK 中删除了框架,我相信此人在设备上使用了Runtime Browser 之类的工具来生成头文件。在您的情况下,将 BluetoothManager.h 和 BluetoothDevice.h 标头添加到 Xcode 项目。

3a) Note: the generated headers sometimes don't compile. I had to comment out a couple structtypedefs in the above Runtime Browser headersin order to get the project to build. Hattip @Alan_s below.

3a)注意:生成的头文件有时无法编译。我不得不struct在上面的运行时浏览器标头中注释掉几个typedef ,以便构建项目。下面的 Hattip @Alan_s。

4) Change your imports from:

4)更改您的进口:

#import <BluetoothManager/BluetoothManager.h>

to

#import "BluetoothManager.h"

5) Where you use the private class, you're going to need to first open up the framework dynamically. To do this, use (in MDBluetoothManager.m):

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;
}

I placed the call to dlopenin your singleton method, but you could put it elsewhere. It just needs to be called beforeany code uses the private API classes.

我将调用dlopen放在你的单例方法中,但你可以把它放在其他地方。它只需要任何代码使用私有 API 类之前调用。

I added a convenience method [MDBluetoothManager bluetoothManagerSharedInstance]because you'll be calling that repeatedly. I'm sure you could find alternate implementations, of course. The important detail is that this new method dynamically instantiates the private class using NSClassFromString().

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

6) Everywhere you were directly calling [BluetoothManager sharedInstance], replace it with the new [MDBluetoothManager bluetoothManagerSharedInstance]call.

6)在您直接调用的任何地方[BluetoothManager sharedInstance],将其替换为新[MDBluetoothManager bluetoothManagerSharedInstance]调用。

I tested this with Xcode 7.3 / iOS 9.3 SDK and your project runs fine on my iPhone.

我使用 Xcode 7.3 / iOS 9.3 SDK 对此进行了测试,您的项目在我的 iPhone 上运行良好。

Update

更新

Since there seems to be some confusion, this same technique (and exact code) still works in iOS 10.0-11.1 (as of this writing).

由于似乎有些混乱,因此相同的技术(和确切的代码)在 iOS 10.0-11.1(截至撰写本文时)仍然有效。

Also, another option to force loading of a framework is to use [NSBundle bundleWithPath:]instead of dlopen(). Notice the slight difference in paths, though:

此外,另一种选择一个框架的力加载使用[NSBundle bundleWithPath:],而不是dlopen()。但是,请注意路径的细微差别:

handle = dlopen("/System/Library/PrivateFrameworks/BluetoothManager.framework/BluetoothManager", RTLD_NOW);
NSBundle *bt = [NSBundle bundleWithPath: @"/System/Library/PrivateFrameworks/BluetoothManager.framework"];