前言
前端時(shí)間,同學(xué)在做項(xiàng)目過程中遇到關(guān)于藍(lán)牙方面的問題,今天我就給大家進(jìn)行詳細(xì)的進(jìn)行講解下藍(lán)牙在iOS開發(fā)中的具體實(shí)現(xiàn).在介紹藍(lán)牙前,大家要搞清楚什么是藍(lán)牙?
什么是藍(lán)牙?
隨著藍(lán)牙低功耗技術(shù)BLE(Bluetooth Low Energy)的發(fā)展,藍(lán)牙技術(shù)正在一步步成熟,如今的大部分移動設(shè)備都配備有藍(lán)牙4.0,相比之前的藍(lán)牙技術(shù)耗電量大大降低。從iOS的發(fā)展史也不難看出蘋果目前對藍(lán)牙技術(shù)也是越來越關(guān)注,例如蘋果于2013年9月發(fā)布的iOS7就配備了iBeacon技術(shù),這項(xiàng)技術(shù)完全基于藍(lán)牙傳輸。但是眾所周知蘋果的設(shè)備對于權(quán)限要求也是比較高的,因此在iOS中并不能像Android一樣隨意使用藍(lán)牙進(jìn)行文件傳輸(除非你已經(jīng)越獄)。知道什么是藍(lán)牙之后,那么在iOS中進(jìn)行藍(lán)牙傳輸應(yīng)用開發(fā)常用的框架有哪幾種呢?
藍(lán)牙在開發(fā)中的框架有哪些?
GameKit.framework:iOS7之前的藍(lán)牙通訊框架,從iOS7開始過期,但是目前多數(shù)應(yīng)用還是基于此框架。MultipeerConnectivity.framework:iOS7開始引入的新的藍(lán)牙通訊開發(fā)框架,用于取代GameKit。CoreBluetooth.framework:功能強(qiáng)大的藍(lán)牙開發(fā)框架,要求設(shè)備必須支持藍(lán)牙4.0。
藍(lán)牙在開發(fā)中的框架優(yōu)缺點(diǎn)?
現(xiàn)在就給大家來總結(jié)下這三種框架的優(yōu)缺點(diǎn).?
前兩個(gè)框架使用起來比較簡單,但是缺點(diǎn)也比較明顯:僅僅支持iOS設(shè)備,傳輸內(nèi)容僅限于沙盒或者照片庫中用戶選擇的文件,并且第一個(gè)框架只能在同一個(gè)應(yīng)用之間進(jìn)行傳輸(一個(gè)iOS設(shè)備安裝應(yīng)用A,另一個(gè)iOS設(shè)備上安裝應(yīng)用B是無法傳輸?shù)?#xff09;。當(dāng)然CoreBluetooth就擺脫了這些束縛,它不再局限于iOS設(shè)備之間進(jìn)行傳輸,你可以通過iOS設(shè)備向Android、Windows Phone以及其他安裝有藍(lán)牙4.0芯片的智能設(shè)備傳輸,因此也是目前智能家居、無線支付等熱門智能設(shè)備所推崇的技術(shù)。
藍(lán)牙框架之GameKit框架
其實(shí)從名稱來看這個(gè)框架并不是專門為了支持藍(lán)牙傳輸而設(shè)計(jì)的,它是為游戲設(shè)計(jì)的。而很多游戲中會用到基于藍(lán)牙的點(diǎn)對點(diǎn)信息傳輸,因此這個(gè)框架中集成了藍(lán)牙傳輸模塊。前面也說了這個(gè)框架本身有很多限制,但是在iOS7之前的很多藍(lán)牙傳輸都是基于此框架的,所以有必要對它進(jìn)行了解。GameKit中的藍(lán)牙使用設(shè)計(jì)很簡單,并沒有給開發(fā)者留有太多的復(fù)雜接口,而多數(shù)連接細(xì)節(jié)開發(fā)者是不需要關(guān)注的。GameKit中提供了兩個(gè)關(guān)鍵類來操作藍(lán)牙連接:
GKPeerPickerController:藍(lán)牙查找、連接用的視圖控制器,通常情況下應(yīng)用程序A打開后會調(diào)用此控制器的show方法來展示一個(gè)藍(lán)牙查找的視圖,一旦發(fā)現(xiàn)了另一個(gè)同樣在查找藍(lán)牙連接的客戶客戶端B就會出現(xiàn)在視圖列表中,此時(shí)如果用戶點(diǎn)擊連接B,B客戶端就會詢問用戶是否允許A連接B,如果允許后A和B之間建立一個(gè)藍(lán)牙連接。
GKSession:連接會話,主要用于發(fā)送和接受傳輸數(shù)據(jù)。一旦A和B建立連接GKPeerPickerController的代理方法會將A、B兩者建立的會話(GKSession)對象傳遞給開發(fā)人員,開發(fā)人員拿到此對象可以發(fā)送和接收數(shù)據(jù)。
其實(shí)理解了上面兩個(gè)類之后,使用起來就比較簡單了,下面就以一個(gè)圖片發(fā)送程序來演示GameKit中藍(lán)牙的使用。此程序一個(gè)客戶端運(yùn)行在模擬器上作為客戶端A,另一個(gè)運(yùn)行在iPhone真機(jī)上作為客戶端B(注意A、B必須運(yùn)行同一個(gè)程序,GameKit藍(lán)牙開發(fā)是不支持兩個(gè)不同的應(yīng)用傳輸數(shù)據(jù)的)。兩個(gè)程序運(yùn)行之后均調(diào)用GKPeerPickerController來發(fā)現(xiàn)周圍藍(lán)牙設(shè)備,一旦A發(fā)現(xiàn)了B之后就開始連接B,然后iOS會詢問用戶是否接受連接,一旦接受之后就會調(diào)用GKPeerPickerController的-(void)peerPickerController:(GKPeerPickerController?)picker didConnectPeer:(NSString?)peerID toSession:(GKSession *)session代理方法,在此方法中可以獲得連接的設(shè)備id(peerID)和連接會話(session);此時(shí)可以設(shè)置會話的數(shù)據(jù)接收句柄(相當(dāng)于一個(gè)代理)并保存會話以便發(fā)送數(shù)據(jù)時(shí)使用;一旦一端(假設(shè)是A)調(diào)用會話的sendDataToAllPeers: withDataMode: error:方法發(fā)送數(shù)據(jù),此時(shí)另一端(假設(shè)是B)就會調(diào)用句柄的?
- (void) receiveData:(NSData?)data fromPeer:(NSString?)peer inSession: (GKSession?)session context:(void?)context方法,在此方法可以獲得發(fā)送數(shù)據(jù)并處理。下面是程序代碼:
#import "ViewController.h"
#import <GameKit/GameKit.h>@interface ViewController ()<GKPeerPickerControllerDelegate,UIImagePickerControllerDelegate,UINavigationBarDelegate>@property (
weak,
nonatomic)
IBOutlet UIImageView *imageView;
@property (
strong,
nonatomic) GKSession *session;
@end@implementation ViewController#pragma mark - 控制器視圖方法
- (
void)viewDidLoad {[
super viewDidLoad];GKPeerPickerController *pearPickerController=[[GKPeerPickerController alloc]init];pearPickerController
.delegate=
self;[pearPickerController show];
}
#pragma mark - UI事件
- (
IBAction)selectClick:(
UIBarButtonItem *)sender {UIImagePickerController *imagePickerController=[[UIImagePickerController alloc]init];imagePickerController
.delegate=
self;[
self presentViewController:imagePickerController animated:
YES completion:
nil];
}- (
IBAction)sendClick:(
UIBarButtonItem *)sender {NSData *data=UIImagePNGRepresentation(
self.imageView.image);
NSError *error=
nil;[
self.session sendDataToAllPeers:data withDataMode:GKSendDataReliable error:&error];
if (error) {
NSLog(@
"發(fā)送圖片過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription);}
}
#pragma mark - GKPeerPickerController代理方法
-(
void)peerPickerController:(GKPeerPickerController *)picker didConnectPeer:(
NSString *)peerID toSession:(GKSession *)session{
self.session=session;
NSLog(@
"已連接客戶端設(shè)備:%@.",peerID);[
self.session setDataReceiveHandler:
self withContext:
nil];[picker dismiss];
}
#pragma mark - 藍(lán)牙數(shù)據(jù)接收方法
- (
void) receiveData:(NSData *)data fromPeer:(
NSString *)peer inSession: (GKSession *)session context:(
void *)context{
UIImage *image=[
UIImage imageWithData:data];
self.imageView.image=image;UIImageWriteToSavedPhotosAlbum(image,
nil,
nil,
nil);
NSLog(@
"數(shù)據(jù)發(fā)送成功!");
}
#pragma mark - UIImagePickerController代理方法
-(
void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(
NSDictionary *)info{
self.imageView.image=[info objectForKey:UIImagePickerControllerOriginalImage];[
self dismissViewControllerAnimated:
YES completion:
nil];
}-(
void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{[
self dismissViewControllerAnimated:
YES completion:
nil];
}
@end- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
運(yùn)行效果(左側(cè)是真機(jī),右側(cè)是模擬器,程序演示了兩個(gè)客戶端互發(fā)圖片的場景:首先是模擬器發(fā)送圖片給真機(jī),然后真機(jī)發(fā)送圖片給模擬器)?
藍(lán)牙框架之MultipeerConnectivity框架
前面已經(jīng)說了GameKit相關(guān)的藍(lán)牙操作類從iOS7已經(jīng)全部過期,蘋果官方推薦使用MultipeerConnectivity代替。但是應(yīng)該了解,MultipeerConnectivity.framework并不僅僅支持藍(lán)牙連接,準(zhǔn)確的說它是一種支持Wi-Fi網(wǎng)絡(luò)、P2P Wi-Fi已經(jīng)藍(lán)牙個(gè)人局域網(wǎng)的通信框架,它屏蔽了具體的連接技術(shù),讓開發(fā)人員有統(tǒng)一的接口編程方法。通過MultipeerConnectivity連接的節(jié)點(diǎn)之間可以安全的傳遞信息、流或者其他文件資源而不必通過網(wǎng)絡(luò)服務(wù)。此外使用MultipeerConnectivity進(jìn)行近場通信也不再局限于同一個(gè)應(yīng)用之間傳輸,而是可以在不同的應(yīng)用之間進(jìn)行數(shù)據(jù)傳輸(當(dāng)然如果有必要的話你仍然可以選擇在一個(gè)應(yīng)用程序之間傳輸)。
要了解MultipeerConnectivity的使用必須要清楚一個(gè)概念:廣播(Advertisting)和發(fā)現(xiàn)(Disconvering),這很類似于一種Client-Server模式。假設(shè)有兩臺設(shè)備A、B,B作為廣播去發(fā)送自身服務(wù),A作為發(fā)現(xiàn)的客戶端。一旦A發(fā)現(xiàn)了B就試圖建立連接,經(jīng)過B同意二者建立連接就可以相互發(fā)送數(shù)據(jù)。在使用GameKit框架時(shí),A和B既作為廣播又作為發(fā)現(xiàn),當(dāng)然這種情況在MultipeerConnectivity中也很常見。
A.廣播
無論是作為服務(wù)器端去廣播還是作為客戶端去發(fā)現(xiàn)廣播服務(wù),那么兩個(gè)(或更多)不同的設(shè)備之間必須要有區(qū)分,通常情況下使用MCPeerID對象來區(qū)分一臺設(shè)備,在這個(gè)設(shè)備中可以指定顯示給對方查看的名稱(display name)。另外不管是哪一方,還必須建立一個(gè)會話MCSession用于發(fā)送和接受數(shù)據(jù)。通常情況下會在會話的-(void)session:(MCSession?)session peer:(MCPeerID?)peerID didChangeState:(MCSessionState)state代理方法中跟蹤會話狀態(tài)(已連接、正在連接、未連接);在會話的-(void)session:(MCSession?)session didReceiveData:(NSData?)data fromPeer:(MCPeerID *)peerID代理方法中接收數(shù)據(jù);同時(shí)還會調(diào)用會話的-(void)sendData: toPeers:withMode: error:方法去發(fā)送數(shù)據(jù)。
廣播作為一個(gè)服務(wù)器去發(fā)布自身服務(wù),供周邊設(shè)備發(fā)現(xiàn)連接。在MultipeerConnectivity中使用MCAdvertiserAssistant來表示一個(gè)廣播,通常創(chuàng)建廣播時(shí)指定一個(gè)會話MCSession對象將廣播服務(wù)和會話關(guān)聯(lián)起來。一旦調(diào)用廣播的start方法周邊的設(shè)備就可以發(fā)現(xiàn)該廣播并可以連接到此服務(wù)。在MCSession的代理方法中可以隨時(shí)更新連接狀態(tài),一旦建立了連接之后就可以通過MCSession的connectedPeers獲得已經(jīng)連接的設(shè)備。
B.發(fā)現(xiàn)
前面已經(jīng)說過作為發(fā)現(xiàn)的客戶端同樣需要一個(gè)MCPeerID來標(biāo)志一個(gè)客戶端,同時(shí)會擁有一個(gè)MCSession來監(jiān)聽連接狀態(tài)并發(fā)送、接受數(shù)據(jù)。除此之外,要發(fā)現(xiàn)廣播服務(wù),客戶端就必須要隨時(shí)查找服務(wù)來連接,在MultipeerConnectivity中提供了一個(gè)控制器MCBrowserViewController來展示可連接和已連接的設(shè)備(這類似于GameKit中的GKPeerPickerController),當(dāng)然如果想要自己定制一個(gè)界面來展示設(shè)備連接的情況你可以選擇自己開發(fā)一套UI界面。一旦通過MCBroserViewController選擇一個(gè)節(jié)點(diǎn)去連接,那么作為廣播的節(jié)點(diǎn)就會收到通知,詢問用戶是否允許連接。由于初始化MCBrowserViewController的過程已經(jīng)指定了會話MCSession,所以連接過程中會隨時(shí)更新會話狀態(tài),一旦建立了連接,就可以通過會話的connected屬性獲得已連接設(shè)備并且可以使用會話發(fā)送、接受數(shù)據(jù)。
下面用兩個(gè)不同的應(yīng)用程序來演示使用MultipeerConnectivity的使用過程,其中一個(gè)應(yīng)用運(yùn)行在模擬器中作為廣播節(jié)點(diǎn),另一個(gè)運(yùn)行在iPhone真機(jī)上作為發(fā)現(xiàn)節(jié)點(diǎn),并且實(shí)現(xiàn)兩個(gè)節(jié)點(diǎn)的圖片互傳。
首先看一下作為廣播節(jié)點(diǎn)的程序:
界面:?
?
點(diǎn)擊“開始廣播”來發(fā)布服務(wù),一旦有節(jié)點(diǎn)連接此服務(wù)就可以使用“選擇照片”來從照片庫中選取一張圖片并發(fā)送到所有已連接節(jié)點(diǎn)。?
程序:
#import "ViewController.h"
#import <MultipeerConnectivity/MultipeerConnectivity.h>@interface ViewController ()<MCSessionDelegate,MCAdvertiserAssistantDelegate, UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (
strong,
nonatomic) MCSession *session;
@property (
strong,
nonatomic) MCAdvertiserAssistant *advertiserAssistant;
@property (
strong,
nonatomic) UIImagePickerController *imagePickerController;
@property (
weak,
nonatomic)
IBOutlet UIImageView *photo;
@end@implementation ViewController#pragma mark - 控制器視圖事件
- (
void)viewDidLoad {[
super viewDidLoad];MCPeerID *peerID=[[MCPeerID alloc]initWithDisplayName:@
"KenshinCui_Advertiser"];_session=[[MCSession alloc]initWithPeer:peerID];_session
.delegate=
self;_advertiserAssistant=[[MCAdvertiserAssistant alloc]initWithServiceType:@
"cmj-stream" discoveryInfo:
nil session:_session];_advertiserAssistant
.delegate=
self;}
#pragma mark - UI事件
- (
IBAction)advertiserClick:(
UIBarButtonItem *)sender {[
self.advertiserAssistant start];
}
- (
IBAction)selectClick:(
UIBarButtonItem *)sender {_imagePickerController=[[UIImagePickerController alloc]init];_imagePickerController
.delegate=
self;[
self presentViewController:_imagePickerController animated:
YES completion:
nil];
}
#pragma mark - MCSession代理方法
-(
void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
NSLog(@
"didChangeState");
switch (state) {
case MCSessionStateConnected:
NSLog(@
"連接成功.");
break;
case MCSessionStateConnecting:
NSLog(@
"正在連接...");
break;
default:
NSLog(@
"連接失敗.");
break;}
}
-(
void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
NSLog(@
"開始接收數(shù)據(jù)...");
UIImage *image=[
UIImage imageWithData:data];[
self.photo setImage:image];UIImageWriteToSavedPhotosAlbum(image,
nil,
nil,
nil);}
#pragma mark - MCAdvertiserAssistant代理方法#pragma mark - UIImagePickerController代理方法
-(
void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(
NSDictionary *)info{
UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage];[
self.photo setImage:image];
NSError *error=
nil;[
self.session sendData:UIImagePNGRepresentation(image) toPeers:[
self.session connectedPeers] withMode:MCSessionSendDataUnreliable error:&error];
NSLog(@
"開始發(fā)送數(shù)據(jù)...");
if (error) {
NSLog(@
"發(fā)送數(shù)據(jù)過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription);}[
self.imagePickerController dismissViewControllerAnimated:
YES completion:
nil];
}
-(
void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{[
self.imagePickerController dismissViewControllerAnimated:
YES completion:
nil];
}
@end- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
再看一下作為發(fā)現(xiàn)節(jié)點(diǎn)的程序:
界面:
?
點(diǎn)擊“查找設(shè)備”瀏覽可用服務(wù),點(diǎn)擊服務(wù)建立連接;一旦建立了連接之后就可以點(diǎn)擊“選擇照片”會從照片庫中選擇一張圖片并發(fā)送給已連接的節(jié)點(diǎn)。
程序:
#import "ViewController.h"
#import <MultipeerConnectivity/MultipeerConnectivity.h>@interface ViewController ()<MCSessionDelegate,MCBrowserViewControllerDelegate,UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (
strong,
nonatomic) MCSession *session;
@property (
strong,
nonatomic) MCBrowserViewController *browserController;
@property (
strong,
nonatomic) UIImagePickerController *imagePickerController;
@property (
weak,
nonatomic)
IBOutlet UIImageView *photo;
@end@implementation ViewController#pragma mark - 控制器視圖事件
- (
void)viewDidLoad {[
super viewDidLoad];MCPeerID *peerID=[[MCPeerID alloc]initWithDisplayName:@
"KenshinCui"];_session=[[MCSession alloc]initWithPeer:peerID];_session
.delegate=
self;}
#pragma mark- UI事件
- (
IBAction)browserClick:(
UIBarButtonItem *)sender {_browserController=[[MCBrowserViewController alloc]initWithServiceType:@
"cmj-stream" session:
self.session];_browserController
.delegate=
self;[
self presentViewController:_browserController animated:
YES completion:
nil];
}
- (
IBAction)selectClick:(
UIBarButtonItem *)sender {_imagePickerController=[[UIImagePickerController alloc]init];_imagePickerController
.delegate=
self;[
self presentViewController:_imagePickerController animated:
YES completion:
nil];
}
#pragma mark - MCBrowserViewController代理方法
-(
void)browserViewControllerDidFinish:(MCBrowserViewController *)browserViewController{
NSLog(@
"已選擇");[
self.browserController dismissViewControllerAnimated:
YES completion:
nil];
}
-(
void)browserViewControllerWasCancelled:(MCBrowserViewController *)browserViewController{
NSLog(@
"取消瀏覽.");[
self.browserController dismissViewControllerAnimated:
YES completion:
nil];
}
#pragma mark - MCSession代理方法
-(
void)session:(MCSession *)session peer:(MCPeerID *)peerID didChangeState:(MCSessionState)state{
NSLog(@
"didChangeState");
switch (state) {
case MCSessionStateConnected:
NSLog(@
"連接成功.");[
self.browserController dismissViewControllerAnimated:
YES completion:
nil];
break;
case MCSessionStateConnecting:
NSLog(@
"正在連接...");
break;
default:
NSLog(@
"連接失敗.");
break;}
}
-(
void)session:(MCSession *)session didReceiveData:(NSData *)data fromPeer:(MCPeerID *)peerID{
NSLog(@
"開始接收數(shù)據(jù)...");
UIImage *image=[
UIImage imageWithData:data];[
self.photo setImage:image];UIImageWriteToSavedPhotosAlbum(image,
nil,
nil,
nil);}
#pragma mark - UIImagePickerController代理方法
-(
void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(
NSDictionary *)info{
UIImage *image=[info objectForKey:UIImagePickerControllerOriginalImage];[
self.photo setImage:image];
NSError *error=
nil;[
self.session sendData:UIImagePNGRepresentation(image) toPeers:[
self.session connectedPeers] withMode:MCSessionSendDataUnreliable error:&error];
NSLog(@
"開始發(fā)送數(shù)據(jù)...");
if (error) {
NSLog(@
"發(fā)送數(shù)據(jù)過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription);}[
self.imagePickerController dismissViewControllerAnimated:
YES completion:
nil];
}
-(
void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{[
self.imagePickerController dismissViewControllerAnimated:
YES completion:
nil];
}
@end- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
在兩個(gè)程序中無論是MCBrowserViewController還是MCAdvertiserAssistant在初始化的時(shí)候都指定了一個(gè)服務(wù)類型“cmj-photo”,這是唯一標(biāo)識一個(gè)服務(wù)類型的標(biāo)記,可以按照官方的要求命名,應(yīng)該盡可能表達(dá)服務(wù)的作用。需要特別指出的是,如果廣播命名為“cmj-photo”那么發(fā)現(xiàn)節(jié)點(diǎn)只有在MCBrowserViewController中指定為“cmj-photo”才能發(fā)現(xiàn)此服務(wù)。
運(yùn)行效果:?
藍(lán)牙框架之CoreBluetooth框架
無論是GameKit還是MultipeerConnectivity,都只能在iOS設(shè)備之間進(jìn)行數(shù)據(jù)傳輸,這就大大降低了藍(lán)牙的使用范圍,于是從iOS6開始蘋果推出了CoreBluetooth.framework,這個(gè)框架最大的特點(diǎn)就是完全基于BLE4.0標(biāo)準(zhǔn)并且支持非iOS設(shè)備。當(dāng)前BLE應(yīng)用相當(dāng)廣泛,不再僅僅是兩個(gè)設(shè)備之間的數(shù)據(jù)傳輸,它還有很多其他應(yīng)用市場,例如室內(nèi)定位、無線支付、智能家居等等,這也使得CoreBluetooth成為當(dāng)前最熱門的藍(lán)牙技術(shù)。
CoreBluetooth設(shè)計(jì)同樣也是類似于客戶端-服務(wù)器端的設(shè)計(jì),作為服務(wù)器端的設(shè)備稱為外圍設(shè)備(Peripheral),作為客戶端的設(shè)備叫做中央設(shè)備(Central),CoreBlueTooth整個(gè)框架就是基于這兩個(gè)概念來設(shè)計(jì)的。
?
外圍設(shè)備和中央設(shè)備在CoreBluetooth中使用CBPeripheralManager和CBCentralManager表示。
CBPeripheralManager:外圍設(shè)備通常用于發(fā)布服務(wù)、生成數(shù)據(jù)、保存數(shù)據(jù)。外圍設(shè)備發(fā)布并廣播服務(wù),告訴周圍的中央設(shè)備它的可用服務(wù)和特征。
CBCentralManager:中央設(shè)備使用外圍設(shè)備的數(shù)據(jù)。中央設(shè)備掃描到外圍設(shè)備后會就會試圖建立連接,一旦連接成功就可以使用這些服務(wù)和特征。
外圍設(shè)備和中央設(shè)備之間交互的橋梁是服務(wù)(CBService)和特征(CBCharacteristic),二者都有一個(gè)唯一的標(biāo)識UUID(CBUUID類型)來唯一確定一個(gè)服務(wù)或者特征,每個(gè)服務(wù)可以擁有多個(gè)特征,下面是他們之間的關(guān)系:?
?
一臺iOS設(shè)備(注意iPhone4以下設(shè)備不支持BLE,另外iOS7.0、8.0模擬器也無法模擬BLE)既可以作為外圍設(shè)備又可以作為中央設(shè)備,但是不能同時(shí)即是外圍設(shè)備又是中央設(shè)備,同時(shí)注意建立連接的過程不需要用戶手動選擇允許,這一點(diǎn)和前面兩個(gè)框架是不同的,這主要是因?yàn)锽LE應(yīng)用場景不再局限于兩臺設(shè)備之間資源共享了。?
A.外圍設(shè)備
創(chuàng)建一個(gè)外圍設(shè)備通常分為以下幾個(gè)步驟:?
1. 創(chuàng)建外圍設(shè)備CBPeripheralManager對象并指定代理。?
2. 創(chuàng)建特征CBCharacteristic、服務(wù)CBSerivce并添加到外圍設(shè)備?
3. 外圍設(shè)備開始廣播服務(wù)(startAdvertisting:)。?
4. 和中央設(shè)備CBCentral進(jìn)行交互。?
下面是簡單的程序示例,程序有兩個(gè)按鈕“啟動”和“更新”,點(diǎn)擊啟動按鈕則創(chuàng)建外圍設(shè)備、添加服務(wù)和特征并開始廣播,一旦發(fā)現(xiàn)有中央設(shè)備連接并訂閱了此服務(wù)的特征則通過更新按鈕更新特征數(shù)據(jù),此時(shí)已訂閱的中央設(shè)備就會收到更新數(shù)據(jù)。?
#import "ViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
#define kPeripheralName @"Kenshin Cui's Device" //外圍設(shè)備名稱
#define kServiceUUID @"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE" //服務(wù)的UUID
#define kCharacteristicUUID @"6A3E4B28-522D-4B3B-82A9-D5E2004534FC" //特征的UUID@interface ViewController ()<CBPeripheralManagerDelegate>@property (
strong,
nonatomic) CBPeripheralManager *peripheralManager;
@property (
strong,
nonatomic)
NSMutableArray *centralM;
@property (
strong,
nonatomic) CBMutableCharacteristic *characteristicM;
@property (
weak,
nonatomic)
IBOutlet UITextView *log;
@end@implementation ViewController
#pragma mark - 視圖控制器方法
- (
void)viewDidLoad {[
super viewDidLoad];
}
#pragma mark - UI事件
- (
IBAction)startClick:(
UIBarButtonItem *)sender {_peripheralManager=[[CBPeripheralManager alloc]initWithDelegate:
self queue:
nil];
}
- (
IBAction)transferClick:(
UIBarButtonItem *)sender {[
self updateCharacteristicValue];
}
#pragma mark - CBPeripheralManager代理方法
-(
void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral{
switch (peripheral
.state) {
case CBPeripheralManagerStatePoweredOn:
NSLog(@
"BLE已打開.");[
self writeToLog:@
"BLE已打開."];[
self setupService];
break;
default:
NSLog(@
"此設(shè)備不支持BLE或未打開藍(lán)牙功能,無法作為外圍設(shè)備.");[
self writeToLog:@
"此設(shè)備不支持BLE或未打開藍(lán)牙功能,無法作為外圍設(shè)備."];
break;}
}
-(
void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(
NSError *)error{
if (error) {
NSLog(@
"向外圍設(shè)備添加服務(wù)失敗,錯誤詳情:%@",error
.localizedDescription);[
self writeToLog:[
NSString stringWithFormat:@
"向外圍設(shè)備添加服務(wù)失敗,錯誤詳情:%@",error
.localizedDescription]];
return;}
NSDictionary *dic=@{CBAdvertisementDataLocalNameKey:kPeripheralName};[
self.peripheralManager startAdvertising:dic];
NSLog(@
"向外圍設(shè)備添加了服務(wù)并開始廣播...");[
self writeToLog:@
"向外圍設(shè)備添加了服務(wù)并開始廣播..."];
}
-(
void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(
NSError *)error{
if (error) {
NSLog(@
"啟動廣播過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription);[
self writeToLog:[
NSString stringWithFormat:@
"啟動廣播過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription]];
return;}
NSLog(@
"啟動廣播...");[
self writeToLog:@
"啟動廣播..."];
}
-(
void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@
"中心設(shè)備:%@ 已訂閱特征:%@.",central,characteristic);[
self writeToLog:[
NSString stringWithFormat:@
"中心設(shè)備:%@ 已訂閱特征:%@.",central
.identifier.UUIDString,characteristic
.UUID]];
if (![
self.centralM containsObject:central]) {[
self.centralM addObject:central];}
}
-(
void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic{
NSLog(@
"didUnsubscribeFromCharacteristic");
}
-(
void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(CBATTRequest *)request{
NSLog(@
"didReceiveWriteRequests");
}
-(
void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(
NSDictionary *)dict{
NSLog(@
"willRestoreState");
}
#pragma mark -屬性
-(
NSMutableArray *)centralM{
if (!_centralM) {_centralM=[
NSMutableArray array];}
return _centralM;
}
#pragma mark - 私有方法
-(
void)setupService{CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
CBMutableCharacteristic *characteristicM=[[CBMutableCharacteristic alloc]initWithType:characteristicUUID properties:CBCharacteristicPropertyNotify value:
nil permissions:CBAttributePermissionsReadable];
self.characteristicM=characteristicM;
CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];CBMutableService *serviceM=[[CBMutableService alloc]initWithType:serviceUUID primary:
YES];[serviceM setCharacteristics:@[characteristicM]];[
self.peripheralManager addService:serviceM];
}
-(
void)updateCharacteristicValue{
NSString *valueStr=[
NSString stringWithFormat:@
"%@ --%@",kPeripheralName,[
NSDate date]];NSData *value=[valueStr dataUsingEncoding:NSUTF8StringEncoding];[
self.peripheralManager updateValue:value forCharacteristic:
self.characteristicM onSubscribedCentrals:
nil];[
self writeToLog:[
NSString stringWithFormat:@
"更新特征值:%@",valueStr]];
}
-(
void)writeToLog:(
NSString *)info{
self.log.text=[
NSString stringWithFormat:@
"%@\r\n%@",
self.log.text,info];
}
@end- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
流程如下(圖中藍(lán)色代表外圍設(shè)備操作,綠色部分表示中央設(shè)備操作):?
B.中央設(shè)備
中央設(shè)備的創(chuàng)建一般可以分為如下幾個(gè)步驟:
創(chuàng)建中央設(shè)備管理對象CBCentralManager并指定代理。掃描外圍設(shè)備,一般發(fā)現(xiàn)可用外圍設(shè)備則連接并保存外圍設(shè)備。查找外圍設(shè)備服務(wù)和特征,查找到可用特征則讀取特征數(shù)據(jù)。?
下面是一個(gè)簡單的中央服務(wù)器端實(shí)現(xiàn),點(diǎn)擊“啟動”按鈕則開始掃描周圍的外圍設(shè)備,一旦發(fā)現(xiàn)了可用的外圍設(shè)備則建立連接并設(shè)置外圍設(shè)備的代理,之后開始查找其服務(wù)和特征。一旦外圍設(shè)備的特征值做了更新,則可以在代理方法中讀取更新后的特征值。?
#import "ViewController.h"
#import <CoreBluetooth/CoreBluetooth.h>
#define kServiceUUID @"C4FB2349-72FE-4CA2-94D6-1F3CB16331EE" //服務(wù)的UUID
#define kCharacteristicUUID @"6A3E4B28-522D-4B3B-82A9-D5E2004534FC" //特征的UUID@interface ViewController ()<CBCentralManagerDelegate,CBPeripheralDelegate>@property (
strong,
nonatomic) CBCentralManager *centralManager;
@property (
strong,
nonatomic)
NSMutableArray *peripherals;
@property (
weak,
nonatomic)
IBOutlet UITextView *log;
@end@implementation ViewController
#pragma mark - 控制器視圖事件
- (
void)viewDidLoad {[
super viewDidLoad];
}
#pragma mark - UI事件
- (
IBAction)startClick:(
UIBarButtonItem *)sender {_centralManager=[[CBCentralManager alloc]initWithDelegate:
self queue:
nil];
}
#pragma mark - CBCentralManager代理方法
-(
void)centralManagerDidUpdateState:(CBCentralManager *)central{
switch (central
.state) {
case CBPeripheralManagerStatePoweredOn:
NSLog(@
"BLE已打開.");[
self writeToLog:@
"BLE已打開."];
[central scanForPeripheralsWithServices:
nil options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
break;
default:
NSLog(@
"此設(shè)備不支持BLE或未打開藍(lán)牙功能,無法作為外圍設(shè)備.");[
self writeToLog:@
"此設(shè)備不支持BLE或未打開藍(lán)牙功能,無法作為外圍設(shè)備."];
break;}
}
-(
void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(
NSDictionary *)advertisementData RSSI:(
NSNumber *)RSSI{
NSLog(@
"發(fā)現(xiàn)外圍設(shè)備...");[
self writeToLog:@
"發(fā)現(xiàn)外圍設(shè)備..."];[
self.centralManager stopScan];
if (peripheral) {
if(![
self.peripherals containsObject:peripheral]){[
self.peripherals addObject:peripheral];}
NSLog(@
"開始連接外圍設(shè)備...");[
self writeToLog:@
"開始連接外圍設(shè)備..."];[
self.centralManager connectPeripheral:peripheral options:
nil];}}
-(
void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral{
NSLog(@
"連接外圍設(shè)備成功!");[
self writeToLog:@
"連接外圍設(shè)備成功!"];peripheral
.delegate=
self;[peripheral discoverServices:@[[CBUUID UUIDWithString:kServiceUUID]]];
}
-(
void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(
NSError *)error{
NSLog(@
"連接外圍設(shè)備失敗!");[
self writeToLog:@
"連接外圍設(shè)備失敗!"];
}
#pragma mark - CBPeripheral 代理方法
-(
void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(
NSError *)error{
NSLog(@
"已發(fā)現(xiàn)可用服務(wù)...");[
self writeToLog:@
"已發(fā)現(xiàn)可用服務(wù)..."];
if(error){
NSLog(@
"外圍設(shè)備尋找服務(wù)過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription);[
self writeToLog:[
NSString stringWithFormat:@
"外圍設(shè)備尋找服務(wù)過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription]];}CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
for (CBService *service in peripheral
.services) {
if([service
.UUID isEqual:serviceUUID]){[peripheral discoverCharacteristics:@[characteristicUUID] forService:service];}}
}
-(
void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(CBService *)service error:(
NSError *)error{
NSLog(@
"已發(fā)現(xiàn)可用特征...");[
self writeToLog:@
"已發(fā)現(xiàn)可用特征..."];
if (error) {
NSLog(@
"外圍設(shè)備尋找特征過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription);[
self writeToLog:[
NSString stringWithFormat:@
"外圍設(shè)備尋找特征過程中發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription]];}CBUUID *serviceUUID=[CBUUID UUIDWithString:kServiceUUID];CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
if ([service
.UUID isEqual:serviceUUID]) {
for (CBCharacteristic *characteristic in service
.characteristics) {
if ([characteristic
.UUID isEqual:characteristicUUID]) {[peripheral setNotifyValue:
YES forCharacteristic:characteristic];
}}}
}
-(
void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(
NSError *)error{
NSLog(@
"收到特征更新通知...");[
self writeToLog:@
"收到特征更新通知..."];
if (error) {
NSLog(@
"更新通知狀態(tài)時(shí)發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription);}CBUUID *characteristicUUID=[CBUUID UUIDWithString:kCharacteristicUUID];
if ([characteristic
.UUID isEqual:characteristicUUID]) {
if (characteristic
.isNotifying) {
if (characteristic
.properties==CBCharacteristicPropertyNotify) {
NSLog(@
"已訂閱特征通知.");[
self writeToLog:@
"已訂閱特征通知."];
return;}
else if (characteristic
.properties ==CBCharacteristicPropertyRead) {[peripheral readValueForCharacteristic:characteristic];}}
else{
NSLog(@
"停止已停止.");[
self writeToLog:@
"停止已停止."];[
self.centralManager cancelPeripheralConnection:peripheral];}}
}
-(
void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(CBCharacteristic *)characteristic error:(
NSError *)error{
if (error) {
NSLog(@
"更新特征值時(shí)發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription);[
self writeToLog:[
NSString stringWithFormat:@
"更新特征值時(shí)發(fā)生錯誤,錯誤信息:%@",error
.localizedDescription]];
return;}
if (characteristic
.value) {
NSString *value=[[
NSString alloc]initWithData:characteristic
.value encoding:NSUTF8StringEncoding];
NSLog(@
"讀取到特征值:%@",value);[
self writeToLog:[
NSString stringWithFormat:@
"讀取到特征值:%@",value]];}
else{
NSLog(@
"未發(fā)現(xiàn)特征值.");[
self writeToLog:@
"未發(fā)現(xiàn)特征值."];}
}
#pragma mark - 屬性
-(
NSMutableArray *)peripherals{
if(!_peripherals){_peripherals=[
NSMutableArray array];}
return _peripherals;
}
#pragma mark - 私有方法
-(
void)writeToLog:(
NSString *)info{
self.log.text=[
NSString stringWithFormat:@
"%@\r\n%@",
self.log.text,info];
}
@end- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
上面程序運(yùn)行的流程圖如下:?
?
有了上面兩個(gè)程序就可以分別運(yùn)行在兩臺支持BLE的iOS設(shè)備上,當(dāng)兩個(gè)應(yīng)用建立連接后,一旦外圍設(shè)備更新特征之后,中央設(shè)備就可以立即獲取到更新后的值。需要強(qiáng)調(diào)的是使用CoreBluetooth開發(fā)的應(yīng)用不僅僅可以和其他iOS設(shè)備進(jìn)行藍(lán)牙通信,還可以同其他第三方遵循BLE規(guī)范的設(shè)備進(jìn)行藍(lán)牙通訊,這里就不再贅述。
總結(jié)
源碼下載網(wǎng)址:?
http://download.csdn.net/detail/baihuaxiu123/9507802?
請關(guān)注:http://blog.csdn.net/baihuaxiu123/article/details/51289499
總結(jié)
以上是生活随笔為你收集整理的iOS开发 蓝牙技术4.0详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。