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 نبضة في الدقيقة)، الواصف هو بيانات وصفية للخاصية (وحدات القياس، تكوين الإشعارات).
| المستوى | فئة Core Bluetooth | الوصف |
|---|---|---|
| الخدمة | CBService | مجموعة منطقية من الخصائص ذات الصلة، محددة بواسطة UUID (16 بت أو 32 بت أو 128 بت) |
| الخاصية | CBCharacteristic | قيمة بيانات محددة، تدعم القراءة والكتابة والإشعارات |
| الواصف | CBDescriptor | بيانات وصفية للخاصية: تكوين العميل CCCD، وصف المستخدم، تنسيق العرض |
خدمات BLE القياسية مسجلة من قبل Bluetooth SIG: Heart Rate Service (UUID 180D)، Battery Service (180F)، Device Information (180A)، Blood Pressure (1810). للخدمات المخصصة، يتم استخدام UUIDs 128 بت (مثل E20A39F4-73F5-4BC4-A12F-17D1AD07A961). iOS يتعرف تلقائياً على UUIDs القياسية ويعرض أسماء قابلة للقراءة؛ UUIDs المخصصة تظهر بتنسيق سداسي عشري.
بعد الاتصال، يكون هرم CBPeripheral فارغاً — الخدمات والخصائص غير محملة. يجب على المطور استدعاء discoverServices: للحصول على الخدمات ثم لكل خدمة استدعاء discoverCharacteristics:forService:. إذا كانت الخدمة تحتوي على خدمات مضمنة، يتم استدعاء discoverIncludedServices:forService: بالإضافة. فقط بعد اكتمال اكتشاف الهرم، يصبح CBPeripheral ممتلئاً ومتاحاً للقراءة والكتابة.
اكتشاف هيكل GATT لـ CBPeripheral هو خطوة إلزامية قبل أي عمليات قراءة أو كتابة. الطريقة discoverServices: تبدأ بحثاً غير متزامن لجميع خدمات الجهاز. إذا تم تمرير nil، يتم اكتشاف جميع الخدمات؛ إذا تم تمرير مصفوفة من CBUUID — فقط الخدمات ذات UUIDs المحددة (تحسين الوقت). تصل النتيجة إلى المفوض 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 — نمط حديث للعمل مع APIs القائمة على المفوضين في Core Bluetooth. connect(to:) ينتظر تأكيد الاتصال عبر didConnectPeripheral، discoverServices() — عبر didDiscoverServices. هذا النهج يلغي الحاجة إلى المفوضين المتداخلين ويجعل كود BLE خطياً وقابلاً للقراءة. معالجة الأخطاء عبر BLEError تغطي جميع سيناريوهات فشل اتصال BLE النموذجية.
الأسئلة الشائعة
CBPeripheral لجهاز متصل سابقاً يمكن الحصول عليه عبر retrievePeripheralsWithIdentifiers: على CBCentralManager. مرر مصفوفة من UUIDs (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). الاستدعاءات المتزامنة من خيوط مختلفة تؤدي إلى حالات سباق وتعطل التطبيق. استخدم DispatchQueue(label: «com.app.ble») لجميع عمليات BLE و DispatchQueue.main.async لتحديثات واجهة المستخدم.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.