1、之前博客中提到了崩溃收集系统。给服务器发送消息是一个http post的过程,其中会接收一个二进制数据流,这个可以是文本,也可以是一个zip文件流。与服务器进行约定,这个参数会先进行base64编码,这就意味着不会出现中文兼容等问题。 但是,ios上面有一个bug,传输的文本数据会在某些字符后错乱掉。如果传zip包,zip数据流会被破坏掉,文件无法正常打开(但是zip文件压缩是没有问题的,本地数据正常)。
我第一反映是base64编码问题。但是换过两个base64的开源实现都无法解决问题。这个是我理解错误,我把base64当成像des加密一样会因实现不同造成加密结果不同,base64算法相对简单和单一,不会有这种问题。
android版本文件上传是正常的,那么服务器就没有什么问题。 后面定位到http url encoding上面。 android的java库提供的接口比较简单明了,所以一次就写对了。ios接口有一些需要注意的地方。 a、我们进行url encoding需要单独对每个参数进行encoding,然后再把参数拼接成完成的url链接,例如这样的形式(?user=xxxx&name=xxxx&data=xxxxxxx) b、CFURLCreateStringByAddingPercentEscapes 这个函数可以进行encoding,但是第四个参数要设置好替换参数,否则无法正确的encoding,例如等于号不会进行替换。 之前的bug就是因为我在第四个参数直接传null,导致url encoding后的结果出问题,服务器无法正确接收数据。
- (NSString *)encodeToPercentEscapeString: (NSString *) input { // Encode all the reserved characters, per RFC 3986 // (<http://www.ietf.org/rfc/rfc3986.txt>) NSString *outputStr = (NSString *) CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)input, NULL, (CFStringRef)@"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8); return outputStr; } - (NSString *)decodeFromPercentEscapeString: (NSString *) input { NSMutableString *outputStr = [NSMutableString stringWithString:input]; [outputStr replaceOccurrencesOfString:@"+" withString:@" " options:NSLiteralSearch range:NSMakeRange(0, [outputStr length])]; return [outputStr stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; }
2、我写了一个通用计时器(后面再写篇文章介绍),主要思路是把所有的计时器添加到一个vector里面,然后每帧遍历所有的计时器,并在到达指定时间后调用计时器的回调。 这里就有个问题,计时器的回调函数里面可能又会添加新的计时器,同时计时器处理完毕后又应该删除掉。 如果不做考虑的话可能会因为迭代器失效造成崩溃。
所以我们的问题就是,如何安全的遍历vector并同时支持vector的动态添加和删除。
我们知道在教科书上面,vector遍历删除的代码是这么写的:
for(auto itr = vec.begin(); itr != vec.end();) { if (needDelete) { itr = vec.erase(itr); } else { ++itr; } }
但是我没有这么写,因为我还要在循环中往迭代器里面动态添加数据。所以我后面的写法是这样的
for (int i = 0; i < vec.size(); ++i) { Timer* pTimer = vec[i]; if (needDelete) { delete pTimer; vec[i] = NULL; } } vec.erase(std::remove(vec.begin(), vec.end(), NULL));
首先遍历是用的索引定位而不是迭代起,这个是因为添加数据可能造成迭代器失效(像vector中添加数据如果大于预先分配的内存数目就会重新申请新的内存块,那么迭代器就失效了),而用索引就不会有这个问题,而且对于vector而言,索引定位也是一个指针加n的过程,不会比迭代器慢。 还有就是vec.size()是每次都进行判断的,这样添加数据不会影响到遍历。 删除元素也不是直接删除,而是做一个标志在循环完毕后统一erase。 这个也有点像清空vector数据时的一种写法:
for (auto itr = vec.begin(); itr != vec.end(); ++itr) { delete *itr; } vec.clear();
虽然看上去有些别扭和低效,但是对实际项目而言,这样的效率消耗是无关紧要的,是可以忽略的。游戏运行中计时器撑死了几百个,对于这种量级,我遍历几次都不会有效率问题.
这样一个简单的问题,却考察了很多c++基本功。stl容器的基本使用,迭代器什么时候失效,vector内存分配原理,遍历和定位vector元素的时间复杂度和实际效率,解决实际问题的能力。
我感觉这样的题目拿来做面试题要比写什么排序算法还要有意义。因为我们实际工作中不会成天写排序搞算法,但是上面这些代码是跑不掉的。