浅谈iOS在物联网应用中的架构

架构,物联网,iOS

Posted by Karim on January 16, 2018

这篇文章在很早之前就在酝酿,我目前的开发生涯中,在现在这间公司之前,都是和硬件打交道的,期间踩了很多坑,尝试了很多不同方式的写法和架构的改变,因此有了些经验,可能文中的一些点大家都比较熟悉,只是在处理上略有不同,接下来我会分享一下自己的做法,作为对自己在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好处是:

  1. 不需要再管理通知的监听和移除,也因此避免了通知带来的没有释放控制器的问题;
  2. 通过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

物联网除了还会有很多关于网络层的小坑以外,别的就和其他领域的也差不多了,所以也不想说太多老生常谈的东西,就这样。


请保持转载后文章内容的完整,以及文章出处。本人保留所有版权相关权利。

分享到: