CBPeripheral হল Core Bluetooth ফ্রেমওয়ার্কের একটি ক্লাস যা iOS-এ একটি দূরবর্তী BLE ডিভাইস উপস্থাপন করে। প্রতিটি CBPeripheral অবজেক্ট একটি সংযুক্ত BLE ডিভাইসের UUID, নাম, RSSI এবং GATT সার্ভিস শ্রেণিবিন্যাস ধারণ করে। ডেভেলপার পেরিফেরালের সাথে একচেটিয়াভাবে CBPeripheral-এর মাধ্যমে ইন্টারঅ্যাক্ট করে: সার্ভিস ডিসকভারি (discoverServices:), ক্যারেক্টারিস্টিক পড়া (readValueForCharacteristic:), ডেটা লেখা (writeValue:forCharacteristic:type:) এবং নোটিফিকেশন সাবস্ক্রিপশন (setNotifyValue:forCharacteristic:)। Apple Developer, 2026 অনুসারে, CBPeripheral হল সমস্ত BLE পেরিফেরাল অপারেশনের কেন্দ্রীয় অবজেক্ট, যা ডিভাইস আবিষ্কার বা সংযোগের সময় CBCentralManager দ্বারা ফেরত দেওয়া হয়।
মূল পয়েন্ট
CBPeripheral হল একটি অবজেক্ট যা iOS অ্যাপ্লিকেশনে একটি দূরবর্তী BLE ডিভাইস উপস্থাপন করে। CBCentralManager-এর বিপরীতে, যা iPhone-এর স্থানীয় Bluetooth অ্যাডাপ্টার পরিচালনা করে, CBPeripheral একটি বাহ্যিক পেরিফেরাল ডিভাইস মডেল করে: সেন্সর, ফিটনেস ট্র্যাকার, বীকন বা চিকিৎসা যন্ত্র। প্রতিটি CBPeripheral ইনস্ট্যান্সে একটি অনন্য শনাক্তকারী (UUID) থাকে যা সংযোগ সেশনের মধ্যে স্থায়ী হয় — Apple সিস্টেম বন্ডিংয়ের মাধ্যমে UUID-কে একটি নির্দিষ্ট ডিভাইসের সাথে লিঙ্ক করে।
CBPeripheral সরাসরি init-এর মাধ্যমে তৈরি করা হয় না। Core Bluetooth ফ্রেমওয়ার্ক দুটি পরিস্থিতিতে CBPeripheral অবজেক্ট ফেরত দেয়: scanForPeripheralsWithServices:-এর মাধ্যমে ডিভাইস আবিষ্কারের সময় (ডেলিগেট didDiscoverPeripheral) এবং retrievePeripheralsWithIdentifiers:-এর মাধ্যমে পূর্বে পরিচিত ডিভাইসে সংযোগের সময়। অবজেক্ট পাওয়ার পর, ডেভেলপার CBCentralManager-এ connectPeripheral: কল করে, যার পরে CBPeripheral GATT অপারেশনের জন্য উপলব্ধ হয়।
CBPeripheral জীবনচক্র ছয়টি অবস্থা অন্তর্ভুক্ত করে: সংযোগ বিচ্ছিন্ন (প্রাথমিক), সংযোগ হচ্ছে (connect কলের পরে), সংযুক্ত (didConnectPeripheral-এর পরে), আবিষ্কার করছে (discoverServices কলের সময়), আবিষ্কৃত (সার্ভিস পাওয়ার পরে) এবং সংযোগ বিচ্ছিন্ন করছে (cancelPeripheralConnection-এর পরে)। প্রতিটি অবস্থা CBPeripheralDelegate প্রোটোকলের মাধ্যমে ট্র্যাক করা হয় — iOS-এ যেকোনো BLE অ্যাপ্লিকেশনের জন্য বাধ্যতামূলক।
CBPeripheral তিনটি স্তর নিয়ে গঠিত একটি শ্রেণিবিন্যাসিত GATT কাঠামো সংরক্ষণ করে। মূল স্তরটি হল CBService (সার্ভিস)-এর একটি অ্যারে, প্রতিটি সার্ভিসে CBCharacteristic (ক্যারেক্টারিস্টিক)-এর একটি অ্যারে থাকে, প্রতিটি ক্যারেক্টারিস্টিকে CBDescriptor (ডেসক্রিপ্টর)-এর একটি অ্যারে থাকে। এই মডেলটি Bluetooth GATT স্পেসিফিকেশনের সাথে সম্পূর্ণ সামঞ্জস্যপূর্ণ: সার্ভিস হল একটি ডিভাইস ফাংশন (যেমন, “হার্ট রেট সার্ভিস”), ক্যারেক্টারিস্টিক হল একটি নির্দিষ্ট মান (নাড়ি 72 bpm), ডেসক্রিপ্টর হল ক্যারেক্টারিস্টিক মেটাডেটা (পরিমাপের একক, নোটিফিকেশন কনফিগারেশন)।
| স্তর | 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)। কাস্টম সার্ভিসের জন্য 128-বিট UUID ব্যবহার করা হয় (যেমন, E20A39F4-73F5-4BC4-A12F-17D1AD07A961)। iOS স্বয়ংক্রিয়ভাবে মানক UUID চিনতে পারে এবং মানব-পাঠযোগ্য নাম প্রদর্শন করে; কাস্টম UUID হেক্স ফর্ম্যাটে প্রদর্শিত হয়।
সংযোগের পরে, CBPeripheral-এর শ্রেণিবিন্যাস খালি থাকে — সার্ভিস এবং ক্যারেক্টারিস্টিক লোড হয় না। ডেভেলপারকে সার্ভিস পেতে discoverServices: কল করতে হবে এবং তারপর প্রতিটি সার্ভিসের জন্য discoverCharacteristics:forService: কল করতে হবে। যদি সার্ভিসে অন্তর্ভুক্ত সার্ভিস থাকে, তবে অতিরিক্তভাবে discoverIncludedServices:forService: কল করা হয়। শ্রেণিবিন্যাস আবিষ্কার সম্পূর্ণ হওয়ার পরেই CBPeripheral পূর্ণ হয় এবং পড়া ও লেখার জন্য উপলব্ধ হয়।
আবিষ্কার CBPeripheral-এর GATT কাঠামোর যেকোনো পড়া বা লেখার অপারেশনের আগে একটি বাধ্যতামূলক পদক্ষেপ। 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 (সর্বোচ্চ ট্রান্সমিশন ইউনিট) সীমিত করে: BLE 4.0-এর জন্য 23 বাইট পর্যন্ত, BLE 5.0+-এর জন্য 251 বাইট পর্যন্ত। 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 (ক্লায়েন্ট ক্যারেক্টারিস্টিক কনফিগারেশন ডেসক্রিপ্টর)-এ লেখে এবং ডিভাইস মান পরিবর্তন হলে আপডেট পাঠাতে শুরু করে।
ইন্ডিকেশনের বিপরীতে, নোটিফিকেশন-এর কেন্দ্রীয় ডিভাইস থেকে নিশ্চিতকরণের প্রয়োজন নেই — প্যাকেট পাঠানো হয় এবং ভুলে যাওয়া হয়। এটি সর্বোচ্চ থ্রুপুট প্রদান করে, কিন্তু প্যাকেট হারানো সম্ভব। ইন্ডিকেশনের প্রোটোকল স্তরে (L2CAP) নিশ্চিতকরণ প্রয়োজন — আরও নির্ভরযোগ্য কিন্তু ধীর। CBCharacteristic-এর properties প্রপার্টি সঠিকভাবে নির্দেশ করে কোন মোড সমর্থিত: .notify, .indicate বা উভয়ই।
যখন CBPeripheral সংযোগ বিচ্ছিন্ন হয় (ডিসকানেক্ট, সীমার বাইরে), সমস্ত সক্রিয় সাবস্ক্রিপশন স্বয়ংক্রিয়ভাবে রিসেট হয়। পুনঃসংযোগের পর, প্রতিটি ক্যারেক্টারিস্টিকের জন্য আবার setNotifyValue:true কল করতে হবে। iOS অ্যাপ্লিকেশন foreground ছাড়লেও সাবস্ক্রিপশন হারায় (যদি ব্যাকগ্রাউন্ড মোড সক্রিয় না থাকে) — ব্যাকগ্রাউন্ড অপারেশনের জন্য Info.plist-এ “Uses Bluetooth LE accessories” ক্ষমতা সক্রিয় করতে হবে।
// 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 ক্লাস প্রয়োগ করে যা আধুনিক async/await API (iOS 15+) ব্যবহার করে Swift-এ সম্পূর্ণ BLE পেরিফেরাল জীবনচক্র পরিচালনা করে।
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 ক্লাস CheckedContinuation-এর মাধ্যমে Swift Concurrency (async/await) ব্যবহার করে — Core Bluetooth-এর ডেলিগেট-ভিত্তিক API-এর সাথে কাজ করার জন্য একটি আধুনিক প্যাটার্ন। connect(to:) didConnectPeripheral-এর মাধ্যমে সংযোগ নিশ্চিতকরণের জন্য অপেক্ষা করে, discoverServices() — didDiscoverServices-এর মাধ্যমে। এই পদ্ধতি নেস্টেড ডেলিগেট দূর করে এবং BLE কোডকে রৈখিক এবং পঠনযোগ্য করে তোলে। BLEError-এর মাধ্যমে ত্রুটি পরিচালনা সমস্ত সাধারণ BLE সংযোগ ব্যর্থতার পরিস্থিতি কভার করে।
সচরাচর জিজ্ঞাসিত প্রশ্ন
CBPeripheral পূর্বে সংযুক্ত ডিভাইসের জন্য CBCentralManager-এ retrievePeripheralsWithIdentifiers:-এর মাধ্যমে পাওয়া যেতে পারে। পূর্বে সংরক্ষিত ডিভাইসের UUID (NSUUID)-এর একটি অ্যারে পাস করুন — ফ্রেমওয়ার্ক সিস্টেম BLE বন্ডিং ডেটাবেসে ডিভাইসের জন্য CBPeripheral-এর একটি অ্যারে ফেরত দেয়। এটি শুধুমাত্র সেই ডিভাইসগুলির জন্য কাজ করে যার সাথে iPhone পূর্বে জোড়া হয়েছে। নতুন ডিভাইসের জন্য, স্ক্যানিং বাধ্যতামূলক।
সাধারণ কারণ: ডিভাইস সীমার বাইরে (RSSI থ্রেশহোল্ডের নীচে), BLE রেডিও বন্ধ (CBCentralManager.state != .poweredOn), CBPeripheralDelegate সেট করা নেই (peripheral.delegate = self), বা সংযোগের আগে discoverServices কল করা হয়েছে। centralManager.state পরীক্ষা করুন, নিশ্চিত করুন connect কলের আগে ডেলিগেট সেট করা আছে এবং 5–10 সেকেন্ড টাইমআউট সহ পুনরায় চেষ্টা ব্যবহার করুন।
কারণ হল এমন একটি ক্যারেক্টারিস্টিকে .withResponse ব্যবহার করা যা শুধুমাত্র .writeWithoutResponse সমর্থন করে, বা উল্টো। কল করার আগে characteristic.properties পরীক্ষা করুন। আরেকটি সম্ভাব্য সমস্যা হল MTU: যদি ডেটা 20 বাইটের (BLE 4.0 MTU) বেশি হয়, তাহলে negotiateMTU বা খণ্ডীকরণের মাধ্যমে MTU আলোচনা প্রয়োজন। সর্বোচ্চ প্যাকেট আকার নির্ধারণ করতে peripheral.maximumWriteValueLength(for: .withResponse) ব্যবহার করুন।
সীমার বাইরে CBPeripheral তাৎক্ষণিকভাবে সংযোগ বিচ্ছিন্ন হয় না — iOS টাইমআউটের (সাধারণত 20–30 সেকেন্ড) পরে এটিকে .disconnected অবস্থায় স্থানান্তর করে। পর্যবেক্ষণের জন্য, CBPeripheral-এ readRSSI ব্যবহার করুন — যদি অনুপলব্ধ হয়, তাহলে এটি CBError.connectionTimeout কোড সহ ত্রুটি ফেরত দেবে। সংযোগ হারানো সময়মতো সনাক্ত করতে centralManager:didDisconnectPeripheral:error: মনিটর করুন।
Core Bluetooth থ্রেড-নিরাপদ নয় — সমস্ত CBPeripheral কল একই কিউ থেকে করা উচিত (সাধারণত প্রধান কিউ বা CBCentralManager আরম্ভ করার সময় নির্দিষ্ট সিরিয়াল কিউ)। বিভিন্ন থ্রেড থেকে সমসাময়িক কল রেস কন্ডিশন এবং অ্যাপ্লিকেশন ক্র্যাশের কারণ হয়। সমস্ত BLE অপারেশনের জন্য DispatchQueue(label: “com.app.ble”) এবং UI আপডেটের জন্য DispatchQueue.main.async ব্যবহার করুন।
সারাংশ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন