iOS下CLLocationManager多次定位引起記憶體訪問錯誤的問題解決

來源:互聯網
上載者:User

好幾個月沒寫東西了,今天有空寫點iOS的(我發現自己是非常不專註,安卓沒搞好,又轉而搞iOS了)。

我的程式中有一個擷取使用者當前位置地址的功能。我寫了一個定位的輔助類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 + 1861   libobjc.A.dylib   0x342d41e5 objc_exception_throw + 322   CoreFoundation    0x34087acb -[NSObject doesNotRecognizeSelector:] + 1743   CoreFoundation    0x34086945 ___forwarding___ + 3004   CoreFoundation    0x33fe1680 _CF_forwarding_prep_0 + 485   Foundation        0x359ce1b7 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 2666   Foundation        0x359cde49 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:] + 1367   AutoTraffic2012   0x001232db -[GeoAddressHelper OnNetReqFinished:] + 5028   Foundation        0x359ce1b7 -[NSObject(NSThreadPerformAdditions) performSelector:onThread:withObject:waitUntilDone:modes:] + 2669   Foundation        0x359cde49 -[NSObject(NSThreadPerformAdditions) performSelectorOnMainThread:withObject:waitUntilDone:] + 13610  AutoTraffic2012   0x0011170f -[NetReqOperation OnNetReqFinished:] + 10611  CoreFoundation    0x33fe322b -[NSObject performSelector:withObject:] + 4212  Foundation        0x35a6e757 __NSThreadPerformPerform + 35013  CoreFoundation    0x34058b03 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 1414  CoreFoundation    0x340582cf __CFRunLoopDoSources0 + 21415  CoreFoundation    0x34057075 __CFRunLoopRun + 65216  CoreFoundation    0x33fda4dd CFRunLoopRunSpecific + 30017  CoreFoundation    0x33fda3a5 CFRunLoopRunInMode + 10418  GraphicsServices  0x3085efcd GSEventRunModal + 15619  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的引用計數防止它自動釋放呢?其實最開始我也是這麼乾的,但這樣又導致了另一個問題,所以後來才改成不加引用的。具體我在下一篇文章再解釋。

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.