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 é 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 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ível | Classe Core Bluetooth | Descrição |
|---|---|---|
| Serviço | CBService | Grupo lógico de características relacionadas, identificado por UUID (16 bits, 32 bits ou 128 bits) |
| Característica | CBCharacteristic | Valor de dados específico, suporta leitura, escrita e notificações |
| Descritor | CBDescriptor | Metadados 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 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.
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 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.
// 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.
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.
// 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.
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+).
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
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.
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.
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.
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.
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
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.
Leia também