CBPeripheral: ما هو، طرقه، وإدارة الأجهزة الطرفية BLE على iOS

المؤلف: IT Sectr نُشر: 2026-07-16 وقت القراءة: 10 دق

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 هي فئة Core Bluetooth للعمل مع جهاز BLE بعيد على iOS
  • هرم GATT — Peripheral يحتوي على خدمات (CBService)، الخدمات تحتوي على خصائص (CBCharacteristic)، الخصائص تحتوي على واصفات (CBDescriptor)
  • الاكتشاف — discoverServices: و discoverCharacteristics:forService: للحصول على هيكل GATT للجهاز
  • القراءة والكتابة — readValueForCharacteristic: و writeValue:forCharacteristic:type: مع تأكيد (withResponse) أو بدون (withoutResponse)
  • الإشعارات — setNotifyValue:forCharacteristic: يفعل الاشتراك في تغييرات خصائص جهاز BLE

ما هو CBPeripheral: الجوهر والغرض

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: الخدمات والخصائص والواصفات

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) التي تحدد العمليات المسموح بها.

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

في المثال، CBPeripheralDelegate يطبق ثلاث طرق اكتشاف إلزامية. didDiscoverServices يتكرر على جميع الخدمات التي تم العثور عليها ويطلب الخصائص. didDiscoverCharacteristicsForService يتحقق من خصائص كل خاصية: لـ .read يستدعي readValue، لـ .notify يستدعي setNotifyValue(true). الطريقة didUpdateValueForCharacteristic تستقبل القيمة الفعلية بتنسيق Data.

قراءة وكتابة الخصائص: withResponse و withoutResponse

قراءة القيم لـ 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، يلزم التجزئة على مستوى التطبيق.

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

اختيار نوع الكتابة withResponse أو withoutResponse يعتمد على متطلبات الموثوقية. للأوامر (تشغيل الضوء، فتح القفل) استخدم withResponse — ضمان التسليم أمر بالغ الأهمية. للبيانات المتدفقة (النبض، درجة الحرارة) استخدم withoutResponse — فقدان حزمة واحدة غير مهم. قد يدعم جهاز BLE نوع كتابة واحد فقط — تحقق من characteristic.properties.contains(.write) و .writeWithoutResponse.

الاشتراك في إشعارات BLE عبر setNotifyValue

الإشعارات هي آلية BLE حيث يرسل الجهاز الطرفي قيم الخصائص إلى الجهاز المركزي بشكل غير متزامن، بدون استقصاء مستمر من الجانب المركزي. CBPeripheral يفعل الاشتراك من خلال الطريقة setNotifyValue:forCharacteristic:. بعد تفعيل الاشتراك، يكتب iOS تلقائياً في CCCD (واصف تكوين خاصية العميل) على الجهاز الطرفي، ويبدأ الجهاز في إرسال التحديثات كلما تغيرت القيمة.

على عكس التنبيهات (indications)، الإشعارات لا تتطلب تأكيداً من الجهاز المركزي — يتم إرسال الحزمة ونسيانها. هذا يوفر أقصى إنتاجية، ولكن من الممكن فقدان الحزم. التنبيهات تتطلب تأكيداً على مستوى البروتوكول (L2CAP) — أكثر موثوقية ولكن أبطأ. خاصية properties لـ CBCharacteristic تشير بدقة إلى أي وضع مدعوم: .notify، .indicate أو كلاهما.

عند قطع اتصال CBPeripheral (انقطاع، خروج عن النطاق)، يتم إعادة تعيين جميع الاشتراكات النشطة تلقائياً. عند إعادة الاتصال، يجب استدعاء setNotifyValue:true مرة أخرى لكل خاصية. iOS أيضاً يفقد الاشتراكات عندما يخرج التطبيق من المقدمة (إذا لم يكن وضع الخلفية مفعلاً) — للتشغيل في الخلفية، يجب تفعيل الإمكانية «Uses Bluetooth LE accessories» في 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 يوضح المعالجة الصحيحة لإشعارات CBPeripheral. subscribeToAllNotifications يتكرر على جميع الخدمات والخصائص، مفعلاً .notify و .indicate. subscribedCharacteristics يتتبع الاشتراكات النشطة لإلغاء الاشتراك بشكل صحيح. didUpdateNotificationStateForCharacteristic يؤكد تغيير حالة الاشتراك بنجاح عبر الخاصية characteristic.isNotifying.

مثال كامل لـ CBPeripheral في Swift

سير العمل الكامل مع CBPeripheral يشمل: الحصول على الكائن من CBCentralManager، الاتصال، الاكتشاف، القراءة/الكتابة، الاشتراك في الإشعارات وقطع الاتصال. المثال أدناه يطبق فئة BLEConnection التي تدير دورة حياة كاملة لجهاز BLE طرفي في Swift باستخدام API الحديث 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
}

فئة BLEConnection تستخدم Swift Concurrency (async/await) عبر CheckedContinuation — نمط حديث للعمل مع APIs القائمة على المفوضين في Core Bluetooth. connect(to:) ينتظر تأكيد الاتصال عبر didConnectPeripheral، discoverServices() — عبر didDiscoverServices. هذا النهج يلغي الحاجة إلى المفوضين المتداخلين ويجعل كود BLE خطياً وقابلاً للقراءة. معالجة الأخطاء عبر BLEError تغطي جميع سيناريوهات فشل اتصال BLE النموذجية.

الأسئلة الشائعة

كيفية الحصول على CBPeripheral بدون مسح ضوئي؟

CBPeripheral لجهاز متصل سابقاً يمكن الحصول عليه عبر retrievePeripheralsWithIdentifiers: على CBCentralManager. مرر مصفوفة من UUIDs (NSUUID) للأجهزة المحفوظة سابقاً — يعيد الإطار مصفوفة من CBPeripheral للأجهزة في قاعدة بيانات ربط BLE النظامية. هذا يعمل فقط للأجهزة التي تم إقران iPhone بها سابقاً. لجهاز جديد، المسح الضوئي إلزامي.

لماذا لا يكتشف CBPeripheral الخدمات؟

الأسباب الشائعة: الجهاز خارج نطاق التغطية (RSSI أقل من الحد الأدنى)، راديو BLE متوقف (CBCentralManager.state != .poweredOn)، لم يتم تعيين مفوض CBPeripheralDelegate (peripheral.delegate = self)، أو تم استدعاء discoverServices قبل الاتصال. تحقق من centralManager.state، تأكد من تعيين المفوض قبل استدعاء connect واستخدم إعادة المحاولة مع مهلة 5–10 ثوانٍ.

ماذا تفعل إذا لم يستجب writeValue؟

السبب هو استخدام .withResponse على خاصية تدعم فقط .writeWithoutResponse، أو العكس. تحقق من characteristic.properties قبل الاستدعاء. مشكلة أخرى محتملة هي MTU: إذا كانت البيانات > 20 بايت (MTU BLE 4.0)، يلزم التفاوض على MTU عبر negotiateMTU أو التجزئة. استخدم peripheral.maximumWriteValueLength(for: .withResponse) لتحديد الحد الأقصى لحجم الحزمة.

كيفية التمييز بين CBPeripheral في النطاق وغير المتاح؟

CBPeripheral خارج النطاق لا ينقطع فوراً — iOS ينقله إلى حالة .disconnected بعد مهلة (عادة 20–30 ثانية). للمراقبة، استخدم readRSSI على CBPeripheral — إذا كان غير متاح، سيعيد خطأ برمز CBError.connectionTimeout. راقب أيضاً centralManager:didDisconnectPeripheral:error: للكشف في الوقت المناسب عن فقدان الاتصال.

هل يمكن استخدام CBPeripheral واحد من خيوط متعددة؟

Core Bluetooth ليس آمناً للخيوط — يجب تنفيذ جميع استدعاءات CBPeripheral من نفس قائمة الانتظار (عادة قائمة الانتظار الرئيسية أو قائمة انتظار تسلسلية محددة عند تهيئة CBCentralManager). الاستدعاءات المتزامنة من خيوط مختلفة تؤدي إلى حالات سباق وتعطل التطبيق. استخدم DispatchQueue(label: «com.app.ble») لجميع عمليات BLE و DispatchQueue.main.async لتحديثات واجهة المستخدم.

الملخص

  • CBPeripheral هي فئة Core Bluetooth للعمل مع جهاز BLE بعيد على iOS، يتم إرجاعها بواسطة CBCentralManager
  • هرم GATT يتكون من خدمات (CBService)، خصائص (CBCharacteristic) وواصفات (CBDescriptor) مع UUIDs 16 بت أو 128 بت
  • الاكتشاف يتم بالتسلسل: discoverServices: ← discoverCharacteristics:forService: مع معالجة عبر المفوض
  • القراءة — readValueForCharacteristic:، الكتابة — writeValue:forCharacteristic:type: (.withResponse أو .withoutResponse)
  • الإشعارات — setNotifyValue:forCharacteristic: يفعل الإرسال غير المتزامن للبيانات من الطرفي إلى المركزي
  • MTU لـ BLE 4.0 يحد الحزم بـ 23 بايت، BLE 5.0+ — حتى 251 بايت، البيانات التي تتجاوز MTU تتطلب تجزئة
  • Swift async/await عبر CheckedContinuation تبسط كود BLE، مستبدلة المفوضين المتداخلين باستدعاءات خطية

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا