这篇文章在很早之前就在酝酿,我目前的开发生涯中,在现在这间公司之前,都是和硬件打交道的,期间踩了很多坑,尝试了很多不同方式的写法和架构的改变,因此有了些经验,可能文中的一些点大家都比较熟悉,只是在处理上略有不同,接下来我会分享一下自己的做法,作为对自己在iOS物联网方面一个短暂的总结吧。如果有什么不同的意见,也可以在评论区留下评论,可以一起探讨一下问题。
物联网都是些什么?
物联网在iOS上的表现,无非两种:
- WiFi
- 蓝牙
注:在这篇文章里,不会细致的讲到WiFi怎么样,蓝牙怎么样,不会去讨论这些,更多的是通用技巧。
而这两种表现形式,在做的事情无非就是:
硬件和App之间通过什么通讯方式交互不属于本文讨论的范畴,因为其表现形式都会像上图一样。从代码层上来讲,通讯这部分都应该封装成一个类,外部无需知道内容是如何实现,业务方只需要得到设备返回的数据以及将数据发送给设备就可以了,类似于下面这样:
@protocol DeviceDataManagerDelegate <NSObject>
// 这里的device他可以是蓝牙的外设,也可以是能某种能标示设备的tag,能get到点就可以了
- (void)readData:(NSData *)data device:(id)device;
@end
@interface DeviceDataManager : NSObject
- (void)sendData:(NSData *)data device:(id)device;
@end
但在实际开发中,并没有这么的简单。首先是App端的收发,发送消息对数据的拼装、硬件端是否在线、失败后的重连重发等,然后到接收消息时的数据解析、数据缓存、粘包等的情况发生,如果事先没有考虑好这些问题,开发的时候很容易写出灾难级的代码。
聊聊传输协议
如果你是直接和硬件进行通讯的,那免不了会接触到二进制协议,下面的都是围绕二进制协议讲讲自己的看法。
为了下面的内容更好理解,避免和http请求的网络层混淆,我把这一层叫作传输层
。
在网络层
中,我们一般会根据业务对网络请求进行封装,避免重复写处理非正常结果时的代码,传输层中并不能像网络层一样能直接使用得到的数据,传输层中得到一般都是经过自定的协议包装加密过后的数据,而不像网络层使用JSON那么方便,抛开自定义的协议来讲,物联网中传输层最基本的内容都会由以下的组成:
传输层数据
│
├────命令字(功能)
├────消息体(行为)
在得到这部分数据之后,我们会经过根据事先与硬件方约定好的协议去进行解析,例如0x0100
这个数据代表了什么:
开关(功能) 状态(行为)
传输层数据 0x01 0x00
这部分数据看起来十分简单,但是对于一个完全没有看过事先与硬件方约定好的协议来说,是非常难懂的。
在我当时团队的code review的时候,也发现了因此带来的一些问题:
1.随着版本更新,新的协议增加导致数据变得越来越庞大,接手的同学一脸懵逼;
2.处理数据后,通知满天飞,甚至没有处理消息体直接丢;
对于第一个问题,我曾经有考虑过使用一门语言作出通用的解析库,硬件端、移动端、前端、后台如果需要解析这个数据,只需要传入传输层数据,返回的就是JSON对象这样的一个方案。想象中挺美好的,但是这样也会带来解析库的维护成本以及额外的文档,后来还是放弃了这个做法。
在第一的问题上,最终还是老老实实的手动解析成model。
造成第二个问题,很大程度也是因为团队里没有约定很好的代码规范导致的。在当时,我还没有很深入的了解过AOP,选择的是在一开始处理完数据之后,会用另外一个model的block去接收,然后另外在controller去订阅,这种做法有个弊端就是代码量十分多,后来我了解到了AOP
之后,我强烈推荐使用AOP
去解决这个问题,这里用AOP好处是:
- 不需要再管理通知的监听和移除,也因此避免了通知带来的没有释放控制器的问题;
- 通过
AOP
,代码会变得更加整洁和少;
空说无益,还是直接上图吧
AOP
的框架,我看了网上也有很多,大部分是在Aspects的基础上封装的,更推荐直接去使用Aspects
,像是一些发送队列,几秒内只接收一个,几秒内只处理最后一个这种需求,都比较方便,当然也用RAC
或者RxSwift
处理,见仁见智。
怕讲的太抽象,放一段使用Aspects
的伪代码:
Action的伪实现
@implementation TAction
+ (NSDictionary *)parse:(NSData *)data
{
/*
校验..
解析..
处理数据..
*/
return @{@"data":@"hello action",
@"code":0}; //不同的code标志不同的事件,例如返回的值为空、拿的是缓存的值、设备丢失连接等
}
@end
Store的伪代码实现
@implementation TStore
+ (void)subscribe:(void(^)(NSString *str))handler_block
{
[object_getClass([TAction class]) aspect_hookSelector:@selector(parse:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo,NSData *data) {
NSDictionary *dict;
[aspectInfo.originalInvocation getReturnValue:&dict];
/*
对不同的code作出相应处理
*/
if (handler_block) {
handler_block(dict);
}
} error:NULL];
}
@end
在Controller上,我们就只需要订阅Store的消息就可以了:
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[TStore subscribe:^(NSDictionary *dict) {
NSLog(@"%@",dict);
}];
}
@end
物联网除了还会有很多关于网络层的小坑以外,别的就和其他领域的也差不多了,所以也不想说太多老生常谈的东西,就这样。
请保持转载后文章内容的完整,以及文章出处。本人保留所有版权相关权利。