iOS下CLLocationManager多次定位引起内存访问错误的问题解决
2012-09-04 00:10
791 查看
好几个月没写东西了,今天有空写点iOS的(我发现自己是非常不专注,安卓没搞好,又转而搞iOS了)。
我的程序中有一个获取用户当前位置地址的功能。我写了一个定位的辅助类LocationHelper,在这个类里调用CLLocationManager,接管didUpdateToLocation事件获取经纬度坐标,然后再向后台发送坐标请求返回地址。使用时,我在某ViewController里创建一个LocationHelper类,将ViewController当成locHandler的Delegate传给过去,当获得到坐标时立即停止定位功能,并调后台请求返回地址,得到地址后再回调locHandler的方法,完成定位地址过程。在这过程中,程序将显示定位进度条不允许用户操作,直到定位完成获取地址。LocationHelper将被ViewController一直保持,直到ViewController释放。
LocationHelper的大概类定义如下:
其中GeoAddressHelper通过网络请求,把经纬度转成中文地址:
但是,就这么看似简单的两个类,居然时不时会出现内存地址错误(无法识别的selector之类的),导致程序闪退。闪退时错误堆栈如下:
从错误堆栈上看,显然LocationHelper这时被释放了。但由于LocationHelper被ViewController引用,而ViewController在这段时间内正在显示定位进度条不允许操作退出,因此理论上是不可能被提前释放的。这个错误不容易重现,研究了很久代码,也没找出哪里写法有问题。
后来经过大量测试,发现有这么个规律:如果经常使用程序,这个错误不容易出现;操作慢一些也不容易出错;但如果把手机闲置一段时间再来使用,并且界面切换的操作速度快一些,则第一次使用时很容易出现这个错误。
闲置一段时间后,程序使用上跟平时有什么区别呢?我感觉可能跟第一次定位有关。在操作地图时,首次定位经常会先出现一个粗略定位,过一会再出现一个更精确的纠正的定位,这样可能会连续触发两次didUpdateToLocation事件,导致出错;而后续的定位可能就都是一次定位。我在代码中,第一次定位成功时,已经立即调stopUpdatingLocation停止定位扫描了,理论上是不会再触发定位了,因此我一直没往这上面想。
但实际情况似乎跟想像的不一样,从现象看很可能didUpdateToLocation触发了两次。于是我在LocationHelper中加了个locFired标识,只要触发一次,立即将此标识置为YES,下次不再处理。经过这么处理,错误果然消失了。
原来,stopUpdatingLocation并不一定能立即停止定位。在我第一次获得经纬度坐标完成地址查询后,ViewController上的定位进度条消失允许操作;这时LocatipnHelper没有完全停止扫描,didUpdateToLocation事件再次触发,又发起了坐标转换地址请求;恰恰在这个时候,ViewController在用户操作快时可能会立即被pop关闭并释放,同时LocationHelper也被释放,导致GeoAddressHelper在请求完成时回调LocationHelper产生内存访问错误。通过设置标识位阻止其第二次触发,问题就解决了。
那有人又会说了,既然GeoAddressHelper引用了LocationHelper,为何不增加LocationHelper的引用计数防止它自动释放呢?其实最开始我也是这么干的,但这样又导致了另一个问题,所以后来才改成不加引用的。具体我在下一篇文章再解释。
我的程序中有一个获取用户当前位置地址的功能。我写了一个定位的辅助类LocationHelper,在这个类里调用CLLocationManager,接管didUpdateToLocation事件获取经纬度坐标,然后再向后台发送坐标请求返回地址。使用时,我在某ViewController里创建一个LocationHelper类,将ViewController当成locHandler的Delegate传给过去,当获得到坐标时立即停止定位功能,并调后台请求返回地址,得到地址后再回调locHandler的方法,完成定位地址过程。在这过程中,程序将显示定位进度条不允许用户操作,直到定位完成获取地址。LocationHelper将被ViewController一直保持,直到ViewController释放。
LocationHelper的大概类定义如下:
@implementation LocationHelper @synthesize locHandler; - (id)initLocationHelper:(id<MyLocationDelegate>)handler{ self=[super init]; self.locHandler=handler; locationMan=[[CLLocationManager alloc]init]; locationMan.delegate=self; [locationMan startUpdatingLocation]; return self; } - (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{ // 取得经纬度 CLLocationCoordinate2D coordinate = newLocation.coordinate; CLLocationDegrees latitude = coordinate.latitude; CLLocationDegrees longitude = coordinate.longitude; [locationMan stopUpdatingLocation]; GeoAddressHelper * gah=[[GeoAddressHelper alloc] initWithGeoX:longitude andGeoY:latitude andResultDelegate:self]; self.curGah=gah; [gah release]; } - (void)OnGeoAddressFound:(NSObject*)res{ [locHandler locationHelperFoundAddress:res]; } @end
其中GeoAddressHelper通过网络请求,把经纬度转成中文地址:
@implementation GeoAddressHelper @synthesize eventDelegate,geoX,geoY; -(void)initWithGeoX:(double)x andGeoY:(double)y andResultDelegate:(NSObject*)evtDlg{ self.eventDelegate=evtDlg; geoX=x; geoY=y; NSMutableString * url=(NSMutableString*)[MyApp getServerHttpUrl:@"opId=7100017"]; [url appendFormat:@"&x=%.6f&y=%.6f",x,y]; NetReqOperation * req=[[NetReqOperation alloc] initWithURL:url withDelegate:self]; [[MyApp netReqQueue] addOperation:req]; [req release]; } - (void)OnNetReqFinished:(NSObject *)res{ [eventDelegate performSelectorOnMainThread:@selector(OnGeoAddressFound:) withObject:res waitUntilDone:YES]; } @end
但是,就这么看似简单的两个类,居然时不时会出现内存地址错误(无法识别的selector之类的),导致程序闪退。闪退时错误堆栈如下:
-[__NSCFSet OnGeoAddressFound:]: unrecognized selector sent to instance 0xf678640 (null) ( 0 CoreFoundation 0x340848d7 __exceptionPreprocess + 186 1 libobjc.A.dylib 0x342d41e5 objc_exception_throw + 32 2 CoreFoundation 0x34087acb -[NSObject doesNotRecognizeSelector:] + 174 3 CoreFoundation 0x34086945 ___forwarding___ + 300 4 CoreFoundation 0x33fe1680 _CF_forwarding_prep_0 + 48 5 Foundation 0x359ce1b7 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 266 6 Foundation 0x359cde49 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:] + 136 7 AutoTraffic2012 0x001232db -[GeoAddressHelper OnNetReqFinished:] + 502 8 Foundation 0x359ce1b7 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 266 9 Foundation 0x359cde49 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:] + 136 10 AutoTraffic2012 0x0011170f -[NetReqOperation OnNetReqFinished:] + 106 11 CoreFoundation 0x33fe322b -[NSObject performSelector:withObject:] + 42 12 Foundation 0x35a6e757 __NSThreadPerformPerform + 350 13 CoreFoundation 0x34058b03 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 14 14 CoreFoundation 0x340582cf __CFRunLoopDoSources0 + 214 15 CoreFoundation 0x34057075 __CFRunLoopRun + 652 16 CoreFoundation 0x33fda4dd CFRunLoopRunSpecific + 300 17 CoreFoundation 0x33fda3a5 CFRunLoopRunInMode + 104 18 GraphicsServices 0x3085efcd GSEventRunModal + 156 19 UIKit 0x3745b743 UIApplicationMain + 1090 )
从错误堆栈上看,显然LocationHelper这时被释放了。但由于LocationHelper被ViewController引用,而ViewController在这段时间内正在显示定位进度条不允许操作退出,因此理论上是不可能被提前释放的。这个错误不容易重现,研究了很久代码,也没找出哪里写法有问题。
后来经过大量测试,发现有这么个规律:如果经常使用程序,这个错误不容易出现;操作慢一些也不容易出错;但如果把手机闲置一段时间再来使用,并且界面切换的操作速度快一些,则第一次使用时很容易出现这个错误。
闲置一段时间后,程序使用上跟平时有什么区别呢?我感觉可能跟第一次定位有关。在操作地图时,首次定位经常会先出现一个粗略定位,过一会再出现一个更精确的纠正的定位,这样可能会连续触发两次didUpdateToLocation事件,导致出错;而后续的定位可能就都是一次定位。我在代码中,第一次定位成功时,已经立即调stopUpdatingLocation停止定位扫描了,理论上是不会再触发定位了,因此我一直没往这上面想。
但实际情况似乎跟想像的不一样,从现象看很可能didUpdateToLocation触发了两次。于是我在LocationHelper中加了个locFired标识,只要触发一次,立即将此标识置为YES,下次不再处理。经过这么处理,错误果然消失了。
原来,stopUpdatingLocation并不一定能立即停止定位。在我第一次获得经纬度坐标完成地址查询后,ViewController上的定位进度条消失允许操作;这时LocatipnHelper没有完全停止扫描,didUpdateToLocation事件再次触发,又发起了坐标转换地址请求;恰恰在这个时候,ViewController在用户操作快时可能会立即被pop关闭并释放,同时LocationHelper也被释放,导致GeoAddressHelper在请求完成时回调LocationHelper产生内存访问错误。通过设置标识位阻止其第二次触发,问题就解决了。
那有人又会说了,既然GeoAddressHelper引用了LocationHelper,为何不增加LocationHelper的引用计数防止它自动释放呢?其实最开始我也是这么干的,但这样又导致了另一个问题,所以后来才改成不加引用的。具体我在下一篇文章再解释。
相关文章推荐
- iOS开发 -- 解决IOS CLLocationManager 定位返回旧数据的问题
- iOS指南系列:如何解决奔溃问题-关于内存访问
- ios无法定位CLLocationManager Delegate方法不能被调用的问题
- iOS指南系列:如何解决奔溃问题-关于内存访问续
- IOS上解决内存越界访问问题
- php出现内存位置访问无效错误问题解决方法
- 内存不足引起的SIGKILL:一个缓冲区不断增长问题的定位与解决(解释SIGKILL原因)
- iOS指南系列:如何解决奔溃问题-关于内存访问续
- 解决:tomcat6 多个web项目页面出现 多次重定向错误无法访问的问题
- 内存不足引起的SIGKILL:一个缓冲区不断增长问题的定位与解决(解释SIGKILL原因)
- iOS 自定义滑动返回和解决连续多次push,pop引起的crash问题
- 内存不足引起的SIGKILL:一个缓冲区不断增长问题的定位与解决
- 关于多次颁发ios开发证书后,真机调试的错误问题解决
- php出现内存位置访问无效错误问题解决方法
- iOS指南系列:如何解决奔溃问题-关于内存访问
- 内存不足引起的SIGKILL:一个缓冲区不断增长问题的定位与解决(解释SIGKILL原因)
- iOS指南系列:如何解决奔溃问题-关于内存访问续
- iOS指南系列:如何解决奔溃问题-关于内存访问续2
- iOS内存错误EXC_BAD_ACCESS的解决方法
- IOS LocationManager定位国内偏移,火星坐标(GCJ-02)解决方法