Erebor 笔记

第二版的みんなのシストレ在8月份上线之后,iOS客户端可谓是问题不断,故事应该是从RPC请求成功后菊花消不掉说起。先来说说我厂的iOS网络请求框架,这个框架个人认为也是有很多卖点的,由于我们与服务器通信是由NSStream流支持的,和AFNetworking不同,我们这个底层的NSStream流存在很多的状态,根据这些状态可以保证流从建立到断开再到建立这样一个无缝的操作,也就是说如果当前正在建立流的时候需要断开,在流成功建立后会立即断开,反过来也是,另一个标杆项是当前的流正在建立的时候,可以让发送到RPC层的请求等到流建立成功之后再发送,这样在TCP链接成功后不会损失这期间发送的消息。

在发送请求的时候,会有一个ProgressManager来控制从RPC层回来的消息或者错误信息,用户看到的就是一个大菊花罩在屏幕上(不管调用多少次,都只有一个菊花,我们通过其它手段控制菊花的消失),这时用户无法进行其它操作,每次调用ProgressManager的时候都需要判断流的状态,需要的话会与服务器建立连接,之后发送请求、等待消息、错误处理。

第一个严重的问题就是在项目上线的时候,发现这个菊花消不掉,经过调查发现是底层RPC的状态切换存在问题,导致reading线程已经挂掉,而writing线程依然在等待reading线程建立连接。这种问题是比较恶心的,不但不崩溃,还需要用户眼睁睁的看着菊花然后手动将程序退出,当然在这期间可能会有很多在等待回包的线程。

第一个问题算是修好了,那么原来那些没有发出去的消息,现在可就都能发出去了,所以Flurry后台看到了几个崩溃的信息,我们系统的midapi日志也发现了一堆的OVERLOAD(单位时间内请求数量过高)警告。

崩溃的问题都是出现在多线程上,很多属性没有加Atomic修饰符,导致线程间的资源竞争,而后一一修复。

对于OVERLOAD其实是解决了好多遍,直到现在还有这个问题,我们程序内有一个拉取客户资金信息的statement请求,由于需要计算取引可能目安和显示,并且statement是随着持仓、汇率实时变化的,我们做了一个定时器,每隔一定的时间去请求,一开始不知道为啥OVERLOAD总报在这个statement请求上,但是通过日志观察都是用户频繁的切换tab造成的,又由于statement请求是每个VC最后发送的,所以只能断定该警告是由于用户无聊导致的。

万事都是横看成岭侧成峰啊,我们的请求都是在viewWillAppear发送的,一开始的时候,iOS的VC需要该statement信息的页面都会起一个timer来拉取这个数据,但是发现当用户进行页面切换的速度太快,也就是说系统只调用了viewWillAppear,再点击tabbar的按钮页面就被切换到其它页面了,这时viewDidAppear就没有触发,当然在viewDidDisappear中取消timer的方法也就没有执行,所以一个程序中可能同时存在多个statement的timer在拉取数据。之后通过一个单例来拉取这个statement就放心了,这个看似很不错的解决方法,直到重新设计了MVC。

重新设计MVC是参考了安卓版的程序,主要的目的就是要保证model的完整性,也就是说假如一个VC的model需要请求三个接口,model的完整性指的是这三个请求的数据都成功返回后才更新VC,若其中任意一个请求失败,都不能更新界面,这样是为了避免用户选择了USD/JPY但是汇率确是EUR/JPY的,这就要用到copy机制,只要VC需要重新订阅信息,就需要将当前VC的model拷贝一份,然后设置请求需要的信息,再发送,如果这个copy的model所有请求成功,需要覆盖原来的model,失败的话只是提示一下,并不更新页面。

那么接着上面的问题继续说,用于获取statement这个单例本身也是一个model,并且属于一个VC,这样的话,单例就copy出来好多份(这个当时没有注意到),后来让这个单例继承自NSObject就修改好了。再一次上线。

这次上线发现Flurry要么就不上报crash,也么就是没法符号化的crash文件,OVERLOAD的警告依然存在(这次不是关于statement的了),多了顶一个警告,socket链接成功后没有进行doConnect(一个链接的请求)的操作,导致某些接口不正常(尚未修复)。

Flurry的问题在DEV环境下测试崩溃的时候也没有上报,UAT环境下也不上报,后来负责人咨询Flurry的工作人员,人家给了个新版的SDK,需要升级一下,经过测试,crash信息的确上报了。

这次的OVERLOAD是关于拉取Product信息的,一秒内发送了好多次,在丢包、延迟高的网络环境下可以重现。由于外汇软件没有Product就像是没有发动机的车一样,Product定义了交易量、注文类型的基本信息,所以设计的时候就考虑到其重要性,所有的后续操作都需要在成功拉取了product信息之后才能进行,所以在拉取Product信息的时候做了个半闭半开的环,如果请求失败就继续请求,但是在请求之前还要先判断网络的链接状况,stream流建立成功后也要发送一次loadProduct操作,如果频繁的建立链接就会产生雪崩效应,不仅如此,程序内如果从服务器获取数据失败会给出提示框,提示框有一个按钮,点击按钮后会执行相关的操作,对于loadProduct这个相关操作就是在拉取一次product信息,偏偏树欲静而风不止,该提示框只能有一个,当前如果窗口中有一个提示框的话,下一个提示框dismiss掉当前的提示框,并将之前提示框要执行的相关操作保存在数组中,当点击按钮的时候会依次执行数组中的block。经过调查发现,在流报错的时候会意外的将connectFuture置成nil,而这个connectFuture其实是与NSStream流的状态绑定的,当链接建立成功后或者断开链接的时候,才能置成nil,而不是在报错的时候,除此之外,将保存提示框block的数组做了调整,在针对类似Product这类请求的时候,将block放入字典中,以类型作为key,这样就排除发送多次product的请求。再次上线新版本。

天有不测风云,Flurry后台一下报了将近十多个crash,其中一个还是程序内没有注意到的多线程问题,其余的都是崩溃在了Flurry SDK内部方法上,经过调查发现,在Flurry的GitHub上,关于新版本的issue中很多人都在讨论这个问题,YAhoo的Flurry小组说大概会在11月15日修正,但是等不了,只能将Flurry版本降回原来的版本,发现竟然又能上传崩溃信息了(此处省略一万字吧。。。),再上一版。

又发现OVERLOAD的警告了,还是那个熟悉的警告,但已不是它的背影而已,这次是由于loadModel造成的,程序内进行loadModel的操作比较多,VC注册了一个从后台回到前台的通知以及连接建立成功的通知,chart页面额外还有一个Product拉取成功的通知,都要进行loadModel操作。

Erebor 笔记_第1张图片
09-55-57.jpg

由于进行loadModel时,model都是要copy的,所以无法从model的状态判断当前是否正在经进行loadModel操作,从上面的图中可以看到,从后台切换回前台或者链接建立的reset操作,loadModel与product的过程其实是相同的,所以将上图改为下图(简易版):

Erebor 笔记_第2张图片
09-59-40.jpg

在loadProduct操作成功后在进行loadModel来作为其reset的机制,这样的话从后台切换回前台的时候网络条件好的情况下就只调用一次,接下来就是要控制reset方法内请求的调用次数了,这就比较好办了通过开关或者其他方式都很简单。

但是新问题又来了,(还是网络状况不好的情况下)我们程序的启动过程如下:

1. 创建一个SplashVC。

2. 将rootViewController设为SplashVC,并检查版本信息

3. 实例化tabbar,将rootViewController指向TarbarController

4. 对tabbar的select 页面加载数据。

正常情况下,凡是用ProgressManager打包的请求页面都会有大菊花,用户是不能有其它操作的,但是问题其实出在了第2、3步,由于大菊花是加在window上的,请求都是在viewWillAppear发送的,也就是说菊花先加到window上了,后来才有的viewDidAppear,所以根本看不到菊花,因为菊花被盖在SplashVC.view下面了,包括后续的操作(之前说过ProgressManager的菊花多次请求只加在window上一次)用户都自由了,直到某次请求执行完,菊花会hide,下次再加载菊花的时候,菊花才会覆盖在window的最上层,没有了菊花的保护,用户就会像那首沧浪之歌一样,在那瞭望无边的草原上摇摆了。这也就解释了之前为什么服务器会报loadChart的警告,该警告的意思就是没有传入(或者传入了0)productID,服务器不知道该给客户端那个货币对的chart数据。可选的两个方案:

1. 执行完2-3步的时候,需要将rootViewController的view sendback.

2. 在progressManager的菊花上加一个tag,再执行2-3步的时候判断一下即可。

。。。待续
附带:弱网、丢包环境配置信息


Erebor 笔记_第3张图片
IMG_0009.PNG

你可能感兴趣的:(Erebor 笔记)