ASIHTTPRequest iOS7下内存泄漏问题解决记录

本文详细记录了解决ASIHTTPRequest在iOS7环境下特定内存泄漏问题的过程。通过对比不同iOS版本表现,定位问题源头并最终修复。

原文地址

发现问题

iOS7发布后,我们对产品进行了iOS7的适配。适配完成之后的某天,我使用Leaks对产品的新版本进行内存泄漏检测时发现ASIHTTPRequest存在内存泄漏问题,当时使用的设备是iTouch5,系统为iOS7.0.2。

Leaks检测结果

Leaks

(ps:使用的是ASIHTTPRequest iPhoneSample的检测图,结果是一样的)

发现之初,我以为是某处ASIHTTPRequest使用不当导致的泄漏,于是把leaks中的堆栈全部都检查了一边,但没有发现任何产品工程中的代码(其中一处泄漏的堆栈如图)。

Leaks中StackTrace结果

StackTrace

由于在iOS7发布之前的所有版本中并未看到类似的内存泄漏,所以我就开始怀疑是ASIHTTPRequest在iOS7才产生的。于是我在iOS5和iOS6的设备上进行了Leak Profile,结果没有发现任何泄漏。

对于这样的结果我仍然不是很确信,因为项目的需要我们对ASIHTTPRequest进行了一定的定制,修改了其中一部分代码。为了确定问题确实是出在ASIHTTPRequest上,我去github上翻出了ASIHTTPRequest的repo,pull了最新的代码,用Leaks在iOS7系统上进行了profile。在profile过程中我对iPhone Sample中的每个Tab以此进行了测试,结果在 Synchronous 和 Queue 上并没有发现内存泄漏,在 Upload 上发现了和之前一样泄漏。随后在iOS5和iOS6上也进行了一样的测试,结果依然是没有任何泄漏。 

自此确定了这是ASIHTTPRequest在iOS7下特有的内存泄漏,并且只会出现在有POST body的情况下。


寻找解答

发现问题之后,我仔细查看了Leaks Profile的结果,发现泄漏集中在 ASIInputStream 上,应该是在使用的过程中 release 方法在某种情况下没有被调用到。于是我重写了ASIIputStream的release方法并在其中断点,分别在iOS7和iOS6进行调试后发现iOS7比iOS6少了一次Release的调用,堆栈如图所示。 

缺少的Release的断点Stack Trace结果

Release

从堆栈来看似乎是 CoreReadStreamFromCFReadStream 这个类的析构函数在iOS7下没有被调用到。当时就感觉没救了,这是私有类,想要强行触发析构似乎是不可能的,能做的就是尽量减少其中的泄漏。于是我们做了如下修改: 

 
- (void)close
{
    [stream close];
    [stream release];
    stream = nil;
}

修改了 ASIInputStream 的 close 方法,在close完成后把其中的 NSInputStream 对象release掉,以减少内存泄漏,同时保证ASIHTTPRequest不被重复使用(因为其中的NSInputStream已经被release了无法再使用)。 

这样一来泄漏有了一定的减少,如图。

修改后的Leaks检测结果

Leakss

但这样并不能真正解决问题,泄漏依然存在,但我一时也想不到很好的办法,于是只能给repo发issue期望能够得到原作者的回复(虽然我知道希望不大,这哥们很久没管这事了- -)。 这是我发的issue: https://github.com/pokeb/asi-http-request/issues/378


解决问题

发issue大约一周后的某一天收到github的邮件,说有人回复我的issue了,进去一看有一个好心人这样解答道:

@mjohnson12

The leak is because ASIInputStream is being cast to a CFReadStreamRef but ASIInputStream does not derive from NSInputStream it just wraps it.

My Solution is to get rid of ASIInputStream and create a NSInputStream instead in the ASIHTTPRequest startRequest: method.

It breaks using the metrics that ASIInputStream records but I wasn't using them.

I'm using a fairly old version of ASIHTTPRequest v.1.6.2 so your milage may vary.

于是我按照他的做法,把ASIHTTPRequest里的 ASIInputStream 全部替换成了 NSInputStream ,再用Leaks Profile的时候泄漏果真消失了。也正如这位仁兄所说的, ASIInputStream 只是把自己伪装成一个 NSInputStream ,并且实现了一些 NSInputStream 的接口,实际都是由其中包含的 NSInputStream 实例完成的。 ASIInputStream 这个类的功能主要是用来做流量限制,如果不需要这个功能的话,直接把 ASIInputStream 替换成 NSInputStream 即可解决问题。 

那么如果我把 ASIInputStream 继承自 NSInputStream 的话是不是就能既保留流量限制功能又解决泄漏问题了呢?于是我开始尝试继承 NSInputStream ,其中碰到了一些困难。NSInputStream的init方法都是写在一个Category里的,无法被继承- -!。 

 
@interface NSInputStream (NSInputStreamExtensions)
- (id)initWithData:(NSData *)data;
- (id)initWithFileAtPath:(NSString *)path;
- (id)initWithURL:(NSURL *)url NS_AVAILABLE(10_6, 4_0);

+ (id)inputStreamWithData:(NSData *)data;
+ (id)inputStreamWithFileAtPath:(NSString *)path;
+ (id)inputStreamWithURL:(NSURL *)url NS_AVAILABLE(10_6, 4_0);
@end

经过一番google我在git上发现了一个repo: https://github.com/bjhomer/HSCountingInputStream

这个repo中实现了对于 NSInputStream 的继承。仔细阅读完成后发现其实所谓的继承也只不过时在类的@interface中声明了一下继承自 NSInputStream 而已,实际的工作还是由类中的一个 NSInputStream 实例完成的,但相比于 ASIInputStream 这个repo里的实现多了几个方法: 

 
//私有的CFRunLoopRef schedule、unschedule方法
- (void)_scheduleInCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode;
- (BOOL)_setCFClientFlags:(CFOptionFlags)inFlags
                 callback:(CFReadStreamClientCallBack)inCallback
                  context:(CFStreamClientContext *)inContext;
- (void)_unscheduleFromCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode;

//NSInputStream的代理方法
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;

这些方法的作用大家可以参考链接: http://blog.octiplex.com/2011/06/how-to-implement-a-corefoundation-toll-free-bridged-nsinputstream-subclass/ 。 

简单的说就是 NSInputStream 需要同时支持 NSRunLoop 和 CFRunLoopRef 的schedule和unschedule方法,那几个私有方法就是负责 CFRunLoopRef 的schedule,NSInputStream的代理方法则是负责事件的传递。这使我联想到了之前提到的没有被调用的 release 方法,iOS6下它的堆栈中正好有 CFRunLoopRef 的一些方法。莫非这就是 ASIInputStream 内存泄漏的原因所在? 

我当时的猜测是,iOS7下的内存泄漏是 ASIInputStream 伪装的不够像而导致的,之所以这么认为因为虽然有泄漏但 ASIInputStream 的功能依然存在,HTTP请求并未因此失效,这说明 ASIInputStream 还是被bridge成了 CFReadStream ,但只是因为少了和 NSInputStream 一样的unschedule方法导致其没有被正常unschedule。如果我把 ASIInputStream 的unschedule方法补上是否就可以解决问题? 

我把上述的几个方法在 ASIInputStream 中实现了以后再进行Leaks Profile,内存泄漏果然如预期的那样消失了!在 ASIInputStream 的 release 方法中打断点后发现也能够正常调用了。问题到这里算是解决了。接下来要解决这些私有方法调用可能会碰到的审核不通过问题,正好上面那篇文章里提供了思路,用runtime把三个私有方法重定向到自定义的方法上(方法名只是把”_“去掉了)。 

 
+ (BOOL) resolveInstanceMethod:(SEL) selector
{
    NSString * name = NSStringFromSelector(selector);

    if ( [name hasPrefix:@"_"] )
    {
        name = [name substringFromIndex:1];
        SEL aSelector = NSSelectorFromString(name);
        Method method = class_getInstanceMethod(self, aSelector);

        if ( method )
        {
            class_addMethod(self,
                            selector,
                            method_getImplementation(method),
                            method_getTypeEncoding(method));
            return YES;
        }
    }
    return [super resolveInstanceMethod:selector];
}

至此大功告成,问题顺利解决。但这个问题之所以会出现的真正缘由我还是没有弄清楚,Apple在iOS7下究竟做了什么,哪位大神如果知道的话还请告知。。感激不尽 ~_~。

附上修改过后的 ASIInputStream 代码


后记

问题没解决的时候其他同事建议我更换成AFNetworking等等其他开源库,因为这些库更新快文档全,ASIHTTPRequest接口复杂、代码繁多而且如今已年久失修无人维护了。确实如此,但由于一些项目上的原因我们无法更换,况且ASIHTTRequest在效率上略好,并且拥有其他基于NSURLConnection的库不具备一些功能。在解决问题的过程中也让我对NSInputStream的工作机制有了更深的理解,可谓一石二鸟。由于鄙人能力有限,其中一些地方可能说的有错误或者有纰漏的话还请大神们指正:)

内容概要:本文档围绕水声网络(UAN)仿真中的信道建模展开,提供了基于Matlab的代码实现方案,旨在帮助科研人员构建和理解水声通信系统的仿真环境。文档不仅聚焦于信道建模的核心技术,还系统性地整合了智能优化算法、机器学习、图像处理、路径规划、无人机应用、信号处理、电力系统管理等多个前沿科研方向的技术方法与实际案例。涵盖的具体算法包括遗传算法、粒子群优化(PSO)、神经网络、卡尔曼滤波、支持向量机、极限学习机、卷积神经网络等,并深入探讨其在通信系统仿真中的应用。文档强调“借力”科研理念,倡导利用成熟的算法工具与仿真平台提升研究效率,并提供了丰富的配套资源,包括百度网盘链接和微信公众号入口,便于读者获取完整的代码、仿真模型及相关论文资料,全面支持科研复现与创新。; 适合人群:具备一定Matlab编程基础,从事通信工程、信号与信息处理、水声工程、自动化控制、电子信息、电力系统、无人机技术等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:① 学习并复现水声网络信道建模的Matlab仿真流程;② 探索智能优化算法、机器学习、路径规划等技术在通信与多学科交叉系统仿真中的集成应用;③ 获取可用于科研项目的质量算法代码、仿真案例与复现资源,加速课题研究进程,提升科研创新能力与论文发表水平。; 阅读建议:建议读者按照文档推荐的主题顺序逐步学习,优先聚焦自身研究方向的相关内容,并结合网盘提供的完整资源进行代码实践、仿真调试与结果验证,以实现理论理解与动手能力的双重提升,充分发挥本资源在科研攻关中的支撑作用。
代码下载链接: https://pan.quark.cn/s/b27638adc362 在工业自动化监控领域中,WinCC 被视为一种常用的可视化软件,其功能在于构建人机界面(HMI)以及SCADA系统。本资料将阐释在WinCC环境下如何构建一个展现管道中流体流动的动态效果,此功能主要通过C动作脚本来完成。在此过程中,我们需要构建两个矩形,分别标记为 rec1 和 rec2。这两个矩形的度相等,但 rec1 的宽度要小于 rec2。在具体实施时,rec1 将作为展示流体运动的可见单元,而 rec2 则作为辅助元素,尽管其“显示”属性被设为关闭,但在程序执行期间,其属性参数对流体动画的表现起着决定性作用,因为 rec2 与 rec1 的宽度差将决定流体每次移动的长度。 随后,我们需要将 rec1 和 rec2 整合为一个自定义对象。在自定义对象的属性配置中,应包含 rec1.Left、rec1.Width、rec1.Visible 和 rec2.Width 这些核心属性。这些属性将有助于在C脚本中精确控制对象的位置和可见状态,以达成流体运动的视觉效果。同时,用户可根据实际需求增加其他属性,以增强自定义对象的功能性。 在自定义对象的C脚本部分,我们设定了一个周期为“250ms”的触发器。该脚本的核心职责是计算并更新流体块的位置。借助 GetPropBOOL、GetPropWord 和 GetLeft 函数,我们可以获取对象的当前状态,涵盖其可见性、位置及宽度等参数。在循环操作中,流体块(rec1)将向右移动 rec2 - rec1 的距离,一旦流体块移出显示范围(即 rec1 的右边界达到或超过 rec2 的左边界),它将重新回到起始位置,从而形成流体持续流动的模...
内容概要:本文围绕同步电机与构网型变流器在低惯量电力系统中的频率稳定特性及多时间尺度动态交互机理展开深入研究,基于IEEE9节点混合拓扑结构,采用Simulink平台构建电磁暂态仿真模型,系统复现并对比了下垂控制、虚拟同步机控制(VSM)、匹配控制以及可调度虚拟振荡器控制(dVOC)等多种构网型变流器控制策略的动态响应特性与频率支撑能力。研究重点剖析了不同类型电源在系统扰动下的频率响应行为,揭示了同步机与变流器在惯性、阻尼及调节响应等不同时间尺度上的耦合机制与相互作用规律,旨在深化对比例电力电子化电力系统稳定机理的理解,为新型电力系统的稳定分析、控制策略设计与运行优化提供坚实的理论依据与技术支撑。; 适合人群:具备电力系统分析、自动控制理论基础,熟悉Simulink仿真环境,从事新能源并网、微电网、构网型控制、电力系统稳定性研究的研究生、校科研人员及电力行业工程技术人员。; 使用场景及目标:① 掌握基于IEEE9节点系统的低惯量电力系统建模与电磁暂态仿真方法;② 深入理解并对比主流构网型变流器控制策略的核心原理、实现方式及其对系统频率稳定的贡献差异;③ 分析同步电机与构网型变流器在故障扰动下的多时间尺度动态交互过程;④ 服务于相关领域的科研课题复现、水平学术论文撰写、科技项目申报与技术方案论证。; 阅读建议:建议读者结合文中所述控制策略,亲手在Simulink中搭建或复现仿真模型,重点关注系统在负荷突变或短路故障等扰动工况下的频率响应曲线、功率动态过程及控制器内部变量变化,同时配合阅读相关领域的权威文献,以深刻理解控制机理与系统稳定性判据,从而有效提升科研实践与创新能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值