CBPeripheral: o que é, métodos e gerenciamento de periféricos BLE no iOS

Autor: IT Sectr Publicado: 2026-07-16 Tempo de leitura: 10 min

CBPeripheral é uma classe do framework Core Bluetooth que representa um dispositivo BLE remoto no iOS. Cada objeto CBPeripheral encapsula o UUID, nome, RSSI e a hierarquia de serviços GATT de um dispositivo BLE conectado. O desenvolvedor interage com o periférico exclusivamente através do CBPeripheral: descoberta de serviços (discoverServices:), leitura de características (readValueForCharacteristic:), escrita de dados (writeValue:forCharacteristic:type:) e assinatura de notificações (setNotifyValue:forCharacteristic:). De acordo com Apple Developer, 2026, CBPeripheral é o objeto central para todas as operações com periféricos BLE, retornado pelo CBCentralManager ao descobrir ou conectar um dispositivo.

Pontos principais

  • CBPeripheral é uma classe Core Bluetooth para trabalhar com um dispositivo BLE remoto no iOS
  • Hierarquia GATT — Peripheral contém serviços (CBService), serviços contêm características (CBCharacteristic), características contêm descritores (CBDescriptor)
  • Descoberta — discoverServices: e discoverCharacteristics:forService: para obter a estrutura GATT de um dispositivo
  • Leitura e escrita — readValueForCharacteristic: e writeValue:forCharacteristic:type: com confirmação (withResponse) ou sem (withoutResponse)
  • Notificações — setNotifyValue:forCharacteristic: ativa a assinatura de alterações de características do dispositivo BLE

O que é CBPeripheral: essência e propósito

CBPeripheral é um objeto que representa um dispositivo BLE remoto em uma aplicação iOS. Ao contrário do CBCentralManager, que gerencia o adaptador Bluetooth local do iPhone, CBPeripheral modela um dispositivo periférico externo: um sensor, rastreador fitness, beacon ou instrumento médico. Cada instância de CBPeripheral contém um identificador único (UUID) que persiste entre sessões de conexão — a Apple vincula o UUID a um dispositivo específico através do Bonding do sistema.

CBPeripheral não é criado diretamente via init. O framework Core Bluetooth retorna um objeto CBPeripheral em dois cenários: quando um dispositivo é descoberto via scanForPeripheralsWithServices: (delegado didDiscoverPeripheral) e ao conectar a um dispositivo previamente conhecido via retrievePeripheralsWithIdentifiers:. Após obter o objeto, o desenvolvedor chama connectPeripheral: no CBCentralManager, após o que o CBPeripheral fica disponível para operações GATT.

Ciclo de vida do CBPeripheral inclui seis estados: desconectado (inicial), conectando (após chamar connect), conectado (após didConnectPeripheral), descobrindo (durante a chamada discoverServices), descoberto (após receber serviços) e desconectando (após cancelPeripheralConnection). Cada estado é rastreado através do protocolo delegado CBPeripheralDelegate — essencial para qualquer aplicação BLE no iOS.

CBPeripheral e hierarquia GATT: serviços, características, descritores

CBPeripheral armazena uma estrutura hierárquica GATT composta por três níveis. O nível raiz é um array de CBService (serviços), cada serviço contém um array de CBCharacteristic (características), cada característica contém um array de CBDescriptor (descritores). Este modelo está totalmente em conformidade com a especificação Bluetooth GATT: um serviço é uma função do dispositivo (por exemplo, “Heart Rate Service”), uma característica é um valor específico (pulso 72 bpm), um descritor são metadados da característica (unidades de medida, configuração de notificações).

NívelClasse Core BluetoothDescrição
ServiçoCBServiceGrupo lógico de características relacionadas, identificado por UUID (16 bits, 32 bits ou 128 bits)
CaracterísticaCBCharacteristicValor de dados específico, suporta leitura, escrita e notificações
DescritorCBDescriptorMetadados da característica: configuração de cliente CCCD, descrição do usuário, formato de apresentação

Os serviços BLE padrão são registrados pelo Bluetooth SIG: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). Para serviços personalizados, são usados UUIDs de 128 bits (por exemplo, E20A39F4-73F5-4BC4-A12F-17D1AD07A961). O iOS reconhece automaticamente UUIDs padrão e exibe nomes legíveis; UUIDs personalizados aparecem em formato hexadecimal.

Após a conexão, a hierarquia do CBPeripheral está vazia — serviços e características não estão carregados. O desenvolvedor deve chamar discoverServices: para obter os serviços e, em seguida, para cada serviço chamar discoverCharacteristics:forService:. Se o serviço contiver serviços incluídos, chame adicionalmente discoverIncludedServices:forService:. Somente após a conclusão da descoberta da hierarquia o CBPeripheral é preenchido e fica disponível para leitura e escrita.

Descoberta de serviços e características: métodos e delegados

Descoberta da estrutura GATT do CBPeripheral é uma etapa obrigatória antes de qualquer operação de leitura ou escrita. O método discoverServices: inicia uma busca assíncrona por todos os serviços do dispositivo. Se nil for passado, todos os serviços são descobertos; se um array de CBUUID for passado — apenas os serviços com os UUIDs especificados (otimização de tempo). O resultado chega ao delegado peripheral:didDiscoverServices: — o objeto CBPeripheral preenche sua propriedade services com um array de CBService.

Após receber os serviços, para cada CBService deve-se chamar discoverCharacteristics:forService:. Da mesma forma, nil — todas as características, array de CBUUID — apenas as especificadas. O resultado: peripheral:didDiscoverCharacteristicsForService:error:. Nesta etapa, o CBCharacteristic recebe propriedades (properties: .read, .write, .notify, .indicate) que definem as operações 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)")
    }
}

No exemplo, CBPeripheralDelegate implementa três métodos de descoberta obrigatórios. didDiscoverServices itera sobre todos os serviços encontrados e solicita características. didDiscoverCharacteristicsForService verifica as propriedades de cada característica: para .read chama readValue, para .notify chama setNotifyValue(true). O método didUpdateValueForCharacteristic recebe o valor real no formato Data.

Leitura e escrita de características: withResponse e withoutResponse

Leitura de valores de CBCharacteristic é realizada usando o método readValueForCharacteristic:. O resultado chega assincronamente em peripheral:didUpdateValueForCharacteristic:error:. Importante: o dispositivo pode ter um valor em cache (characteristic.value está disponível imediatamente após a descoberta), mas para obter o dado atual, chamar readValue é obrigatório. O iOS pode armazenar valores em cache para eficiência energética — readValue atualiza o cache.

Escrita de valores é realizada usando o método writeValue:forCharacteristic:type:. O parâmetro type determina o tipo de escrita: .withResponse (CBCharacteristicWriteWithResponse) — o dispositivo confirma a escrita através de didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — escrita sem confirmação, velocidade máxima mas sem garantia de entrega. A especificação BLE limita o MTU (Maximum Transmission Unit): até 23 bytes para BLE 4.0, até 251 bytes para BLE 5.0+. Para dados maiores que o MTU, é necessária fragmentação no nível da aplicação.

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

A escolha do tipo de escrita withResponse ou withoutResponse depende dos requisitos de confiabilidade. Para comandos (ligar luz, abrir fechadura) use withResponse — a garantia de entrega é crítica. Para dados em streaming (pulso, temperatura) use withoutResponse — a perda de um pacote é irrelevante. Um dispositivo BLE pode suportar apenas um tipo de escrita — verifique as propriedades characteristic.properties.contains(.write) e .writeWithoutResponse.

Assinatura de notificações BLE via setNotifyValue

Notificações são um mecanismo BLE onde o dispositivo periférico envia valores de características para o dispositivo central de forma assíncrona, sem polling constante por parte do central. CBPeripheral ativa a assinatura através do método setNotifyValue:forCharacteristic:. Após ativar a assinatura, o iOS escreve automaticamente no CCCD (Client Characteristic Configuration Descriptor) do periférico, e o dispositivo começa a enviar atualizações sempre que o valor muda.

Ao contrário das indicações, as notificações não exigem confirmação do dispositivo central — o pacote é enviado e esquecido. Isso proporciona a máxima taxa de transferência, mas é possível a perda de pacotes. As indicações exigem confirmação no nível do protocolo (L2CAP) — mais confiáveis, porém mais lentas. A propriedade properties do CBCharacteristic indica precisamente qual modo é suportado: .notify, .indicate ou ambos.

Quando o CBPeripheral desconecta (desconexão, fora do alcance), todas as assinaturas ativas são redefinidas automaticamente. Ao reconectar, deve-se chamar setNotifyValue:true novamente para cada característica. O iOS também perde assinaturas quando o aplicativo sai do primeiro plano (se o modo background não estiver ativado) — para operação em segundo plano, é necessário ativar a capacidade “Uses Bluetooth LE accessories” no 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)")
        }
    }
}

O NotificationManager demonstra o tratamento correto de notificações do CBPeripheral. subscribeToAllNotifications itera por todos os serviços e características, ativando .notify e .indicate. subscribedCharacteristics rastreia as assinaturas ativas para cancelamento adequado. didUpdateNotificationStateForCharacteristic confirma a mudança bem-sucedida do estado da assinatura através da propriedade characteristic.isNotifying.

Exemplo completo de CBPeripheral em Swift

Fluxo de trabalho completo com CBPeripheral inclui: obtenção do objeto do CBCentralManager, conexão, descoberta, leitura/escrita, assinatura de notificações e desconexão. O exemplo abaixo implementa uma classe BLEConnection que gerencia o ciclo de vida completo de um periférico BLE em Swift usando a 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
}

A classe BLEConnection usa Swift Concurrency (async/await) através de CheckedContinuation — um padrão moderno para trabalhar com APIs delegadas do Core Bluetooth. connect(to:) aguarda a confirmação da conexão através de didConnectPeripheral, discoverServices() — através de didDiscoverServices. Esta abordagem elimina delegados aninhados e torna o código BLE linear e legível. O tratamento de erros através de BLEError cobre todos os cenários típicos de falha de conexão BLE.

Perguntas frequentes

Como obter CBPeripheral sem escanear?

CBPeripheral para um dispositivo previamente conectado pode ser obtido através de retrievePeripheralsWithIdentifiers: no CBCentralManager. Passe um array de UUIDs (NSUUID) de dispositivos salvos anteriormente — o framework retorna um array de CBPeripheral para dispositivos no banco de dados de bonding BLE do sistema. Isso funciona apenas para dispositivos com os quais o iPhone foi previamente pareado. Para um novo dispositivo, a digitalização é obrigatória.

Por que o CBPeripheral não descobre serviços?

Causas comuns: o dispositivo está fora do alcance (RSSI abaixo do limite), o rádio BLE está desligado (CBCentralManager.state != .poweredOn), o delegado CBPeripheralDelegate não está definido (peripheral.delegate = self), ou discoverServices foi chamado antes da conexão. Verifique centralManager.state, certifique-se de que o delegado esteja definido antes de chamar connect e use uma nova tentativa com tempo limite de 5 a 10 segundos.

O que fazer se writeValue não responder?

A causa é usar .withResponse em uma característica que suporta apenas .writeWithoutResponse, ou vice-versa. Verifique characteristic.properties antes de chamar. Outro problema possível é o MTU: se os dados excederem 20 bytes (MTU BLE 4.0), é necessária negociação de MTU via negotiateMTU ou fragmentação. Use peripheral.maximumWriteValueLength(for: .withResponse) para determinar o tamanho máximo do pacote.

Como distinguir um CBPeripheral no alcance de um indisponível?

CBPeripheral fora do alcance não desconecta imediatamente — o iOS o transiciona para o estado .disconnected após um tempo limite (geralmente 20–30 segundos). Para monitoramento, use readRSSI no CBPeripheral — se estiver indisponível, retornará um erro com código CBError.connectionTimeout. Monitore também centralManager:didDisconnectPeripheral:error: para detecção oportuna de perda de conexão.

Pode-se usar um único CBPeripheral de múltiplas threads?

O Core Bluetooth não é thread-safe — todas as chamadas ao CBPeripheral devem ser feitas da mesma fila (geralmente a fila principal ou uma fila serial especificada ao inicializar o CBCentralManager). Chamadas concorrentes de diferentes threads causam condições de corrida e travamentos do aplicativo. Use DispatchQueue(label: “com.app.ble”) para todas as operações BLE e DispatchQueue.main.async para atualizações da interface do usuário.

Resumo

  • CBPeripheral é uma classe Core Bluetooth para trabalhar com um dispositivo BLE remoto no iOS, retornado pelo CBCentralManager
  • Hierarquia GATT consiste em serviços (CBService), características (CBCharacteristic) e descritores (CBDescriptor) com UUIDs de 16 ou 128 bits
  • Descoberta é realizada sequencialmente: discoverServices: → discoverCharacteristics:forService: com manipulação via delegado
  • Leitura — readValueForCharacteristic:, escrita — writeValue:forCharacteristic:type: (.withResponse ou .withoutResponse)
  • Notificações — setNotifyValue:forCharacteristic: ativa a transmissão assíncrona de dados do periférico para o central
  • MTU para BLE 4.0 limita pacotes a 23 bytes, BLE 5.0+ — até 251 bytes, dados que excedem o MTU requerem fragmentação
  • Swift async/await via CheckedContinuation simplifica o código BLE, substituindo delegados aninhados por chamadas lineares

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também