CBPeripheral es una clase del framework Core Bluetooth que representa un dispositivo BLE remoto en iOS. Cada objeto CBPeripheral encapsula el UUID, nombre, RSSI y la jerarquía de servicios GATT de un dispositivo BLE conectado. El desarrollador interactúa con el periférico exclusivamente a través de CBPeripheral: descubrimiento de servicios (discoverServices:), lectura de características (readValueForCharacteristic:), escritura de datos (writeValue:forCharacteristic:type:) y suscripción a notificaciones (setNotifyValue:forCharacteristic:). Según Apple Developer, 2026, CBPeripheral es el objeto central para todas las operaciones con periféricos BLE, devuelto por CBCentralManager al descubrir o conectar un dispositivo.
Puntos clave
CBPeripheral es un objeto que representa un dispositivo BLE remoto en una aplicación iOS. A diferencia de CBCentralManager, que gestiona el adaptador Bluetooth local del iPhone, CBPeripheral modela un dispositivo periférico externo: un sensor, rastreador de actividad, baliza o instrumento médico. Cada instancia de CBPeripheral contiene un identificador único (UUID) que persiste entre sesiones de conexión — Apple vincula el UUID a un dispositivo específico mediante el Bonding del sistema.
CBPeripheral no se crea directamente mediante init. El framework Core Bluetooth devuelve un objeto CBPeripheral en dos escenarios: cuando se descubre un dispositivo mediante scanForPeripheralsWithServices: (delegado didDiscoverPeripheral) y al conectarse a un dispositivo previamente conocido mediante retrievePeripheralsWithIdentifiers:. Tras obtener el objeto, el desarrollador llama a connectPeripheral: en CBCentralManager, después de lo cual CBPeripheral está disponible para operaciones GATT.
Ciclo de vida de CBPeripheral incluye seis estados: desconectado (inicial), conectando (tras llamar a connect), conectado (tras didConnectPeripheral), descubriendo (durante la llamada a discoverServices), descubierto (tras recibir servicios) y desconectando (tras cancelPeripheralConnection). Cada estado se rastrea mediante el protocolo delegado CBPeripheralDelegate — imprescindible para cualquier aplicación BLE en iOS.
CBPeripheral almacena una estructura jerárquica GATT compuesta por tres niveles. El nivel raíz es un array de CBService (servicios), cada servicio contiene un array de CBCharacteristic (características), cada característica contiene un array de CBDescriptor (descriptores). Este modelo se ajusta completamente a la especificación Bluetooth GATT: un servicio es una función del dispositivo (por ejemplo, “Heart Rate Service”), una característica es un valor específico (pulso 72 lpm), un descriptor son metadatos de la característica (unidades de medida, configuración de notificaciones).
| Nivel | Clase Core Bluetooth | Descripción |
|---|---|---|
| Servicio | CBService | Grupo lógico de características relacionadas, identificado por UUID (16 bits, 32 bits o 128 bits) |
| Característica | CBCharacteristic | Valor de datos específico, admite lectura, escritura y notificaciones |
| Descriptor | CBDescriptor | Metadatos de la característica: configuración de cliente CCCD, descripción de usuario, formato de presentación |
Los servicios BLE estándar están registrados por Bluetooth SIG: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). Para servicios personalizados se utilizan UUID de 128 bits (por ejemplo, E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS reconoce automáticamente los UUID estándar y muestra nombres legibles; los UUID personalizados se muestran en formato hexadecimal.
Después de la conexión, la jerarquía de CBPeripheral está vacía — los servicios y características no están cargados. El desarrollador debe llamar a discoverServices: para obtener los servicios y luego, para cada servicio, llamar a discoverCharacteristics:forService:. Si el servicio contiene servicios incluidos, se llama adicionalmente a discoverIncludedServices:forService:. Solo después de completar el descubrimiento de la jerarquía, CBPeripheral se llena y está disponible para lectura y escritura.
Descubrimiento de la estructura GATT de CBPeripheral es un paso obligatorio antes de cualquier operación de lectura o escritura. El método discoverServices: inicia una búsqueda asíncrona de todos los servicios del dispositivo. Si se pasa nil, se descubren todos los servicios; si se pasa un array de CBUUID — solo los servicios con los UUID especificados (optimización de tiempo). El resultado llega al delegado peripheral:didDiscoverServices: — el objeto CBPeripheral llena su propiedad services con un array de CBService.
Después de recibir los servicios, para cada CBService se debe llamar a discoverCharacteristics:forService:. De manera similar, nil — todas las características, array de CBUUID — solo las especificadas. El resultado: peripheral:didDiscoverCharacteristicsForService:error:. En esta etapa, CBCharacteristic recibe propiedades (properties: .read, .write, .notify, .indicate) que definen las operaciones permitidas.
import CoreBluetooth
extension BLEViewController: CBPeripheralDelegate {
// 1. Service discovery
func peripheral(_ peripheral: CBPeripheral,
didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
// Request characteristics for each service
peripheral.discoverCharacteristics(nil, for: service)
}
}
// 2. Characteristic discovery
func peripheral(_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.properties.contains(.read) {
peripheral.readValue(for: characteristic)
}
if characteristic.properties.contains(.notify) {
peripheral.setNotifyValue(true, for: characteristic)
}
}
}
// 3. Read value
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
guard let data = characteristic.value,
let value = String(data: data, encoding: .utf8)
else { return }
print("Characteristic value: \(value)")
}
}
En el ejemplo, CBPeripheralDelegate implementa tres métodos de descubrimiento obligatorios. didDiscoverServices itera sobre todos los servicios encontrados y solicita características. didDiscoverCharacteristicsForService verifica las propiedades de cada característica: para .read llama a readValue, para .notify llama a setNotifyValue(true). El método didUpdateValueForCharacteristic recibe el valor real en formato Data.
Lectura de valores de CBCharacteristic se realiza mediante el método readValueForCharacteristic:. El resultado llega asíncronamente a peripheral:didUpdateValueForCharacteristic:error:. Es importante: el dispositivo puede tener un valor en caché (characteristic.value está disponible inmediatamente después del descubrimiento), pero para obtener el dato actual, es obligatorio llamar a readValue. iOS puede almacenar en caché los valores por eficiencia energética — readValue actualiza la caché.
Escritura de valores se realiza mediante el método writeValue:forCharacteristic:type:. El parámetro type determina el tipo de escritura: .withResponse (CBCharacteristicWriteWithResponse) — el dispositivo confirma la escritura mediante didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — escritura sin confirmación, velocidad máxima pero sin garantía de entrega. La especificación BLE limita el MTU (Maximum Transmission Unit): hasta 23 bytes para BLE 4.0, hasta 251 bytes para BLE 5.0+. Para datos mayores que el MTU, se requiere fragmentación a nivel de aplicación.
// CBPeripheral characteristic read and write
class BLEService {
private let peripheral: CBPeripheral
private let serviceUUID = CBUUID(string: "180D")
private let charUUID = CBUUID(string: "2A37")
init(peripheral: CBPeripheral) {
self.peripheral = peripheral
}
// Read with response
func readHeartRate() {
guard let service = peripheral.services?.first(where: { $0.uuid == serviceUUID }),
let characteristic = service.characteristics?.first(where: { $0.uuid == charUUID })
else { return }
peripheral.readValue(for: characteristic)
}
// Write with response (withResponse)
func writeWithResponse(data: Data) {
guard let characteristic = findCharacteristic() else { return }
peripheral.writeValue(data, for: characteristic,
type: .withResponse)
}
// Write without response (withoutResponse)
// Max throughput, no delivery guarantee
func writeWithoutResponse(data: Data) {
guard let characteristic = findCharacteristic() else { return }
peripheral.writeValue(data, for: characteristic,
type: .withoutResponse)
}
private func findCharacteristic() -> CBCharacteristic? {
return peripheral.services?
.flatMap { $0.characteristics ?? [] }
.first(where: { $0.uuid == charUUID })
}
}
La elección del tipo de escritura withResponse o withoutResponse depende de los requisitos de fiabilidad. Para comandos (encender luz, abrir cerradura) use withResponse — la garantía de entrega es crítica. Para datos en streaming (pulso, temperatura) use withoutResponse — la pérdida de un paquete es irrelevante. Un dispositivo BLE puede soportar solo un tipo de escritura — verifique las propiedades characteristic.properties.contains(.write) y .writeWithoutResponse.
Notificaciones son un mecanismo BLE mediante el cual el dispositivo periférico envía valores de características al dispositivo central de forma asíncrona, sin sondeo constante por parte del central. CBPeripheral activa la suscripción mediante el método setNotifyValue:forCharacteristic:. Tras activar la suscripción, iOS escribe automáticamente en el CCCD (Client Characteristic Configuration Descriptor) del periférico, y el dispositivo comienza a enviar actualizaciones cada vez que el valor cambia.
A diferencia de las indicaciones, las notificaciones no requieren confirmación del dispositivo central — el paquete se envía y se olvida. Esto proporciona el máximo rendimiento, pero es posible la pérdida de paquetes. Las indicaciones requieren confirmación a nivel de protocolo (L2CAP) — más fiables pero más lentas. La propiedad properties de CBCharacteristic indica exactamente qué modo soporta: .notify, .indicate o ambos.
Cuando CBPeripheral se desconecta (desconexión, fuera de alcance), todas las suscripciones activas se restablecen automáticamente. Al reconectar, se debe llamar nuevamente a setNotifyValue:true para cada característica. iOS también pierde las suscripciones cuando la aplicación sale del primer plano (si el modo background no está activado) — para funcionamiento en segundo plano, se debe activar la capacidad “Uses Bluetooth LE accessories” en Info.plist.
// CBPeripheral notification subscription management
class NotificationManager: NSObject {
private var peripheral: CBPeripheral?
private var subscribedCharacteristics: Set<CBUUID> = []
// Subscribe to notifications for all .notify characteristics
func subscribeToAllNotifications(peripheral: CBPeripheral) {
self.peripheral = peripheral
guard let services = peripheral.services else { return }
for service in services {
guard let characteristics = service.characteristics else { continue }
for characteristic in characteristics {
if characteristic.properties.contains(.notify)
|| characteristic.properties.contains(.indicate) {
peripheral.setNotifyValue(true, for: characteristic)
subscribedCharacteristics.insert(characteristic.uuid)
}
}
}
}
// Unsubscribe from all notifications
func unsubscribeFromAll() {
guard let peripheral = peripheral else { return }
guard let services = peripheral.services else { return }
for service in services {
guard let characteristics = service.characteristics else { continue }
for characteristic in characteristics {
if subscribedCharacteristics.contains(characteristic.uuid) {
peripheral.setNotifyValue(false, for: characteristic)
}
}
}
subscribedCharacteristics.removeAll()
}
// Notification handler
func peripheral(_ peripheral: CBPeripheral,
didUpdateNotificationStateFor characteristic: CBCharacteristic,
error: Error?) {
if characteristic.isNotifying {
print("Subscription active: \(characteristic.uuid)")
} else {
print("Subscription inactive: \(characteristic.uuid)")
}
}
}
El NotificationManager demuestra el manejo correcto de las notificaciones de CBPeripheral. subscribeToAllNotifications itera sobre todos los servicios y características, activando .notify y .indicate. subscribedCharacteristics rastrea las suscripciones activas para una cancelación de suscripción adecuada. didUpdateNotificationStateForCharacteristic confirma el cambio exitoso del estado de suscripción mediante la propiedad characteristic.isNotifying.
Flujo de trabajo completo con CBPeripheral incluye: obtención del objeto de CBCentralManager, conexión, descubrimiento, lectura/escritura, suscripción a notificaciones y desconexión. El siguiente ejemplo implementa una clase BLEConnection que gestiona el ciclo de vida completo de un periférico BLE en Swift utilizando la API moderna async/await (iOS 15+).
import CoreBluetooth
// Full CBPeripheral management example with async/await
class BLEConnection: NSObject {
private let centralManager: CBCentralManager
private var peripheral: CBPeripheral?
private var continuation: CheckedContinuation<Void, Error>?
override init() {
centralManager = CBCentralManager(delegate: nil, queue: .main)
super.init()
centralManager.delegate = self
}
// 1. Connect to peripheral
func connect(to peripheral: CBPeripheral) async throws {
self.peripheral = peripheral
peripheral.delegate = self
centralManager.connect(peripheral, options: nil)
try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation
}
}
// 2. Discovery
func discoverServices() async throws {
guard let peripheral = peripheral else {
throw BLEError.notConnected
}
peripheral.discoverServices(nil)
try await withCheckedThrowingContinuation { continuation in
self.continuation = continuation
}
}
}
// 3. CBCentralManager
extension BLEConnection: CBCentralManagerDelegate {
func centralManagerDidUpdateState(_ central: CBCentralManager) {
// Handle Bluetooth device state
}
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
continuation?.resume()
continuation = nil
}
func centralManager(_ central: CBCentralManager,
didFailToConnect peripheral: CBPeripheral,
error: Error?) {
continuation?.resume(throwing: error ?? BLEError.connectionFailed)
continuation = nil
}
}
enum BLEError: Error {
case notConnected
case connectionFailed
case serviceNotFound
case characteristicNotFound
}
La clase BLEConnection utiliza Swift Concurrency (async/await) mediante CheckedContinuation — un patrón moderno para trabajar con APIs delegadas de Core Bluetooth. connect(to:) espera la confirmación de conexión a través de didConnectPeripheral, discoverServices() — a través de didDiscoverServices. Este enfoque elimina los delegados anidados y hace que el código BLE sea lineal y legible. El manejo de errores mediante BLEError cubre todos los escenarios típicos de fallo de conexión BLE.
Preguntas frecuentes
CBPeripheral para un dispositivo previamente conectado se puede obtener mediante retrievePeripheralsWithIdentifiers: en CBCentralManager. Pase un array de UUID (NSUUID) de dispositivos previamente guardados — el framework devuelve un array de CBPeripheral para dispositivos en la base de datos de vinculación BLE del sistema. Esto solo funciona para dispositivos con los que el iPhone se ha emparejado previamente. Para un dispositivo nuevo, el escaneo es obligatorio.
Causas comunes: el dispositivo está fuera de alcance (RSSI por debajo del umbral), la radio BLE está apagada (CBCentralManager.state != .poweredOn), el delegado CBPeripheralDelegate no está establecido (peripheral.delegate = self), o se llamó a discoverServices antes de la conexión. Verifique centralManager.state, asegúrese de que el delegado esté establecido antes de llamar a connect y utilice un reintento con un tiempo de espera de 5 a 10 segundos.
La causa es usar .withResponse en una característica que solo admite .writeWithoutResponse, o viceversa. Verifique characteristic.properties antes de llamar. Otro posible problema es el MTU: si los datos superan los 20 bytes (MTU de BLE 4.0), se necesita negociación de MTU mediante negotiateMTU o fragmentación. Use peripheral.maximumWriteValueLength(for: .withResponse) para determinar el tamaño máximo de paquete.
CBPeripheral fuera de alcance no se desconecta inmediatamente — iOS lo transiciona al estado .disconnected después de un tiempo de espera (generalmente 20–30 segundos). Para monitoreo, use readRSSI en CBPeripheral — si no está disponible, devolverá un error con código CBError.connectionTimeout. También monitoree centralManager:didDisconnectPeripheral:error: para la detección oportuna de pérdida de conexión.
Core Bluetooth no es seguro para hilos — todas las llamadas a CBPeripheral deben realizarse desde la misma cola (generalmente la cola principal o una cola serial especificada al inicializar CBCentralManager). Las llamadas concurrentes desde diferentes hilos provocan condiciones de carrera y caídas de la aplicación. Use DispatchQueue(label: “com.app.ble”) para todas las operaciones BLE y DispatchQueue.main.async para actualizaciones de la interfaz de usuario.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también