Обработка частных структур в 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"];