文章的目的为了记录使用Objective-C 进行IOS app 开发学习的经历。本职为嵌入式软件开发,公司安排开发app,临时学习,完成app的开发。开发流程和要点有些记忆模糊,赶紧记录,防止忘记。
相关链接:
开源 Objective-C IOS 应用开发(一)macOS 的使用
开源 Objective-C IOS 应用开发(二)Xcode安装
开源 Objective-C IOS 应用开发(三)第一个iPhone的APP
开源 Objective-C IOS 应用开发(四)Xcode工程文件结构
开源 Objective-C IOS 应用开发(五)iOS操作(action)和输出口(Outlet)
开源 Objective-C IOS 应用开发(六)Objective-C 和 C语言
开源 Objective-C IOS 应用开发(七)Objective-C核心代码示例
开源 Objective-C IOS 应用开发(八)常见控件UI
开源 Objective-C IOS 应用开发(九)复杂控件-tableview
开源 Objective-C IOS 应用开发(十)数据持久化--文件
开源 Objective-C IOS 应用开发(十一)数据持久化--sqlite
开源 Objective-C IOS 应用开发(十二)通讯--ble
开源 Objective-C IOS 应用开发(十三)通讯--Http访问
开源 Objective-C IOS 应用开发(十四)传感器--陀螺仪和gps
开源 Objective-C IOS 应用开发(十五)通讯--蓝牙ble扫描
开源 Objective-C IOS 应用开发(十六)Storyboard模式下的纯代码界面
开源 Objective-C IOS 应用开发(十七)CAF音频的录制
开源 Objective-C IOS 应用开发(十八)音频的播放
开源 Objective-C IOS 应用开发(十九)视频的播放
开源 Objective-C IOS 应用开发(二十)多线程处理
开源 Objective-C IOS 应用开发(二十一)自定义控件--示波器
开源 Objective-C IOS 应用开发(二十二)自定义控件--车速仪表盘
推荐链接:
开源 Arkts 鸿蒙应用 开发(一)工程文件分析-CSDN博客
开源 Arkts 鸿蒙应用 开发(二)封装库.har制作和应用-CSDN博客
开源 Arkts 鸿蒙应用 开发(三)Arkts的介绍-CSDN博客
开源 Arkts 鸿蒙应用 开发(四)布局和常用控件-CSDN博客
开源 Arkts 鸿蒙应用 开发(五)控件组成和复杂控件-CSDN博客
开源 Arkts 鸿蒙应用 开发(六)数据持久--文件和首选项存储-CSDN博客
开源 Arkts 鸿蒙应用 开发(七)数据持久--sqlite关系数据库-CSDN博客
开源 Arkts 鸿蒙应用 开发(八)多媒体--相册和相机-CSDN博客
开源 Arkts 鸿蒙应用 开发(九)通讯--tcp客户端-CSDN博客
开源 Arkts 鸿蒙应用 开发(十)通讯--Http-CSDN博客
开源 Arkts 鸿蒙应用 开发(十一)证书和包名修改-CSDN博客
开源 Arkts 鸿蒙应用 开发(十二)传感器的使用-CSDN博客
开源 Arkts 鸿蒙应用 开发(十三)音频--MP3播放_arkts avplayer播放音频 mp3-CSDN博客
开源 Arkts 鸿蒙应用 开发(十四)线程--任务池(taskpool)-CSDN博客
开源 Arkts 鸿蒙应用 开发(十五)自定义绘图控件--仪表盘-CSDN博客
开源 Arkts 鸿蒙应用 开发(十六)自定义绘图控件--波形图-CSDN博客
开源 Arkts 鸿蒙应用 开发(十七)通讯--http多文件下载-CSDN博客
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器-CSDN博客
推荐链接:
开源 java android app 开发(一)开发环境的搭建-CSDN博客
开源 java android app 开发(二)工程文件结构-CSDN博客
开源 java android app 开发(三)GUI界面布局和常用组件-CSDN博客
开源 java android app 开发(四)GUI界面重要组件-CSDN博客
开源 java android app 开发(五)文件和数据库存储-CSDN博客
开源 java android app 开发(六)多媒体使用-CSDN博客
开源 java android app 开发(七)通讯之Tcp和Http-CSDN博客
开源 java android app 开发(八)通讯之Mqtt和Ble-CSDN博客
开源 java android app 开发(九)后台之线程和服务-CSDN博客
开源 java android app 开发(十)广播机制-CSDN博客
开源 java android app 开发(十一)调试、发布-CSDN博客
开源 java android app 开发(十二)封库.aar-CSDN博客
本章内容主要使用iphone手机对蓝牙ble设备进行扫描。
目录:
1.手机演示
2.所有源码
3.源码分析
一、手机演示
二、所有源码
使用storyboard创建以下界面
在Storyboard中需要添加以下UI组件:
-
添加两个按钮:
-
"开始扫描"按钮 - 连接到
startScanButton和startScanButtonTapped:方法 -
"清空日志"按钮 - 连接到
clearLogButton和clearLogButtonTapped:方法
-
-
添加一个TextView:
-
连接到
logTextView
-
ViewController.h文件
#import <UIKit/UIKit.h>
#import <CoreBluetooth/CoreBluetooth.h>
@interface ViewController : UIViewController <CBCentralManagerDelegate, CBPeripheralDelegate>
@property (strong, nonatomic) CBCentralManager *centralManager;
@property (strong, nonatomic) NSMutableArray *discoveredPeripherals;
@property (strong, nonatomic) NSTimer *scanTimer;
// UI ***ponents - 这些将在Storyboard中连接
@property (weak, nonatomic) IBOutlet UITextView *logTextView;
@property (weak, nonatomic) IBOutlet UIButton *startScanButton;
@property (weak, nonatomic) IBOutlet UIButton *clearLogButton;
@end
ViewController.m文件
#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
[self initializeBLE];
}
- (void)setupUI {
// 设置按钮样式
self.startScanButton.backgroundColor = [UIColor systemBlueColor];
self.startScanButton.tintColor = [UIColor whiteColor];
self.startScanButton.layer.cornerRadius = 8;
self.clearLogButton.backgroundColor = [UIColor systemGrayColor];
self.clearLogButton.tintColor = [UIColor whiteColor];
self.clearLogButton.layer.cornerRadius = 8;
// 设置文本框样式
self.logTextView.editable = NO;
self.logTextView.scrollEnabled = YES;
self.logTextView.font = [UIFont systemFontOfSize:12];
self.logTextView.backgroundColor = [UIColor blackColor];
self.logTextView.textColor = [UIColor greenColor];
self.logTextView.layer.cornerRadius = 8;
self.logTextView.layer.borderWidth = 1;
self.logTextView.layer.borderColor = [UIColor grayColor].CGColor;
}
- (void)initializeBLE {
self.discoveredPeripherals = [NSMutableArray array];
// 在后台队列中初始化CBCentralManager
dispatch_queue_t centralQueue = dispatch_queue_create("***.ble.scan.queue", DISPATCH_QUEUE_SERIAL);
self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:centralQueue options:@{CBCentralManagerOptionShowPowerAlertKey: @YES}];
}
#pragma mark - Button Actions
- (IBAction)startScanButtonTapped:(id)sender {
[self startScanning];
}
- (IBAction)clearLogButtonTapped:(id)sender {
[self clearLog];
}
- (void)startScanning {
if (self.centralManager.state != CBManagerStatePoweredOn) {
[self appendToLog:@"蓝牙未开启,请检查蓝牙状态"];
return;
}
[self appendToLog:@"开始扫描BLE设备..."];
[self.startScanButton setTitle:@"扫描中..." forState:UIControlStateNormal];
self.startScanButton.enabled = NO;
// 清空之前发现的设备
[self.discoveredPeripherals removeAllObjects];
// 开始扫描
NSDictionary *scanOptions = @{CBCentralManagerScanOptionAllowDuplicatesKey: @YES};
[self.centralManager scanForPeripheralsWithServices:nil options:scanOptions];
// 设置10秒后停止扫描
self.scanTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector:@selector(stopScanning) userInfo:nil repeats:NO];
}
- (void)stopScanning {
[self.centralManager stopScan];
[self appendToLog:@"扫描结束"];
// 在主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
[self.startScanButton setTitle:@"开始扫描" forState:UIControlStateNormal];
self.startScanButton.enabled = YES;
});
if (self.scanTimer) {
[self.scanTimer invalidate];
self.scanTimer = nil;
}
}
- (void)clearLog {
self.logTextView.text = @"";
[self appendToLog:@"日志已清空"];
}
#pragma mark - CBCentralManagerDelegate
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
NSString *stateString;
switch (central.state) {
case CBManagerStatePoweredOn:
stateString = @"蓝牙已开启";
break;
case CBManagerStatePoweredOff:
stateString = @"蓝牙已关闭";
break;
case CBManagerStateResetting:
stateString = @"蓝牙重置中";
break;
case CBManagerStateUnauthorized:
stateString = @"蓝牙未授权";
break;
case CBManagerStateUnknown:
stateString = @"蓝牙状态未知";
break;
case CBManagerStateUnsupported:
stateString = @"设备不支持蓝牙";
break;
default:
stateString = @"蓝牙状态异常";
break;
}
dispatch_async(dispatch_get_main_queue(), ^{
[self appendToLog:[NSString stringWithFormat:@"蓝牙状态: %@", stateString]];
});
}
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary<NSString *,id> *)advertisementData
RSSI:(NSNumber *)RSSI {
// 防止重复添加同一设备
if (![self.discoveredPeripherals containsObject:peripheral]) {
[self.discoveredPeripherals addObject:peripheral];
}
// 获取设备信息
NSString *deviceName = peripheral.name ?: @"Unknown";
NSString *macAddress = [self getMacAddressFromAdvertisementData:advertisementData];
NSString *rssiString = [RSSI stringValue];
// 解析广播数据
NSString *adDataString = [self parseAdvertisementData:advertisementData];
// 在主线程更新UI
dispatch_async(dispatch_get_main_queue(), ^{
NSString *logEntry = [NSString stringWithFormat:
@"设备名: %@\n"
@"MAC地址: %@\n"
@"RSSI: %@ dBm\n"
@"广播数据: %@\n"
@"UUID: %@\n"
@"----------------------------------------\n",
deviceName, macAddress, rssiString, adDataString, peripheral.identifier.UUIDString];
[self appendToLog:logEntry];
});
}
#pragma mark - Helper Methods
- (NSString *)getMacAddressFromAdvertisementData:(NSDictionary *)advertisementData {
// 在iOS中,出于隐私考虑,无法直接获取真实的MAC地址
// 这里我们使用设备的UUID作为标识符
return @"N/A (iOS隐私限制)";
}
- (NSString *)parseAdvertisementData:(NSDictionary *)advertisementData {
NSMutableString *result = [NSMutableString string];
// 解析服务UUID
NSArray *serviceUUIDs = advertisementData[CBAdvertisementDataServiceUUIDsKey];
if (serviceUUIDs && serviceUUIDs.count > 0) {
[result appendString:@"服务UUID: "];
for (CBUUID *uuid in serviceUUIDs) {
[result appendFormat:@"%@ ", uuid.UUIDString];
}
[result appendString:@"\n"];
}
// 解析本地名称
NSString *localName = advertisementData[CBAdvertisementDataLocalNameKey];
if (localName) {
[result appendFormat:@"本地名称: %@\n", localName];
}
// 解析制造商数据
NSData *manufacturerData = advertisementData[CBAdvertisementDataManufacturerDataKey];
if (manufacturerData) {
[result appendFormat:@"制造商数据: %@\n", [self hexStringFromData:manufacturerData]];
}
// 解析服务数据
NSDictionary *serviceData = advertisementData[CBAdvertisementDataServiceDataKey];
if (serviceData) {
[result appendString:@"服务数据: "];
for (CBUUID *uuid in serviceData) {
NSData *data = serviceData[uuid];
[result appendFormat:@"%@: %@ ", uuid.UUIDString, [self hexStringFromData:data]];
}
[result appendString:@"\n"];
}
// 解析TX功率级别
NSNumber *txPower = advertisementData[CBAdvertisementDataTxPowerLevelKey];
if (txPower) {
[result appendFormat:@"TX功率: %@ dBm\n", txPower];
}
if (result.length == 0) {
[result appendString:@"无广播数据"];
}
return [result copy];
}
- (NSString *)hexStringFromData:(NSData *)data {
if (!data) return @"";
const unsigned char *dataBuffer = (const unsigned char *)[data bytes];
if (!dataBuffer) return @"";
NSUInteger dataLength = [data length];
NSMutableString *hexString = [NSMutableString stringWithCapacity:(dataLength * 2)];
for (int i = 0; i < dataLength; ++i) {
[hexString appendFormat:@"%02lX", (unsigned long)dataBuffer[i]];
}
return [NSString stringWithString:hexString];
}
- (void)appendToLog:(NSString *)message {
NSString *timestamp = [self getCurrentTimestamp];
NSString *logMessage = [NSString stringWithFormat:@"[%@] %@\n", timestamp, message];
// 在主线程更新文本框
dispatch_async(dispatch_get_main_queue(), ^{
self.logTextView.text = [self.logTextView.text stringByAppendingString:logMessage];
// 自动滚动到底部
if (self.logTextView.text.length > 0) {
NSRange bottom = NSMakeRange(self.logTextView.text.length - 1, 1);
[self.logTextView scrollRangeToVisible:bottom];
}
});
}
- (NSString *)getCurrentTimestamp {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"HH:mm:ss.SSS"];
return [formatter stringFromDate:[NSDate date]];
}
- (void)dealloc {
if (self.scanTimer) {
[self.scanTimer invalidate];
self.scanTimer = nil;
}
}
@end
在 Info.plist 中添加蓝牙使用描述:
<key>NSBluetoothAlwaysUsageDescription</key>
<string>应用需要蓝牙权限来扫描附近的BLE设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>应用需要蓝牙权限来扫描附近的BLE设备</string>
三、源码分析
1. 数据模型类
BLEDevice 类
@interface BLEDevice : NSObject
@property (strong, nonatomic) CBPeripheral *peripheral;
@property (strong, nonatomic) NSString *deviceIdentifier;
@property (strong, nonatomic) NSNumber *rssi;
@property (strong, nonatomic) NSDictionary *advertisementData;
@property (strong, nonatomic) NSDate *discoveryTime;
@end
功能:自定义数据模型,用于存储BLE设备的完整信息,实现设备数据的封装和管理。
2. 生命周期函数
viewDidLoad
- (void)viewDidLoad {
[super viewDidLoad];
[self setupUI];
[self initializeBLE];
}
功能:视图控制器生命周期函数,在视图加载完成后调用,初始化UI和BLE模块。
dealloc
- (void)dealloc {
if (self.scanTimer) {
[self.scanTimer invalidate];
self.scanTimer = nil;
}
if (self.deviceUpdateTimer) {
[self.deviceUpdateTimer invalidate];
self.deviceUpdateTimer = nil;
}
}
功能:对象销毁时调用,清理定时器资源,防止内存泄漏。
3. 初始化函数
setupUI
- (void)setupUI {
// 设置按钮样式和文本框样式
}
功能:配置用户界面元素的视觉样式,包括按钮颜色、圆角、文本框背景等。
initializeBLE
- (void)initializeBLE {
self.discoveredDevices = [NSMutableDictionary dictionary];
}
功能:初始化BLE相关数据结构,创建用于存储已发现设备的字典。
4. 按钮动作函数
startScanButtonTapped:
- (IBAction)startScanButtonTapped:(id)sender {
[self startScanning];
}
功能:开始扫描按钮的点击事件处理函数。
clearLogButtonTapped:
- (IBAction)clearLogButtonTapped:(id)sender {
[self clearLog];
}
功能:清空日志按钮的点击事件处理函数。
5. BLE扫描控制函数
startScanning
- (void)startScanning {
// 1. 检查蓝牙状态
// 2. 清空之前发现的设备
// 3. 开始BLE扫描
// 4. 启动设备更新定时器
// 5. 设置10秒后停止扫描的定时器
}
功能:启动BLE设备扫描流程,包含状态检查、数据清理、扫描启动和定时器设置。
stopScanning
- (void)stopScanning {
// 1. 停止BLE扫描
// 2. 停止设备更新定时器
// 3. 更新UI状态
// 4. 清理扫描定时器
}
功能:停止BLE扫描,清理相关资源,恢复UI状态。
6. CBCentralManagerDelegate 协议函数
centralManagerDidUpdateState:
- (void)centralManagerDidUpdateState:(CBCentralManager *)central {
// 处理蓝牙状态变化
switch (central.state) {
case CBManagerStatePoweredOn: ...
// 其他状态处理
}
}
功能:蓝牙状态变化的回调函数,处理蓝牙开启、关闭、未授权等各种状态。
centralManager:didDiscoverPeripheral:advertisementData:RSSI:
- (void)centralManager:(CBCentralManager *)central
didDiscoverPeripheral:(CBPeripheral *)peripheral
advertisementData:(NSDictionary *)advertisementData
RSSI:(NSNumber *)RSSI {
// 设备发现逻辑:
// 1. 使用UUID作为设备唯一标识
// 2. 检查设备是否已存在
// 3. 存在则更新数据,不存在则创建新记录
}
功能:核心的设备发现回调函数,处理新设备的发现和已存在设备的更新。
7. 设备显示管理函数
updateDeviceDisplay
- (void)updateDeviceDisplay {
dispatch_async(dispatch_get_main_queue(), ^{
[self refreshDeviceListDisplay];
});
}
功能:封装设备显示更新操作,确保在主线程执行UI更新。
refreshDeviceListDisplay
- (void)refreshDeviceListDisplay {
// 1. 检查是否有设备
// 2. 按设备名称排序
// 3. 构建显示字符串
// 4. 更新文本框内容
// 5. 自动滚动到底部
}
功能:刷新设备列表显示,包括排序、格式化和UI更新。
8. 工具函数
clearLog
- (void)clearLog {
self.logTextView.text = @"";
[self appendToLog:@"日志已清空"];
}
功能:清空日志文本框内容。
appendToLog:
- (void)appendToLog:(NSString *)message {
// 添加时间戳,在主线程更新文本框
}
功能:向日志文本框添加带时间戳的消息。
getMacAddressFromAdvertisementData:
- (NSString *)getMacAddressFromAdvertisementData:(NSDictionary *)advertisementData {
return @"N/A (iOS隐私限制)";
}
功能:由于iOS隐私限制,无法获取真实MAC地址,返回提示信息。
parseAdvertisementData:
- (NSString *)parseAdvertisementData:(NSDictionary *)advertisementData {
// 解析广播数据中的:
// - 服务UUID
// - 本地名称
// - 制造商数据
// - 服务数据
// - TX功率级别
}
功能:解析BLE设备的广播数据,提取各种有用信息。
hexStringFromData:
- (NSString *)hexStringFromData:(NSData *)data {
// 将NSData转换为十六进制字符串表示
}
功能:将二进制数据转换为可读的十六进制字符串。
getCurrentTimestamp
- (NSString *)getCurrentTimestamp {
// 获取当前时间的格式化字符串
}
功能:生成格式化的时间戳字符串。