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

本文深入探讨了在代码实现过程中遇到的核心挑战——计时器管理与HTTP URL编码问题,详细介绍了如何通过优化计时器遍历逻辑及正确处理URL编码避免数据丢失。文章还分享了一个通用计时器类的实现,旨在帮助开发者更高效地管理计时任务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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元素的时间复杂度和实际效率,解决实际问题的能力。

我感觉这样的题目拿来做面试题要比写什么排序算法还要有意义。因为我们实际工作中不会成天写排序搞算法,但是上面这些代码是跑不掉的。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值