Обработка частных структур в Xcode ≥ 7.3

С Xcode 7.3 / iOS 9.3 Apple удалила все частные фреймворки из iOS SDK. В исследовательских целях (не в App Store!) Мне нужно работать с частной структурой (а именно BluetoothManager.framework, но это также проблема для любых других частных структур).

Поскольку эти платформы больше не поставляются в iOS SDK, я получаю ошибку сборки (компоновщик), если мой проект пытается явно связать эту платформу.

Есть идеи для долгосрочного решения?

1 ответ

Решение

Вы можете решить эту проблему, динамически связываясь с частной платформой, а не с более распространенным способом связывания во время сборки. Во время сборки BluetoothManager.framework должен существовать на вашем Mac для разработки, чтобы компоновщик мог его использовать. С динамическим связыванием вы откладываете процесс до времени выполнения. На устройстве iOS 9.3 все еще имеет этот фреймворк (и другие, конечно же).

Вот как вы можете изменить свой проект на Github:

1) В Навигаторе проекта XCode, под Каркасами, удалите ссылку на BluetoothManager.framework. Это, вероятно, показывал красным (не найден) в любом случае.

2) В проекте " Настройки сборки" у вас есть старый частный каталог фреймворка, явно указанный как путь поиска фреймворка. Удалить это. Ищите "PrivateFrameworks" в настройках сборки, если у вас возникли проблемы с его поиском.

3) Убедитесь, что вы добавили нужные заголовки, чтобы компилятор понимал эти закрытые классы. Я считаю, что вы можете получить текущие заголовки здесь, например. Даже если рамки удаляются из Mac SDK, я полагаю, что этот человек использовал такой инструмент, как Runtime Browser на устройстве, для создания заголовочных файлов. В вашем случае добавьте заголовки BluetoothManager.h и BluetoothDevice.h в проект Xcode.

3a) Примечание: сгенерированные заголовки иногда не компилируются. Я должен был закомментировать пару struct напечатайте deffs в вышеупомянутых заголовках Runtime Browser для получения проекта для сборки. Hattip @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"];
Другие вопросы по тегам