Вы можете решить эту проблему путем динамического связывания с частной инфраструктурой вместо более распространенного способа связывания во время сборки. Во время сборки BluetoothManager.framework должен существовать на вашем Mac для разработки, чтобы компоновщик мог его использовать. При динамической компоновке вы откладываете процесс до выполнения. На устройстве iOS 9.3 все еще присутствует этот фреймворк (и, конечно, другие).
Вот как вы можете изменить свой проект на Github:
1) В Project Navigator Xcode в разделе Frameworks удалите ссылку на BluetoothManager.framework. Вероятно, он все равно отображался красным (не найден).
2) В проекте Build Settings у вас есть старый частный каталог фреймворка, явно указанный как путь поиска фреймворка. Удалите это. Найдите «PrivateFrameworks» в настройках сборки, если у вас возникли проблемы с его поиском.
3) Обязательно добавьте нужные заголовки, чтобы компилятор понял эти приватные классы. Я считаю, что вы можете получить текущие заголовки здесь, например а>. Даже если фреймворки удалены из Mac SDK, я полагаю, что этот человек использовал такой инструмент, как Runtime Browser. на устройстве для создания файлов заголовков. В вашем случае добавьте заголовки BluetoothManager.h и BluetoothDevice.h в проект Xcode.
3a) Примечание: сгенерированные заголовки иногда не компилируются. Мне пришлось закомментировать пару struct
определений типов в приведенном выше Заголовки Runtime Browser, чтобы запустить сборку проекта. Hattip @Alan_s ниже.
4) Измените импорт из:
#import <BluetoothManager/BluetoothManager.h>
to
#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"];
person
Nate
schedule
03.05.2016