深入分析iOS问题-performSelector:onThread:withObject:waitUntilDone
2015-09-23 23:31
447 查看
今天检查内存泄露的问题,发现误用系统API的问题。导致内存泄露。这个问题还是比较常见的,我觉得还是记下了,分享给大家
–使用代码用例
请看下图:
内存泄露就是这个吧。
直接上源码
重点关注下面代码
内部实现是创建了一个对象,并且把这个对象放到runloopinfo的数组中,创建这个对象的成员变量就是之前传入的参数。
结合今天遇到的问题,进行测试,调用该代码对应的stop函数不会被调用到,说该线程停止运行了。所以runloopinfo数组中任务不会被处理,这样就出现的不断内存增长。
大胆推测,如果[FYSingleInstance stopThread:_currentThread withUserId:nil];这里WithUserId参数传入对象,那么该对象不会被被释放,因为它被任务对象持有了内存。
马上实现。
调用代码修改为:
FYTempData实现具体为
运行现象:每一次1M的内存泄露
内存,不断上升
每次调用,就有1M内存,调用了16次就有1M内存
对于传入参数
如果系统类型,比如array,dictionary
请使用 []NSArray arrayWithObjects:xxx],等方法创建一个有autorelease属性的对象。
如果是自定义的类,希望深拷贝一个新的对象,当参数传入。
对于NSThread使用,如果要加入新的任务,请要确定,该Thread还在运行中,runloop还在执行。因为通过实现,及时把把thread实例设置为nil,内部的内存还是不会释放。
转发请注明出处,谢谢
现场还原
–FYSingleInstance类主要代码// 初始化上下文 + (FYSingleInstance *) S { static FYSingleInstance * instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ if (!instance) { instance = [[FYSingleInstance alloc] init]; } }); return instance; } /* * 停止线程 */ + (void) stopThread:(id) thread withUserId:(id) userId { // 停止初始化线程 @try { [[FYSingleInstance S] performSelector:@selector(stop:) onThread:thread withObject:userId waitUntilDone:NO]; } @catch (NSException * exception) { NSLog(@"%@", exception.callStackSymbols); } }
–使用代码用例
[FYSingleInstance stopThread:_currentThread withUserId:nil];
内存泄露现象
如果不断调用上述代码,就看到不断有内存泄露。请看下图:
抽丝剥茧开始
好奇怪为啥这么多16 bytes在内存中,而且使用代码用例调用越多,这些16bytes的内存越多。内存泄露就是这个吧。
查找原因
performSelector:onThread:withObject:waitUntilDone这个执行原理.直接上源码
- (void) performSelector: (SEL)aSelector onThread: (NSThread*)aThread withObject: (id)anObject waitUntilDone: (BOOL)aFlag modes: (NSArray*)anArray { GSRunLoopThreadInfo *info; NSThread *t; if ([anArray count] == 0) { return; } t = GSCurrentThread(); if (aThread == nil) { aThread = t; } info = GSRunLoopInfoForThread(aThread); if (t == aThread) { /* Perform in current thread. */ if (aFlag == YES || info->loop == nil) { /* Wait until done or no run loop. */ [self performSelector: aSelector withObject: anObject]; } else { /* Don't wait ... schedule operation in run loop. */ [info->loop performSelector: aSelector target: self argument: anObject order: 0 modes: anArray]; } } else { GSPerformHolder *h; NSConditionLock *l = nil; if ([aThread isFinished] == YES) { [NSException raise: NSInternalInconsistencyException format: @"perform on finished thread"]; } if (aFlag == YES) { l = [[NSConditionLock alloc] init]; } h = [GSPerformHolder newForReceiver: self argument: anObject selector: aSelector modes: anArray lock: l]; [info addPerformer: h]; if (l != nil) { [l lockWhenCondition: 1]; [l unlock]; RELEASE(l); if ([h isInvalidated] == YES) { RELEASE(h); [NSException raise: NSInternalInconsistencyException format: @"perform on finished thread"]; } /* If we have an exception passed back from the remote thread, * re-raise it. */ if (nil != h->exception) { NSException *e = AUTORELEASE(RETAIN(h->exception)); RELEASE(h); [e raise]; } } RELEASE(h); } }
重点关注下面代码
h = [GSPerformHolder newForReceiver: self argument: anObject selector: aSelector modes: anArray lock: l]; [info addPerformer: h];
内部实现是创建了一个对象,并且把这个对象放到runloopinfo的数组中,创建这个对象的成员变量就是之前传入的参数。
泄露原因
对于iOS,线程驱动都是通过runloop驱动的。当runloop运行时,都是运行该线程空间中的runloop任务,也就是源码中的runloopinfo数组中的内容。当一个任务完成后,该任务对象就会释放了,对应的内存也会释放。如果线程停止了,runloop就停止工作,那么还是可以通过之前说的函数给runloopinfo数组加入任务。但是由于runloop不在运行,所以任务对象不会被处理,那么也不会从runloopinfo数组中删除,对应的内存也不会被释放。结合今天遇到的问题,进行测试,调用该代码对应的stop函数不会被调用到,说该线程停止运行了。所以runloopinfo数组中任务不会被处理,这样就出现的不断内存增长。
大胆推测,如果[FYSingleInstance stopThread:_currentThread withUserId:nil];这里WithUserId参数传入对象,那么该对象不会被被释放,因为它被任务对象持有了内存。
马上实现。
调用代码修改为:
FYTempData* tmpData = [[FYTempData alloc] init]; tmpData.log = @"lalallalalalla"; [FYSingleInstance stopThread:_currentThread withUserId:tmpData];
FYTempData实现具体为
@implementation FYTempData { char* buffer; } - (void)dealloc { free(buffer); NSLog(@"dealloc --- FYTempData"); } -(id)init { self = [super init]; if (self) { buffer = (char*)malloc(sizeof(char) * 1024*1024); } return self; } @end
运行现象:每一次1M的内存泄露
内存,不断上升
每次调用,就有1M内存,调用了16次就有1M内存
使用总结
对于今天case,最快的修改就是(也许大家有很多从设计角度解决的方案)+ (void) stopThread:(id) thread withUserId:(id) userId { // 停止初始化线程 @try { NSThread* oneThread = thread; if (![oneThread isFinished] && ![oneThread isCancelled]) { [[FYSingleInstance S] performSelector:@selector(stop:) onThread:thread withObject:userId waitUntilDone:NO]; } } @catch (NSException * exception) { NSLog(@"%@", exception.callStackSymbols); } }
对于传入参数
如果系统类型,比如array,dictionary
请使用 []NSArray arrayWithObjects:xxx],等方法创建一个有autorelease属性的对象。
如果是自定义的类,希望深拷贝一个新的对象,当参数传入。
对于NSThread使用,如果要加入新的任务,请要确定,该Thread还在运行中,runloop还在执行。因为通过实现,及时把把thread实例设置为nil,内部的内存还是不会释放。
By the way
欢迎大家一起讨论转发请注明出处,谢谢
相关文章推荐
- Copy vs Retain in Objective C
- Objective-C之property属性分析
- Objective-C type encodings
- struts2 Map<String,Object>session心得 浅析
- Mybatis之配置文件中的objectFactory节点内容说明
- 打开页面出现Object reference not set to an instance of an object错误
- 关于swift与objective-c混合编译
- Objective-C入门20:Block
- 在java代码里写Map<String, ?>和Map<String, Object>都是可以用的,他们两个有什么区别?
- Objective-C Runtime Messaging
- 改善Objective-C的日志输出
- Objective-C入门18:KVO
- QMetaObject之invokeMethod说明和使用
- Object-c基础语法
- DynamicCRM清理PrincipalObjectAccess表,清理POA,清理WorkFlowLog
- DynamicCRM清理PrincipalObjectAccess表,清理POA,清理WorkFlowLog
- Objective-C GCC Code Block Evaluation C Extension ({…})语法
- Objective-C中NSNumber与NSDictionary的用法简介
- WaitForSingleObject函数的使用
- Object-C 验证手机号