CBPeripheral: qué es, métodos y gestión de periféricos BLE en iOS

Autor: IT Sectr Publicado: 2026-07-16 Tiempo de lectura: 10 min

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 una clase de Core Bluetooth para trabajar con un dispositivo BLE remoto en iOS
  • Jerarquía GATT — el Peripheral contiene servicios (CBService), los servicios contienen características (CBCharacteristic), las características contienen descriptores (CBDescriptor)
  • Descubrimiento — discoverServices: y discoverCharacteristics:forService: para obtener la estructura GATT de un dispositivo
  • Lectura y escritura — readValueForCharacteristic: y writeValue:forCharacteristic:type: con confirmación (withResponse) o sin (withoutResponse)
  • Notificaciones — setNotifyValue:forCharacteristic: activa la suscripción a cambios de características del dispositivo BLE

Qué es CBPeripheral: esencia y propósito

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 y jerarquía GATT: servicios, características, descriptores

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).

NivelClase Core BluetoothDescripción
ServicioCBServiceGrupo lógico de características relacionadas, identificado por UUID (16 bits, 32 bits o 128 bits)
CaracterísticaCBCharacteristicValor de datos específico, admite lectura, escritura y notificaciones
DescriptorCBDescriptorMetadatos 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 servicios y características: métodos y delegados

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.

swift
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 y escritura de características: withResponse y withoutResponse

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.

swift
// 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.

Suscripción a notificaciones BLE mediante setNotifyValue

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.

swift
// 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.

Ejemplo completo de CBPeripheral en Swift

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+).

swift
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

¿Cómo obtener CBPeripheral sin escanear?

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.

¿Por qué CBPeripheral no descubre servicios?

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.

¿Qué hacer si writeValue no responde?

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.

¿Cómo distinguir un CBPeripheral en alcance de uno no disponible?

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.

¿Se puede usar un solo CBPeripheral desde múltiples hilos?

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

  • CBPeripheral es una clase de Core Bluetooth para trabajar con un dispositivo BLE remoto en iOS, devuelto por CBCentralManager
  • Jerarquía GATT consiste en servicios (CBService), características (CBCharacteristic) y descriptores (CBDescriptor) con UUID de 16 o 128 bits
  • Descubrimiento se realiza secuencialmente: discoverServices: → discoverCharacteristics:forService: con manejo mediante delegado
  • Lectura — readValueForCharacteristic:, escritura — writeValue:forCharacteristic:type: (.withResponse o .withoutResponse)
  • Notificaciones — setNotifyValue:forCharacteristic: activa la transmisión asíncrona de datos del periférico al central
  • MTU para BLE 4.0 limita los paquetes a 23 bytes, BLE 5.0+ — hasta 251 bytes, los datos que exceden el MTU requieren fragmentación
  • Swift async/await mediante CheckedContinuation simplifica el código BLE, reemplazando delegados anidados con llamadas lineales

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.

Discutir el proyecto

Lea también