CBPeripheral: nó là gì, phương thức và quản lý thiết bị ngoại vi BLE trên iOS

Tác giả: IT Sectr Đã đăng: 2026-07-16 Thời gian đọc: 10 phút

CBPeripheral là một lớp của framework Core Bluetooth đại diện cho một thiết bị BLE từ xa trên iOS. Mỗi đối tượng CBPeripheral đóng gói UUID, tên, RSSI và hệ thống phân cấp dịch vụ GATT của một thiết bị BLE được kết nối. Nhà phát triển tương tác với thiết bị ngoại vi độc quyền thông qua CBPeripheral: khám phá dịch vụ (discoverServices:), đọc đặc tính (readValueForCharacteristic:), ghi dữ liệu (writeValue:forCharacteristic:type:) và đăng ký thông báo (setNotifyValue:forCharacteristic:). Theo Apple Developer, 2026, CBPeripheral là đối tượng trung tâm cho tất cả các thao tác với thiết bị ngoại vi BLE, được CBCentralManager trả về khi phát hiện hoặc kết nối thiết bị.

Những điểm chính

  • CBPeripheral là lớp Core Bluetooth để làm việc với thiết bị BLE từ xa trên iOS
  • Phân cấp GATT — Peripheral chứa dịch vụ (CBService), dịch vụ chứa đặc tính (CBCharacteristic), đặc tính chứa bộ mô tả (CBDescriptor)
  • Khám phá — discoverServices: và discoverCharacteristics:forService: để lấy cấu trúc GATT của thiết bị
  • Đọc và ghi — readValueForCharacteristic: và writeValue:forCharacteristic:type: có phản hồi (withResponse) hoặc không (withoutResponse)
  • Thông báo — setNotifyValue:forCharacteristic: kích hoạt đăng ký thay đổi đặc tính của thiết bị BLE

CBPeripheral là gì: bản chất và mục đích

CBPeripheral là một đối tượng đại diện cho thiết bị BLE từ xa trong ứng dụng iOS. Không giống như CBCentralManager quản lý bộ điều hợp Bluetooth cục bộ của iPhone, CBPeripheral mô hình hóa một thiết bị ngoại vi bên ngoài: cảm biến, trình theo dõi sức khỏe, beacon hoặc thiết bị y tế. Mỗi phiên bản CBPeripheral chứa một định danh duy nhất (UUID) tồn tại giữa các phiên kết nối — Apple liên kết UUID với một thiết bị cụ thể thông qua Bonding hệ thống.

CBPeripheral không được tạo trực tiếp qua init. Framework Core Bluetooth trả về đối tượng CBPeripheral trong hai tình huống: khi thiết bị được phát hiện qua scanForPeripheralsWithServices: (delegate didDiscoverPeripheral) và khi kết nối với thiết bị đã biết trước đó qua retrievePeripheralsWithIdentifiers:. Sau khi nhận được đối tượng, nhà phát triển gọi connectPeripheral: trên CBCentralManager, sau đó CBPeripheral có sẵn cho các thao tác GATT.

Vòng đời CBPeripheral bao gồm sáu trạng thái: ngắt kết nối (ban đầu), đang kết nối (sau khi gọi connect), đã kết nối (sau didConnectPeripheral), đang khám phá (trong khi gọi discoverServices), đã khám phá (sau khi nhận dịch vụ) và đang ngắt kết nối (sau cancelPeripheralConnection). Mỗi trạng thái được theo dõi thông qua giao thức delegate CBPeripheralDelegate — bắt buộc cho bất kỳ ứng dụng BLE nào trên iOS.

CBPeripheral và phân cấp GATT: dịch vụ, đặc tính, bộ mô tả

CBPeripheral lưu trữ cấu trúc GATT phân cấp gồm ba cấp độ. Cấp độ gốc là một mảng CBService (dịch vụ), mỗi dịch vụ chứa một mảng CBCharacteristic (đặc tính), mỗi đặc tính chứa một mảng CBDescriptor (bộ mô tả). Mô hình này hoàn toàn tuân thủ đặc tả Bluetooth GATT: dịch vụ là một chức năng của thiết bị (ví dụ: “Dịch vụ nhịp tim”), đặc tính là một giá trị cụ thể (nhịp tim 72 bpm), bộ mô tả là siêu dữ liệu của đặc tính (đơn vị đo lường, cấu hình thông báo).

Cấp độLớp Core BluetoothMô tả
Dịch vụCBServiceNhóm logic các đặc tính liên quan, được xác định bằng UUID (16-bit, 32-bit hoặc 128-bit)
Đặc tínhCBCharacteristicGiá trị dữ liệu cụ thể, hỗ trợ đọc, ghi và thông báo
Bộ mô tảCBDescriptorSiêu dữ liệu của đặc tính: cấu hình máy khách CCCD, mô tả người dùng, định dạng trình bày

Các dịch vụ BLE tiêu chuẩn được đăng ký bởi Bluetooth SIG: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810). Đối với dịch vụ tùy chỉnh, UUID 128-bit được sử dụng (ví dụ: E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS tự động nhận dạng UUID tiêu chuẩn và hiển thị tên có thể đọc được; UUID tùy chỉnh hiển thị ở định dạng hex.

Sau khi kết nối, hệ thống phân cấp của CBPeripheral trống — dịch vụ và đặc tính chưa được tải. Nhà phát triển phải gọi discoverServices: để lấy dịch vụ và sau đó cho mỗi dịch vụ gọi discoverCharacteristics:forService:. Nếu dịch vụ chứa dịch vụ được bao gồm, hãy gọi thêm discoverIncludedServices:forService:. Chỉ sau khi hoàn tất khám phá hệ thống phân cấp, CBPeripheral mới được điền và có sẵn để đọc và ghi.

Khám phá dịch vụ và đặc tính: phương thức và delegate

Khám phá cấu trúc GATT của CBPeripheral là bước bắt buộc trước bất kỳ thao tác đọc hoặc ghi nào. Phương thức discoverServices: bắt đầu tìm kiếm không đồng bộ tất cả các dịch vụ của thiết bị. Nếu nil được truyền, tất cả dịch vụ được khám phá; nếu một mảng CBUUID được truyền — chỉ các dịch vụ có UUID được chỉ định (tối ưu hóa thời gian). Kết quả đến delegate peripheral:didDiscoverServices: — đối tượng CBPeripheral điền thuộc tính services của nó bằng một mảng CBService.

Sau khi nhận được dịch vụ, cho mỗi CBService cần gọi discoverCharacteristics:forService:. Tương tự, nil — tất cả đặc tính, mảng CBUUID — chỉ các đặc tính được chỉ định. Kết quả: peripheral:didDiscoverCharacteristicsForService:error:. Ở giai đoạn này, CBCharacteristic nhận các thuộc tính (properties: .read, .write, .notify, .indicate) xác định các thao tác được phép.

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

Trong ví dụ, CBPeripheralDelegate triển khai ba phương thức khám phá bắt buộc. didDiscoverServices lặp qua tất cả dịch vụ được tìm thấy và yêu cầu đặc tính. didDiscoverCharacteristicsForService kiểm tra thuộc tính của mỗi đặc tính: cho .read gọi readValue, cho .notify gọi setNotifyValue(true). Phương thức didUpdateValueForCharacteristic nhận giá trị thực tế ở định dạng Data.

Đọc và ghi đặc tính: withResponse và withoutResponse

Đọc giá trị của CBCharacteristic được thực hiện bằng phương thức readValueForCharacteristic:. Kết quả đến không đồng bộ trong peripheral:didUpdateValueForCharacteristic:error:. Quan trọng: thiết bị có thể có giá trị được lưu trong bộ nhớ đệm (characteristic.value có sẵn ngay sau khi khám phá), nhưng để lấy dữ liệu hiện tại, việc gọi readValue là bắt buộc. iOS có thể lưu trữ giá trị trong bộ nhớ đệm để tiết kiệm năng lượng — readValue làm mới bộ nhớ đệm.

Ghi giá trị được thực hiện bằng phương thức writeValue:forCharacteristic:type:. Tham số type xác định loại ghi: .withResponse (CBCharacteristicWriteWithResponse) — thiết bị xác nhận ghi qua didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — ghi không xác nhận, tốc độ tối đa nhưng không đảm bảo gửi. Đặc tả BLE giới hạn MTU (Đơn vị truyền tối đa): lên đến 23 byte cho BLE 4.0, lên đến 251 byte cho BLE 5.0+. Đối với dữ liệu lớn hơn MTU, cần phân mảnh ở cấp ứng dụng.

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

Việc chọn loại ghi withResponse hoặc withoutResponse phụ thuộc vào yêu cầu độ tin cậy. Đối với lệnh (bật đèn, mở khóa) hãy sử dụng withResponse — đảm bảo gửi là rất quan trọng. Đối với dữ liệu phát trực tuyến (nhịp tim, nhiệt độ) hãy sử dụng withoutResponse — mất một gói tin không đáng kể. Thiết bị BLE có thể chỉ hỗ trợ một loại ghi — kiểm tra thuộc tính characteristic.properties.contains(.write) và .writeWithoutResponse.

Đăng ký thông báo BLE qua setNotifyValue

Thông báo là cơ chế BLE trong đó thiết bị ngoại vi gửi giá trị đặc tính đến thiết bị trung tâm một cách không đồng bộ, không cần thăm dò liên tục từ phía trung tâm. CBPeripheral kích hoạt đăng ký thông qua phương thức setNotifyValue:forCharacteristic:. Sau khi kích hoạt đăng ký, iOS tự động ghi vào CCCD (Bộ mô tả cấu hình đặc tính máy khách) trên thiết bị ngoại vi và thiết bị bắt đầu gửi cập nhật mỗi khi giá trị thay đổi.

Không giống như indication, thông báo không yêu cầu xác nhận từ thiết bị trung tâm — gói tin được gửi và quên đi. Điều này cung cấp thông lượng tối đa, nhưng có thể mất gói tin. Indication yêu cầu xác nhận ở cấp giao thức (L2CAP) — đáng tin cậy hơn nhưng chậm hơn. Thuộc tính properties của CBCharacteristic cho biết chính xác chế độ nào được hỗ trợ: .notify, .indicate hoặc cả hai.

Khi CBPeripheral ngắt kết nối (mất kết nối, ra ngoài phạm vi), tất cả đăng ký đang hoạt động tự động được đặt lại. Khi kết nối lại, cần gọi lại setNotifyValue:true cho mỗi đặc tính. iOS cũng mất đăng ký khi ứng dụng rời khỏi nền trước (nếu chế độ nền không được bật) — để hoạt động nền, cần bật khả năng “Uses Bluetooth LE accessories” trong 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)")
        }
    }
}

NotificationManager minh họa xử lý đúng đắn các thông báo CBPeripheral. subscribeToAllNotifications lặp qua tất cả dịch vụ và đặc tính, kích hoạt .notify và .indicate. subscribedCharacteristics theo dõi các đăng ký đang hoạt động để hủy đăng ký thích hợp. didUpdateNotificationStateForCharacteristic xác nhận thay đổi trạng thái đăng ký thành công thông qua thuộc tính characteristic.isNotifying.

Ví dụ đầy đủ về CBPeripheral trong Swift

Quy trình làm việc đầy đủ với CBPeripheral bao gồm: lấy đối tượng từ CBCentralManager, kết nối, khám phá, đọc/ghi, đăng ký thông báo và ngắt kết nối. Ví dụ dưới đây triển khai lớp BLEConnection quản lý vòng đời đầy đủ của thiết bị ngoại vi BLE trong Swift sử dụng API async/await hiện đại (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
}

Lớp BLEConnection sử dụng Swift Concurrency (async/await) thông qua CheckedContinuation — một mẫu hiện đại để làm việc với API dựa trên delegate của Core Bluetooth. connect(to:) chờ xác nhận kết nối qua didConnectPeripheral, discoverServices() — qua didDiscoverServices. Cách tiếp cận này loại bỏ các delegate lồng nhau và làm cho mã BLE trở nên tuyến tính và dễ đọc. Xử lý lỗi thông qua BLEError bao gồm tất cả các kịch bản lỗi kết nối BLE điển hình.

Câu hỏi thường gặp

Làm thế nào để có CBPeripheral mà không cần quét?

CBPeripheral cho thiết bị đã kết nối trước đó có thể được lấy qua retrievePeripheralsWithIdentifiers: trên CBCentralManager. Truyền một mảng UUID (NSUUID) của các thiết bị đã lưu trước đó — framework trả về một mảng CBPeripheral cho các thiết bị trong cơ sở dữ liệu bonding BLE hệ thống. Điều này chỉ hoạt động cho các thiết bị mà iPhone đã ghép nối trước đó. Đối với thiết bị mới, việc quét là bắt buộc.

Tại sao CBPeripheral không khám phá dịch vụ?

Nguyên nhân phổ biến: thiết bị ngoài phạm vi (RSSI dưới ngưỡng), radio BLE tắt (CBCentralManager.state != .poweredOn), delegate CBPeripheralDelegate không được đặt (peripheral.delegate = self), hoặc discoverServices được gọi trước khi kết nối. Kiểm tra centralManager.state, đảm bảo delegate được đặt trước khi gọi connect và sử dụng thử lại với thời gian chờ 5–10 giây.

Phải làm gì nếu writeValue không phản hồi?

Nguyên nhân là sử dụng .withResponse trên đặc tính chỉ hỗ trợ .writeWithoutResponse, hoặc ngược lại. Kiểm tra characteristic.properties trước khi gọi. Một vấn đề khác có thể là MTU: nếu dữ liệu > 20 byte (MTU BLE 4.0), cần đàm phán MTU qua negotiateMTU hoặc phân mảnh. Sử dụng peripheral.maximumWriteValueLength(for: .withResponse) để xác định kích thước gói tối đa.

Làm thế nào để phân biệt CBPeripheral trong phạm vi với không khả dụng?

CBPeripheral ngoài phạm vi không ngắt kết nối ngay lập tức — iOS chuyển nó sang trạng thái .disconnected sau thời gian chờ (thường 20–30 giây). Để giám sát, sử dụng readRSSI trên CBPeripheral — nếu không khả dụng, nó sẽ trả về lỗi với mã CBError.connectionTimeout. Cũng theo dõi centralManager:didDisconnectPeripheral:error: để phát hiện kịp thời mất kết nối.

Có thể sử dụng một CBPeripheral từ nhiều luồng không?

Core Bluetooth không an toàn luồng — tất cả các cuộc gọi CBPeripheral phải được thực hiện từ cùng một hàng đợi (thường là hàng đợi chính hoặc hàng đợi tuần tự được chỉ định khi khởi tạo CBCentralManager). Các cuộc gọi đồng thời từ các luồng khác nhau dẫn đến điều kiện cạnh tranh và sự cố ứng dụng. Sử dụng DispatchQueue(label: “com.app.ble”) cho tất cả thao tác BLE và DispatchQueue.main.async để cập nhật UI.

Tổng kết

  • CBPeripheral là lớp Core Bluetooth để làm việc với thiết bị BLE từ xa trên iOS, được trả về bởi CBCentralManager
  • Phân cấp GATT bao gồm dịch vụ (CBService), đặc tính (CBCharacteristic) và bộ mô tả (CBDescriptor) với UUID 16-bit hoặc 128-bit
  • Khám phá được thực hiện tuần tự: discoverServices: → discoverCharacteristics:forService: với xử lý delegate
  • Đọc — readValueForCharacteristic:, ghi — writeValue:forCharacteristic:type: (.withResponse hoặc .withoutResponse)
  • Thông báo — setNotifyValue:forCharacteristic: kích hoạt truyền dữ liệu không đồng bộ từ ngoại vi đến trung tâm
  • MTU cho BLE 4.0 giới hạn gói ở 23 byte, BLE 5.0+ — lên đến 251 byte, dữ liệu vượt quá MTU cần phân mảnh
  • Swift async/await qua CheckedContinuation đơn giản hóa mã BLE, thay thế delegate lồng nhau bằng các cuộc gọi tuyến tính

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm