iOS个版本蓝牙比较
随着蓝牙低功耗技术BLE(Bluetooth Low Energy)的发展,蓝牙技术正在一步步成熟,如今的大部分移动设备都配备有蓝牙4.0,相比之前的蓝牙技术耗电量大大降低。从iOS的发展史也不难看出苹果目前对蓝牙技术也是越来越关注,例如苹果于2013年9月发布的iOS7就配备了iBeacon技术,这项技术完全基于蓝牙传输。但是众所周知苹果的设备对于权限要求也是比较高的,因此在iOS中并不能像Android一样随意使用蓝牙进行文件传输(除非你已经越狱)。在iOS中进行蓝牙传输应用开发常用的框架有如下几种:
- GameKit.framework:iOS7之前的蓝牙通讯框架,从iOS7开始过期,但是目前多数应用还是基于此框架。
- MultipeerConnectivity.framework:iOS7开始引入的新的蓝牙通讯开发框架,用于取代GameKit。
- CoreBluetooth.framework:功能强大的蓝牙开发框架,要求设备必须支持蓝牙4.0。
前两个框架使用起来比较简单,但是缺点也比较明显:仅仅支持iOS设备,传输内容仅限于沙盒或者照片库中用户选择的文件,并且第一个框架只能在同一个应用之间进行传输(一个iOS设备安装应用A,另一个iOS设备上安装应用B是无法传输的)。当然CoreBluetooth就摆脱了这些束缚,它不再局限于iOS设备之间进行传输,你可以通过iOS设备向Android、Windows Phone以及其他安装有蓝牙4.0芯片的智能设备传输,因此也是目前智能家居、无线支付等热门智能设备所推崇的技术。
对比
框架 | 版本 | 运用 |
---|---|---|
GameKit.framework | 仅支持iOS设备(iOS7之后淘汰) | 同一游戏应用相关 |
MultipeerConnectivity.framework | 仅支持iOS设备(替代GameKit) | 不同应用之间 |
CoreBluetooth.framework | 支持第三方外设(蓝牙4.0以上) | 不同应用之间 |
GameKit
目前因为GameKit已经淘汰过时,那么就不在详细写这个框架,网上也有相关的讲解,有兴趣就可以去了解了解,看看别人写的代码。
MultipeerConnectivity
MultipeerConnectivity并不仅仅支持蓝牙连接,准确的说它是一种支持Wi-Fi网络、P2P Wi-Fi已经蓝牙个人局域网的通信框架,它屏蔽了具体的连接技术,让开发人员有统一的接口编程方法。通过MultipeerConnectivity连接的节点之间可以安全的传递信息、流或者其他文件资源而不必通过网络服务。此外使用MultipeerConnectivity进行近场通信也不再局限于同一个应用之间传输,而是可以在不同的应用之间进行数据传输(当然如果有必要的话你仍然可以选择在一个应用程序之间传输)。
要了解MultipeerConnectivity的使用必须要清楚一个概念:广播(Advertisting)和发现(Disconvering),这很类似于一种Client-Server模式。假设有两台设备A、B,B作为广播去发送自身服务,A作为发现的客户端。一旦A发现了B就试图建立连接,经过B同意二者建立连接就可以相互发送数据。在使用GameKit框架时,A和B既作为广播又作为发现,当然这种情况在MultipeerConnectivity中也很常见。
- A.广播
无论是作为服务器端去广播还是作为客户端去发现广播服务,那么两个(或更多)不同的设备之间必须要有区分,通常情况下使用MCPeerID对象来区分一台设备,在这个设备中可以指定显示给对方查看的名称(display name)。另外不管是哪一方,还必须建立一个会话MCSession用于发送和接受数据。通常情况下会在会话的-(void)session:(MCSession )session peer:(MCPeerID )peerID didChangeState:(MCSessionState)state代理方法中跟踪会话状态(已连接、正在连接、未连接);在会话的-(void)session:(MCSession )session didReceiveData:(NSData )data fromPeer:(MCPeerID )peerID代理方法中接收数据;同时还会调用会话的-(void)sendData: toPeers:withMode: error:*方法去发送数据。广播作为一个服务器去发布自身服务,供周边设备发现连接。在MultipeerConnectivity中使用MCAdvertiserAssistant来表示一个广播,通常创建广播时指定一个会话MCSession对象将广播服务和会话关联起来。一旦调用广播的start方法周边的设备就可以发现该广播并可以连接到此服务。在MCSession的代理方法中可以随时更新连接状态,一旦建立了连接之后就可以通过MCSession的connectedPeers获得已经连接的设备。
- B.发现
前面已经说过作为发现的客户端同样需要一个MCPeerID来标志一个客户端,同时会拥有一个MCSession来监听连接状态并发送、接受数据。除此之外,要发现广播服务,客户端就必须要随时查找服务来连接,在MultipeerConnectivity中提供了一个控制器MCBrowserViewController来展示可连接和已连接的设备(这类似于GameKit中的GKPeerPickerController),当然如果想要自己定制一个界面来展示设备连接的情况你可以选择自己开发一套UI界面。一旦通过MCBroserViewController选择一个节点去连接,那么作为广播的节点就会收到通知,询问用户是否允许连接。由于初始化MCBrowserViewController的过程已经指定了会话MCSession,所以连接过程中会随时更新会话状态,一旦建立了连接,就可以通过会话的connected属性获得已连接设备并且可以使用会话发送、接受数据
下面用两个不同的应用程序来演示使用MultipeerConnectivity的使用过程,其中:
- 一个应用运行在模拟器中作为广播节点,
- 另一个运行在iPhone真机上作为发现节点,并且实现两个节点的图片互传。
效果展示(同一个分代码,同一个UI界面):
SB中的界面搭建:
代码:
// ViewController.swift
// 蓝牙数据传输(MultipeerConnectivity)
// Created by 礼光 on 2016/11/2.
// Copyright © 2016年 LLG. All rights reserved.
import UIKit
import MultipeerConnectivity
//需要用到的内容是:
//1.创建设备ID.发现的名字
//2.会话对象
//3.广播助理
//4.代理方法(状态管理,发送接收消息)
//服务类型随便取
let serviceType = "LLG"
class ViewController: UIViewController {
//控件属性
@IBOutlet weak var stateLabel: UILabel!
@IBOutlet weak var isFound: UISwitch!
@IBOutlet weak var photo: UIImageView!
@IBOutlet weak var isConnect: UIButton!
//自定义属性
lazy var session : MCSession = {
let peerID = MCPeerID(displayName: UIDevice.current.name)//使用当前设备的名称
let session = MCSession(peer: peerID)
session.delegate = self
return session
}()
var advertiserAssistant : MCAdvertiserAssistant!
lazy var matchPeerIDs : NSMutableArray = {
let matchPeerIDs = NSMutableArray.init(array: [])
return matchPeerIDs;
}()
override func viewDidLoad() {
super.viewDidLoad()
self.isFound.isOn = false//初始化不能被发现
self.stateLabel.text = "匹配状态: 未连接"
self.isConnect.isEnabled = false
self.isConnect.backgroundColor = UIColor.gray
self.isConnect.setTitle("未连接成功", for: .normal)
}
@IBAction func switchToBeFound(_ sender: UISwitch) {
//创建一个广播助手
self.advertiserAssistant = MCAdvertiserAssistant(serviceType: serviceType, discoveryInfo: nil, session: self.session)
switch sender.isOn {
case true:
//"可被发现"
print("可被发现")
self.advertiserAssistant.start()
default:
//"不可被发现"
print("不可被发现")
self.advertiserAssistant.stop()
}
}
@IBAction func searchDevice(_ sender: UIButton) {
//创建一个蓝牙发现弹出页面,用于选择连接的设备
let browserVc = MCBrowserViewController(serviceType: serviceType, session: self.session)
browserVc.delegate = self;
self.present(browserVc, animated: true, completion: nil)
}
@IBAction func sendData(_ sender: UIButton) {
let data = UIImagePNGRepresentation(UIImage(named: "01")!)
//发送数据给已经连接的所有蓝牙设备,以不可靠的方式
try?self.session.send(data!, toPeers: self.session.connectedPeers, with: .unreliable)
}
}
//MARK: - MCBrowserViewController代理
extension ViewController : MCBrowserViewControllerDelegate {
//点击完成
func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
self.dismiss(animated: true, completion: nil)
}
//点击取消
func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
self.dismiss(animated: true, completion: nil)
}
}
//MARK: - MCSession代理
extension ViewController : MCSessionDelegate {
//监听数据传输
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
let image = UIImage(data: data)
DispatchQueue.main.async {
self.photo.image = image
}
}
//监听连接状态
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
switch state {
case .connected:
//回到主线程刷新界面
DispatchQueue.main.async(execute: {
self.stateLabel.text = "匹配状态: 已连接设备\(peerID.displayName)"
self.isConnect.isEnabled = true
self.isConnect.backgroundColor = UIColor.red
self.isConnect.setTitle("传输数据", for: .normal)
})
case .connecting:
//回到主线程刷新界面
DispatchQueue.main.async(execute: {
self.stateLabel.text = "匹配状态: 连接中..."
})
default:
DispatchQueue.main.async(execute: {
self.stateLabel.text = "匹配状态: 连接失败"
})
}
}
//用不到,代理空实现
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
}
func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
}
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL, withError error: Error?) {
}
}
ahdiuiqhwudbqwiuiuqwbsjdhvfajfhaskdhfasidhfisadhfjashdfkshdjkfhsjkdfhaksjdhfkaljshdfklasjhdfiueoiruweoriuweoirkjsdalksjdlkasjdio wqeajdhjasdhiehr iuhdksajd qidoiq dasd
qwdhashdoi dq dakjsdn qw
这个使用markdown来编辑的的代码效果实在是太恶心了,后续再补充