您的位置:首页 > 其它

工作bug现场回放 (CFURLCreateStringByAddingPercentEscapes和vector)

2013-02-24 01:24 417 查看
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元素的时间复杂度和实际效率,解决实际问题的能力。

       我感觉这样的题目拿来做面试题要比写什么排序算法还要有意义。因为我们实际工作中不会成天写排序搞算法,但是上面这些代码是跑不掉的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: