ios - Manejo de frameworks privados en Xcode ≥ 7.3



iphone-privateapi ios9.3 (1)

Con Xcode 7.3 / iOS 9.3, Apple eliminó todos los marcos privados de los SDK de iOS. Para fines de investigación (¡no App Store!), Necesito trabajar con un marco privado (es decir, el marco de BluetoothManager.framework , pero esto también es un problema para cualquier otro marco privado ).

Debido a que estos marcos ya no se entregan en los SDK de iOS, obtengo un error de compilación (enlazador) si mi proyecto intenta vincularse a este marco explícitamente.

¿Alguna idea para una solución a largo plazo?


Puede resolver este problema vinculando dinámicamente al marco privado, en lugar de la forma más común de vincular en tiempo de compilación . En el momento de la compilación, el marco BluetoothManager. necesitaría existir en su Mac de desarrollo para que el enlazador pueda usarlo. Con la vinculación dinámica, difiere el proceso hasta el tiempo de ejecución. En el dispositivo, iOS 9.3 todavía tiene ese marco presente (y los otros, también, por supuesto).

Así es como puede modificar su proyecto en Github :

1) En el Navegador de proyectos de Xcode, en Frameworks, elimine la referencia al marco de BluetoothManager. Probablemente se mostraba en rojo (no encontrado) de todos modos.

2) En la configuración de compilación del proyecto, tiene el antiguo directorio de marco privado enumerado explícitamente como ruta de búsqueda de marco. Eliminar eso. Busque "PrivateFrameworks" en la configuración de compilación si tiene problemas para encontrarlo.

3) Asegúrese de agregar los encabezados reales que necesita, para que el compilador comprenda estas clases privadas. Creo que puedes obtener encabezados actuales aquí, por ejemplo . Incluso si los marcos se eliminan de los SDK de Mac, creo que esta persona ha utilizado una herramienta como Runtime Browser en el dispositivo para generar los archivos de encabezado. En su caso, agregue encabezados BluetoothManager.h y BluetoothDevice.h al proyecto Xcode.

3a) Nota : los encabezados generados a veces no se compilan. Tuve que comentar un par de struct typedefs en los encabezados de Runtime Browser anteriores para poder construir el proyecto. Hattip @Alan_s a continuación.

4) Cambia tus importaciones de:

#import <BluetoothManager/BluetoothManager.h>

a

#import "BluetoothManager.h"

5) Cuando use la clase privada, primero necesitará abrir el marco dinámicamente. Para hacer esto, use (en 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;
}

Puse la llamada a dlopen en su método singleton, pero podría ponerlo en otro lugar. Solo necesita ser llamado antes de que cualquier código use las clases privadas de API.

[MDBluetoothManager bluetoothManagerSharedInstance] un método de conveniencia [MDBluetoothManager bluetoothManagerSharedInstance] porque lo [MDBluetoothManager bluetoothManagerSharedInstance] repetidamente. Estoy seguro de que podrías encontrar implementaciones alternativas, por supuesto. El detalle importante es que este nuevo método NSClassFromString() instancia dinámica de la clase privada mediante NSClassFromString() .

6) En todas partes donde estaba llamando directamente [BluetoothManager sharedInstance] , reemplácelo con la nueva [MDBluetoothManager bluetoothManagerSharedInstance] .

Probé esto con Xcode 7.3 / iOS 9.3 SDK y su proyecto funciona bien en mi iPhone.

Actualizar

Como parece haber cierta confusión, esta misma técnica (y código exacto) todavía funciona en iOS 10.0-11.1 (a partir de este escrito).

Además, otra opción para forzar la carga de un marco es usar [NSBundle bundleWithPath:] lugar de dlopen() . Sin embargo, observe la ligera diferencia en las rutas:

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




ios9.3