Обработка частных фреймворков в Xcode ≥ 7.3

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

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

Любые идеи для долгосрочного решения?


person Michael Dorner    schedule 03.05.2016    source источник
comment
Что бы это ни стоило, ожидая удачи, я временно решил это, установив SDK iOS9.2 в XCode 7.3: оттуда можно перейти к частным фреймворкам.   -  person JBA    schedule 03.05.2016


Ответы (1)


Вы можете решить эту проблему путем динамического связывания с частной инфраструктурой вместо более распространенного способа связывания во время сборки. Во время сборки 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
comment
Я думаю, что это сработает, но, конечно, это чрезвычайно хрупкое решение. Любые изменения API будут иметь катастрофические последствия. - person trojanfoe; 03.05.2016
comment
@trojanfoe, каждый раз, когда выпускается новая версия iOS, вам нужно получать обновленные приватные заголовки (с сайта, на который я дал ссылку ... они восстанавливают каждый выпуск). Если вы будете в курсе новых заголовков, все будет в порядке. По определению, использование частных API-интерфейсов ненадежно. Вы делаете это, если вам нужна функциональность, которой нет в общедоступных API (как в данном случае). - person Nate; 03.05.2016
comment
Довольно красивое и чистое решение, спасибо !! @trojanfoe: как только вы начинаете играть с Forbidden Holly PrivateFrameworks, вы обязательно создаете себе проблемы - это цена, которую нужно заплатить :) - person JBA; 08.05.2016
comment
@JBA. Спасибо. Преимущество этого решения также в том, что оно фактически позволяет компилятору проверить, существуют ли используемые вами API (при условии, что вы получаете новый набор заголовков с каждым выпуском iOS). Как известно, приватные API могут исчезнуть в любой момент :( - person Nate; 09.05.2016
comment
Спасибо за публикацию - я получаю сообщение об ошибке компилятора в BluetoothDevice.h Redefinition of BTDeviceImpl Похоже, что структура уже определена в начале файла в интерфейсе. С этой небольшой настройкой все в порядке. Вы должны раскошелиться и отправить PR - или я могу, если хотите (но вы заслуживаете похвалы!) - person Alan Scarpa; 01.06.2016
comment
@Alan_s, да, мне тоже было необходимо закомментировать пару избыточных struct typedef в обратно сгенерированных файлах заголовков. Ответ обновлен. Идите вперед и сделайте Github PR, если хотите. Голоса за переполнение стека для меня достаточно;) - person Nate; 01.06.2016
comment
Я пытаюсь реализовать Private Framework MobileWIFI.Framework в xcode 7.3, но это дает мне эту ошибку: _WiFiManagerClientCopyNetworks, на которую ссылается: -[AppDelegate application:didFinishLaunchingWithOptions:] в AppDelegate.o _WiFiManagerClientCreate, на которую ссылается: -[AppDelegate application:didFinishLaunchingWithOptions: ] в AppDelegate.old: символ (символы) не найден для архитектуры x86_64 clang: ошибка: команда компоновщика не удалась с кодом выхода 1 (используйте -v, чтобы увидеть вызов) я пробовал с framework ios 9.3, ios 8.1, ios 7, это дает такая же ошибка. - person Muhammad Faizan Khatri; 29.09.2016
comment
@MuhammadFaizanKhatri, задайте новый вопрос здесь, в Stackoverflow. Этот комментарий не имеет отношения к исходной проблеме автора вопроса. - person Nate; 30.09.2016
comment
@Nate, я разместил для него новый вопрос: вот ссылка < /а> - person Muhammad Faizan Khatri; 30.09.2016
comment
@nate, твое решение отлично работает. Но мне любопытно, почему Xcode не жалуется на ошибки связывания (мы все еще ссылаемся на неопределенные API в BluetoothManager.h) с динамической ссылкой? - person Quanlong; 21.10.2016
comment
@Quanlong, Xcode не знает, что вы ссылаетесь на частную структуру. Он не ищет использование dlopen(), а затем проверяет имя фреймворка, которое вы ему даете. Связывание с BluetoothManager.framework вообще не происходит, пока вы не запустите свое приложение. Чтобы удовлетворить требованиям компилятора (который отличается от компоновщика), вам нужны только заголовки частного API. Это ключ, который заставляет это решение работать. Динамическое связывание. - person Nate; 21.10.2016
comment
Если это решение все еще работает на iOS10? Пожалуйста, смотрите ответ Леона Добника - person tatiana_c; 23.11.2016
comment
@tatiana_c, у меня все еще работает на iOS 10.1. Я только что проверил это. Кроме того, ответ Леона не является ответом. - person Nate; 23.11.2016
comment
Не могли бы вы добавить ссылку для скачивания проекта? Я не могу это сделать, а затем изменить класс заголовков и частную структуру :( - person user3745888; 29.01.2017
comment
@ user3745888, ссылка прямо в моем ответе (измените свой проект на github). Человек, задавший вопрос, обновил свой проект, чтобы использовать ответ. - person Nate; 29.01.2017
comment
Похоже, это больше не работает на iOS 11, каталоги частных фреймворков больше не содержат двоичные файлы. - person newenglander; 26.03.2018
comment
@newenglander, это все еще работает. Каталоги частных фреймворков на Mac уже были лишены двоичных файлов в версии 9.3. Этот метод открывает их только на устройстве, где они все еще существуют, и их невозможно удалить. Примечание. Я слышал, что приложения были отклонены из-за использования dlopen(), поэтому я заменил его вызовами для загрузки библиотек через [NSBundle bundleWithPath:], что в основном делает то же самое. - person Nate; 26.03.2018
comment
@newenglander Я только что протестировал именно эту библиотеку на своем стандартном устройстве 11.1.2, и она работает. Следует отметить, что путь для bundle требует, чтобы вы удалили последний компонент пути. Так, например. используйте 1_ - person Nate; 26.03.2018
comment
Я проверял файлы на своем взломанном устройстве на iOS 11 и не видел двоичных файлов в каталогах в PrivateFrameworks. При этом я не вижу их и на взломанном устройстве под управлением iOS 9, поэтому я думаю, что в этом отношении ничего не изменилось. - person newenglander; 26.03.2018