CBPeripheral เป็นคลาสของเฟรมเวิร์ก Core Bluetooth ที่แทนอุปกรณ์ BLE ระยะไกลบน iOS วัตถุ CBPeripheral แต่ละตัวห่อหุ้ม UUID, ชื่อ, RSSI และลำดับชั้นบริการ GATT ของอุปกรณ์ BLE ที่เชื่อมต่อ นักพัฒนาโต้ตอบกับอุปกรณ์ต่อพ่วงผ่าน CBPeripheral เท่านั้น: การค้นหาบริการ (discoverServices:), การอ่านคุณลักษณะ (readValueForCharacteristic:), การเขียนข้อมูล (writeValue:forCharacteristic:type:) และการสมัครรับการแจ้งเตือน (setNotifyValue:forCharacteristic:) ตาม Apple Developer, 2026, CBPeripheral เป็นวัตถุศูนย์กลางสำหรับการดำเนินการกับอุปกรณ์ต่อพ่วง BLE ทั้งหมด ซึ่งส่งคืนโดย CBCentralManager เมื่อค้นพบหรือเชื่อมต่ออุปกรณ์
ประเด็นสำคัญ
CBPeripheral เป็นวัตถุที่แทนอุปกรณ์ BLE ระยะไกลในแอปพลิเคชัน iOS ต่างจาก CBCentralManager ที่จัดการอะแดปเตอร์ Bluetooth ในเครื่องของ iPhone, CBPeripheral จะจำลองอุปกรณ์ต่อพ่วงภายนอก: เซ็นเซอร์, ตัวติดตามฟิตเนส, บีคอน หรือเครื่องมือทางการแพทย์ อินสแตนซ์ CBPeripheral แต่ละตัวประกอบด้วยตัวระบุเฉพาะ (UUID) ซึ่งคงอยู่ระหว่างเซสชันการเชื่อมต่อ — Apple เชื่อมโยง UUID กับอุปกรณ์เฉพาะผ่านระบบ Bonding
CBPeripheral ไม่ได้ถูกสร้างขึ้นโดยตรงผ่าน init เฟรมเวิร์ก Core Bluetooth ส่งคืนวัตถุ CBPeripheral ในสองสถานการณ์: เมื่อค้นพบอุปกรณ์ผ่าน scanForPeripheralsWithServices: (ตัวแทน didDiscoverPeripheral) และเมื่อเชื่อมต่อกับอุปกรณ์ที่รู้จักก่อนหน้านี้ผ่าน retrievePeripheralsWithIdentifiers: หลังจากได้รับวัตถุ นักพัฒนาเรียก connectPeripheral: บน CBCentralManager หลังจากนั้น CBPeripheral จะพร้อมใช้งานสำหรับการดำเนินการ GATT
วงจรชีวิตของ CBPeripheral ประกอบด้วยหกสถานะ: ตัดการเชื่อมต่อ (เริ่มต้น), กำลังเชื่อมต่อ (หลังเรียก connect), เชื่อมต่อแล้ว (หลัง didConnectPeripheral), กำลังค้นพบ (ระหว่างเรียก discoverServices), ค้นพบแล้ว (หลังจากได้รับบริการ) และกำลังตัดการเชื่อมต่อ (หลัง cancelPeripheralConnection) แต่ละสถานะถูกติดตามผ่านโพรโทคอลตัวแทน CBPeripheralDelegate — สิ่งจำเป็นสำหรับแอปพลิเคชัน BLE บน iOS
CBPeripheral จัดเก็บโครงสร้าง GATT แบบลำดับชั้นที่ประกอบด้วยสามระดับ ระดับรากคืออาร์เรย์ของ CBService (บริการ), แต่ละบริการประกอบด้วยอาร์เรย์ของ CBCharacteristic (คุณลักษณะ), แต่ละคุณลักษณะประกอบด้วยอาร์เรย์ของ CBDescriptor (ตัวอธิบาย) โมเดลนี้สอดคล้องกับข้อกำหนด Bluetooth GATT อย่างสมบูรณ์: บริการคือฟังก์ชันของอุปกรณ์ (เช่น “บริการอัตราการเต้นของหัวใจ”), คุณลักษณะคือค่าเฉพาะ (ชีพจร 72 bpm), ตัวอธิบายคือเมตาดาต้าของคุณลักษณะ (หน่วยวัด, การกำหนดค่าการแจ้งเตือน)
| ระดับ | คลาส Core Bluetooth | คำอธิบาย |
|---|---|---|
| บริการ | CBService | กลุ่มตรรกะของคุณลักษณะที่เกี่ยวข้อง ระบุโดย UUID (16-bit, 32-bit หรือ 128-bit) |
| คุณลักษณะ | CBCharacteristic | ค่าข้อมูลเฉพาะ รองรับการอ่าน การเขียน และการแจ้งเตือน |
| ตัวอธิบาย | CBDescriptor | เมตาดาต้าของคุณลักษณะ: การกำหนดค่าไคลเอนต์ CCCD, คำอธิบายผู้ใช้, รูปแบบการนำเสนอ |
บริการ BLE มาตรฐานถูกลงทะเบียนโดย Bluetooth SIG: Heart Rate Service (UUID 180D), Battery Service (180F), Device Information (180A), Blood Pressure (1810) สำหรับบริการที่กำหนดเอง จะใช้ UUID 128-bit (เช่น E20A39F4-73F5-4BC4-A12F-17D1AD07A961) iOS จะรู้จัก UUID มาตรฐานโดยอัตโนมัติและแสดงชื่อที่มนุษย์อ่านได้; UUID ที่กำหนดเองจะแสดงในรูปแบบเลขฐานสิบหก
หลังจากการเชื่อมต่อ ลำดับชั้นของ CBPeripheral จะว่างเปล่า — บริการและคุณลักษณะยังไม่ได้โหลด นักพัฒนาต้องเรียก discoverServices: เพื่อรับบริการ จากนั้นสำหรับแต่ละบริการเรียก discoverCharacteristics:forService: หากบริการมีบริการที่รวมอยู่ ให้เรียก discoverIncludedServices:forService: เพิ่มเติม เฉพาะเมื่อการค้นพบลำดับชั้นเสร็จสมบูรณ์ CBPeripheral จึงจะถูกเติมข้อมูลและพร้อมสำหรับการอ่านและการเขียน
การค้นพบ โครงสร้าง GATT ของ CBPeripheral เป็นขั้นตอนบังคับก่อนการดำเนินการอ่านหรือเขียนใดๆ เมธอด discoverServices: เริ่มการค้นหาแบบอะซิงโครนัสของบริการทั้งหมดของอุปกรณ์ หากส่ง nil, บริการทั้งหมดจะถูกค้นพบ; หากส่งอาร์เรย์ของ CBUUID — เฉพาะบริการที่มี UUID ที่ระบุ (การเพิ่มประสิทธิภาพเวลา) ผลลัพธ์มาถึงตัวแทน peripheral:didDiscoverServices: — วัตถุ CBPeripheral เติมคุณสมบัติ services ด้วยอาร์เรย์ของ CBService
หลังจากได้รับบริการ สำหรับแต่ละ CBService จำเป็นต้องเรียก discoverCharacteristics:forService: ในทำนองเดียวกัน nil — คุณลักษณะทั้งหมด, อาร์เรย์ของ CBUUID — เฉพาะที่ระบุ ผลลัพธ์: peripheral:didDiscoverCharacteristicsForService:error: ในขั้นตอนนี้ CBCharacteristic จะได้รับคุณสมบัติ (properties: .read, .write, .notify, .indicate) ซึ่งกำหนดการดำเนินการที่อนุญาต
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)")
}
}
ในตัวอย่าง CBPeripheralDelegate ใช้เมธอดการค้นพบสามรายการที่บังคับ didDiscoverServices วนซ้ำบริการทั้งหมดที่พบและขอคุณลักษณะ didDiscoverCharacteristicsForService ตรวจสอบคุณสมบัติของคุณลักษณะแต่ละรายการ: สำหรับ .read เรียก readValue, สำหรับ .notify เรียก setNotifyValue(true) เมธอด didUpdateValueForCharacteristic รับค่าจริงในรูปแบบ Data
การอ่านค่า ของ CBCharacteristic ดำเนินการโดยใช้เมธอด readValueForCharacteristic: ผลลัพธ์มาถึงแบบอะซิงโครนัสใน peripheral:didUpdateValueForCharacteristic:error: สำคัญ: อุปกรณ์อาจมีค่าในแคช (characteristic.value พร้อมใช้งานทันทีหลังการค้นพบ) แต่เพื่อรับข้อมูลปัจจุบัน การเรียก readValue เป็นสิ่งบังคับ iOS อาจแคชค่าเพื่อประสิทธิภาพการใช้พลังงาน — readValue รีเฟรชแคช
การเขียนค่า ดำเนินการโดยใช้เมธอด writeValue:forCharacteristic:type: พารามิเตอร์ type กำหนดประเภทการเขียน: .withResponse (CBCharacteristicWriteWithResponse) — อุปกรณ์ยืนยันการเขียนผ่าน didWriteValueForCharacteristic; .withoutResponse (CBCharacteristicWriteWithoutResponse) — การเขียนโดยไม่มีการยืนยัน ความเร็วสูงสุดแต่ไม่รับประกันการส่ง ข้อกำหนด BLE จำกัด MTU (หน่วยส่งสูงสุด): สูงสุด 23 ไบต์สำหรับ BLE 4.0, สูงสุด 251 ไบต์สำหรับ BLE 5.0+ สำหรับข้อมูลที่ใหญ่กว่า MTU จำเป็นต้องมีการแยกส่วนในระดับแอปพลิเคชัน
// 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 })
}
}
การเลือกประเภทการเขียน withResponse หรือ withoutResponse ขึ้นอยู่กับข้อกำหนดความน่าเชื่อถือ สำหรับคำสั่ง (เปิดไฟ, ปลดล็อก) ใช้ withResponse — การรับประกันการส่งมีความสำคัญ สำหรับข้อมูลสตรีมมิ่ง (ชีพจร, อุณหภูมิ) ใช้ withoutResponse — การสูญเสียแพ็กเก็ตหนึ่งไม่สำคัญ อุปกรณ์ BLE อาจรองรับการเขียนเพียงประเภทเดียว — ตรวจสอบคุณสมบัติ characteristic.properties.contains(.write) และ .writeWithoutResponse
การแจ้งเตือน เป็นกลไก BLE ที่อุปกรณ์ต่อพ่วงส่งค่าคุณลักษณะไปยังอุปกรณ์กลางแบบอะซิงโครนัส โดยไม่ต้องสอบถามอย่างต่อเนื่องจากฝ่ายกลาง CBPeripheral เปิดใช้งานการสมัครรับข้อมูลผ่านเมธอด setNotifyValue:forCharacteristic: หลังจากเปิดใช้งานการสมัคร iOS จะเขียนลงใน CCCD (ตัวอธิบายการกำหนดค่าคุณลักษณะไคลเอนต์) บนอุปกรณ์ต่อพ่วงโดยอัตโนมัติ และอุปกรณ์เริ่มส่งการอัปเดตทุกครั้งที่มีการเปลี่ยนแปลงค่า
ต่างจากการบอก (indications), การแจ้งเตือน ไม่ต้องการการยืนยันจากอุปกรณ์กลาง — แพ็กเก็ตถูกส่งและถูกลืม สิ่งนี้ให้ปริมาณงานสูงสุด แต่แพ็กเก็ตอาจสูญหายได้ การบอกต้องการการยืนยันในระดับโพรโทคอล (L2CAP) — เชื่อถือได้มากกว่าแต่ช้ากว่า คุณสมบัติ properties ของ CBCharacteristic บ่งชี้อย่างแม่นยำว่ารองรับโหมดใด: .notify, .indicate หรือทั้งสองอย่าง
เมื่อ CBPeripheral ตัดการเชื่อมต่อ (ตัดการเชื่อมต่อ, อยู่นอกระยะ) การสมัครที่ใช้งานอยู่ทั้งหมดจะถูกรีเซ็ตโดยอัตโนมัติ เมื่อเชื่อมต่อใหม่ ต้องเรียก setNotifyValue:true อีกครั้งสำหรับแต่ละคุณลักษณะ iOS ยังสูญเสียการสมัครเมื่อแอปพลิเคชันออกจากเบื้องหน้า (หากไม่ได้เปิดใช้งานโหมดพื้นหลัง) — สำหรับการทำงานในพื้นหลัง ต้องเปิดใช้งานความสามารถ “Uses Bluetooth LE accessories” ใน 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)")
}
}
}
NotificationManager แสดงการจัดการการแจ้งเตือน CBPeripheral ที่ถูกต้อง subscribeToAllNotifications วนซ้ำบริการและคุณลักษณะทั้งหมด โดยเปิดใช้งาน .notify และ .indicate subscribedCharacteristics ติดตามการสมัครที่ใช้งานอยู่เพื่อยกเลิกการสมัครอย่างเหมาะสม didUpdateNotificationStateForCharacteristic ยืนยันการเปลี่ยนแปลงสถานะการสมัครที่สำเร็จผ่านคุณสมบัติ characteristic.isNotifying
เวิร์กโฟลว์ที่สมบูรณ์ กับ CBPeripheral รวมถึง: การรับวัตถุจาก CBCentralManager, การเชื่อมต่อ, การค้นพบ, การอ่าน/เขียน, การสมัครรับการแจ้งเตือนและการตัดการเชื่อมต่อ ตัวอย่างด้านล่างใช้คลาส BLEConnection ที่จัดการวงจรชีวิตอุปกรณ์ต่อพ่วง BLE ที่สมบูรณ์ใน Swift โดยใช้ API 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
}
คลาส BLEConnection ใช้ Swift Concurrency (async/await) ผ่าน CheckedContinuation — รูปแบบที่ทันสมัยสำหรับทำงานกับ API ที่ใช้ตัวแทนของ Core Bluetooth connect(to:) รอการยืนยันการเชื่อมต่อผ่าน didConnectPeripheral, discoverServices() — ผ่าน didDiscoverServices วิธีการนี้กำจัดตัวแทนที่ซ้อนกันและทำให้โค้ด BLE เป็นเชิงเส้นและอ่านง่าย การจัดการข้อผิดพลาดผ่าน BLEError ครอบคลุมสถานการณ์ความล้มเหลวของการเชื่อมต่อ BLE ทั่วไปทั้งหมด
คำถามที่พบบ่อย
CBPeripheral สำหรับอุปกรณ์ที่เชื่อมต่อก่อนหน้านี้สามารถรับได้ผ่าน retrievePeripheralsWithIdentifiers: บน CBCentralManager ส่งอาร์เรย์ของ UUID (NSUUID) ของอุปกรณ์ที่บันทึกไว้ก่อนหน้านี้ — เฟรมเวิร์กส่งคืนอาร์เรย์ของ CBPeripheral สำหรับอุปกรณ์ในฐานข้อมูลการเชื่อมโยง BLE ของระบบ วิธีนี้ใช้ได้เฉพาะกับอุปกรณ์ที่ iPhone จับคู่มาก่อน สำหรับอุปกรณ์ใหม่ การสแกนเป็นสิ่งจำเป็น
สาเหตุทั่วไป: อุปกรณ์อยู่นอกระยะ (RSSI ต่ำกว่าเกณฑ์), วิทยุ BLE ปิดอยู่ (CBCentralManager.state != .poweredOn), ตัวแทน CBPeripheralDelegate ไม่ได้ตั้งค่า (peripheral.delegate = self), หรือเรียก discoverServices ก่อนการเชื่อมต่อ ตรวจสอบ centralManager.state, ตรวจสอบให้แน่ใจว่าตั้งค่าตัวแทนก่อนเรียก connect และใช้การลองใหม่ด้วยการหมดเวลา 5–10 วินาที
สาเหตุคือการใช้ .withResponse กับคุณลักษณะที่รองรับเฉพาะ .writeWithoutResponse หรือกลับกัน ตรวจสอบ characteristic.properties ก่อนเรียก ปัญหาอื่นที่อาจเกิดขึ้นคือ MTU: หากข้อมูลเกิน 20 ไบต์ (MTU BLE 4.0) จำเป็นต้องเจรจา MTU ผ่าน negotiateMTU หรือการแยกส่วน ใช้ peripheral.maximumWriteValueLength(for: .withResponse) เพื่อกำหนดขนาดแพ็กเก็ตสูงสุด
CBPeripheral ที่อยู่นอกระยะไม่ได้ตัดการเชื่อมต่อทันที — iOS เปลี่ยนเป็นสถานะ .disconnected หลังจากหมดเวลา (โดยปกติ 20–30 วินาที) สำหรับการตรวจสอบ ใช้ readRSSI บน CBPeripheral — หากไม่มีให้ใช้ จะส่งคืนข้อผิดพลาดด้วยรหัส CBError.connectionTimeout ตรวจสอบ centralManager:didDisconnectPeripheral:error: เพื่อตรวจจับการสูญเสียการเชื่อมต่อได้ทันท่วงที
Core Bluetooth ไม่ปลอดภัยต่อเธรด — การเรียก CBPeripheral ทั้งหมดต้องทำจากคิวเดียวกัน (โดยปกติคือคิวหลักหรือคิวแบบอนุกรมที่ระบุเมื่อเริ่มต้น CBCentralManager) การเรียกพร้อมกันจากเธรดต่างกันทำให้เกิด race condition และแอปพลิเคชันขัดข้อง ใช้ DispatchQueue(label: “com.app.ble”) สำหรับการดำเนินการ BLE ทั้งหมดและ DispatchQueue.main.async สำหรับการอัปเดต UI
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม