項(xiàng)目中對(duì)定位、地址搜索等功能采用了LocationManager單例模式,關(guān)于單例的優(yōu)點(diǎn)在此就不贅述了,這里主要想分享一下單例模式下LocationManager的兩個(gè)痛點(diǎn)。
首先簡單貼一下單例的實(shí)現(xiàn):
static LocationManger *locationInstance;
+(LocationManger*)getInstanceWithDelegate:(id)delegate
{
@synchronized(self) {
if (!locationInstance) {
locationInstance = [[LocationManger alloc] init];
locationInstance.geocoder = [[CLGeocoder alloc] init];
}
if (delegate) {
locationInstance.delegate = delegate;
}
}
return locationInstance;
}
當(dāng)然,更推薦這樣的寫法:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//TO DO
});
接下來,開始分析痛點(diǎn):
痛點(diǎn)一
項(xiàng)目中某頁面需要同時(shí)用到定位當(dāng)前地址+對(duì)某坐標(biāo)進(jìn)行逆地理編碼驗(yàn)證兩個(gè)功能,高德回調(diào)函數(shù)難以區(qū)分。
當(dāng)前地址的邏輯大致如下(非完整代碼):
//發(fā)起定位
[self.lManager startUpdatingLocation];
//定位回調(diào)里拿到坐標(biāo)->發(fā)起逆地理編碼
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
...
//構(gòu)造AMapReGeocodeSearchRequest對(duì)象,location為必選項(xiàng),radius為可選項(xiàng)
AMapReGeocodeSearchRequest *regeoRequest = [[AMapReGeocodeSearchRequest alloc] init];
regeoRequest.location = [AMapGeoPoint locationWithLatitude:lItem.coordinate.latitude longitude:lItem.coordinate.longitude];
regeoRequest.radius = 10000;
regeoRequest.requireExtension = YES;
[self.lManager stopUpdatingLocation];
//發(fā)起逆地理編碼
[_search AMapReGoecodeSearch:regeoRequest];
}
//逆地理編碼的回調(diào)函數(shù)->數(shù)據(jù)處理并回調(diào)相應(yīng)VC
- (void)onReGeocodeSearchDone:(AMapReGeocodeSearchRequest *)request response:(AMapReGeocodeSearchResponse *)response
{
//TO DO
}
總結(jié)下來定位當(dāng)前地址的流程:
發(fā)起定位->定位回調(diào)里拿到坐標(biāo)->發(fā)起逆地理編碼-> 逆地理編碼回調(diào)函數(shù)->數(shù)據(jù)處理并回調(diào)相應(yīng)VC
而對(duì)某坐標(biāo)進(jìn)行逆地理編碼驗(yàn)證則也是通過:
發(fā)起逆地理編碼-> 逆地理編碼回調(diào)函數(shù)->數(shù)據(jù)處理并回調(diào)相應(yīng)VC
細(xì)心的同學(xué)已經(jīng)發(fā)現(xiàn)了:兩個(gè)不同的功能都會(huì)走同一個(gè)高德回調(diào)函數(shù)。這樣就有可能造成區(qū)分不清對(duì)應(yīng)關(guān)系的問題。
- 項(xiàng)目初期是在LocationManger里用一個(gè)全局的BOOL值去區(qū)分是來自哪種功能(不高頻率切換使用兩種功能的話,基本能掩蓋這個(gè)問題)。
- 后來QA同事中度暴力測(cè)試發(fā)現(xiàn)了這個(gè)問題,臨上線改變方案:發(fā)起對(duì)某坐標(biāo)逆地理編碼驗(yàn)證功能時(shí)全局保存此坐標(biāo),并在逆地理回調(diào)函數(shù)里用此全局坐標(biāo)作區(qū)分。(相當(dāng)于用坐標(biāo)代替BOOL值去作區(qū)分,準(zhǔn)確度大增)。
- 維持了大概兩個(gè)版本,QA同事重度暴力測(cè)試又發(fā)現(xiàn)了這個(gè)問題,盡管出現(xiàn)概率不高,但還是在leader的指導(dǎo)下,采用了最后的解決方案。如下:
在發(fā)起定位/對(duì)某坐標(biāo)進(jìn)行逆地理編碼驗(yàn)證時(shí)分別給request動(dòng)態(tài)綁定一個(gè)bSearchAddress屬性
//構(gòu)造AMapReGeocodeSearchRequest對(duì)象,location為必選項(xiàng),radius為可選項(xiàng)
AMapReGeocodeSearchRequest *regeoRequest = [[AMapReGeocodeSearchRequest alloc] init];
[regeoRequest setValue:@"0" forKey:ReGeocodeSearchRequestKey];//標(biāo)記為定位模式,回調(diào)里用于區(qū)分
AMapReGeocodeSearchRequest *regeoRequest = [[AMapReGeocodeSearchRequest alloc] init];
[regeoRequest setValue:@"1" forKey:ReGeocodeSearchRequestKey];//標(biāo)記為地址驗(yàn)證模式,回調(diào)里用于區(qū)分
這樣就能在逆地理回調(diào)里進(jìn)行區(qū)分處理:
//實(shí)現(xiàn)逆地理編碼的回調(diào)函數(shù)
- (void)onReGeocodeSearchDone:(AMapReGeocodeSearchRequest *)request response:(AMapReGeocodeSearchResponse *)response
{
if(response.regeocode != nil)
{
NSString *bSearchAddress = [request valueForKey:ReGeocodeSearchRequestKey];
//通過AMapReGeocodeSearchResponse對(duì)象處理搜索結(jié)果
if ([bSearchAddress isEqualToString:@"1"]) {
//地址驗(yàn)證模式
}else {
//定位模式
}
}
}
這里需要提一下,給對(duì)象動(dòng)態(tài)綁定屬性需要用到RunTime+Category:
#import "AMapReGeocodeSearchRequest+bSearchAddress.h"
#import <objc/runtime.h>
@implementation AMapReGeocodeSearchRequest (bSearchAddress)
@dynamic bSearchAddress;
static char str_bSearchAddress;
- (void)setBSearchAddress:(NSString *)bSearchAddress
{
[self willChangeValueForKey:@"bSearchAddress"];
objc_setAssociatedObject(self, &str_bSearchAddress, bSearchAddress, OBJC_ASSOCIATION_RETAIN);
[self didChangeValueForKey:@"bSearchAddress"];
}
- (NSString *)bSearchAddress
{
return objc_getAssociatedObject(self, &str_bSearchAddress);
}
@end
痛點(diǎn)二
同一頁面有兩處需要用到同一個(gè)地址搜索的功能,如何區(qū)分的問題。
在剛開始實(shí)現(xiàn)這個(gè)需求的時(shí)候,我還是很天真的在ViewController中用BOOL值去區(qū)分這兩處的搜索,以期能在LocationManger回調(diào)給頁面的方法里作區(qū)分。這個(gè)問題的解決方案應(yīng)該更豐富一點(diǎn),我最后是這么解決的:
- 在第一處直接調(diào)用LocationManger中封裝好的地址搜索方法:
- (void)startAMapKeyWordsSearchRequest:(AMapPOIKeywordsSearchRequest *)request;//高德地址搜索
- 對(duì)于第二處的處理,這里需要定義一個(gè)requestAddressObject,在requestAddressObject中封裝LocationManger中對(duì)應(yīng)的高德地址搜索方法,成為LocationManger的代理并實(shí)現(xiàn)地址搜索的回調(diào)函數(shù)。
#import <Foundation/Foundation.h>
#import "LocationManger.h"
@protocol RequestAddressObjectDelegate <NSObject>
- (void)requestKeywordsPOISuccess:(AMapPOISearchResponse *)response;
- (void)requestKeywordsPOIfailed:(NSString *)sError;
@end
@interface requestAddressObject : NSObject
<
LocationManagerDelegate
>
@property (weak, nonatomic) id<RequestAddressObjectDelegate> delegate;
@property (weak, nonatomic) LocationManger *locationManager;
@property (strong, nonatomic) AMapPOIKeywordsSearchRequest *requestKeywordsPlaceList;
- (void)requestPOIKeywordSearch:(AMapPOIKeywordsSearchRequest *)request;
- (void)requestPOIWithKeyword:(NSString *)keyword city:(NSString *)city cityLimit:(BOOL)cityLimit;
@end
#import "requestAddressObject.h"
@implementation requestAddressObject
- (LocationManger *)locationManager{
if (!_locationManager) {
_locationManager = [LocationManger getInstanceWithDelegate:self];
}else {
if (_locationManager.delegate != self) {
_locationManager.delegate = self;
}
}
return _locationManager;
}
- (void)requestPOIKeywordSearch:(AMapPOIKeywordsSearchRequest *)request
{
[self.locationManager startAMapKeyWordsSearchRequest:request];
}
- (void)lmGetPOISearchDelegate:(AMapPOISearchResponse *)response
{
if (self.delegate && [self.delegate respondsToSelector:@selector(requestKeywordsPOISuccess:)]) {
[self.delegate requestKeywordsPOISuccess:response];
}
}
- (void)lmGetLocationFaild:(NSString *)sError
{
if (self.delegate && [self.delegate respondsToSelector:@selector(requestKeywordsPOIfailed:)]) {
[self.delegate requestKeywordsPOIfailed:sError];
}
}
@end
- 即用requestAddressObject去發(fā)起第二處的地址搜索請(qǐng)求,在requestAddressObject中實(shí)現(xiàn)LocationManger的回調(diào)函數(shù)并用requestAddressObjectDelegate定義好的方法回調(diào)到VC中進(jìn)行最后的數(shù)據(jù)處理和頁面展示。這樣就達(dá)到了區(qū)分同一頁面兩處用到同一地址搜索方法的效果。
//ViewController中的第二處的處理
//懶加載
- (requestAddressObject *)reqAddressObj
{
if (!_reqAddressObj) {
_reqAddressObj = [[requestAddressObject alloc] init];
_reqAddressObj.delegate = self;
}
return _reqAddressObj;
}
//第二處地址搜索的調(diào)用
[self.reqAddressObj requestPOIKeywordSearch:_requestKeywordsPlaceList];
//LocationManager的回調(diào)方法經(jīng)由RequestAddressObject中轉(zhuǎn)了一次之后回到VC的回調(diào)方法
#pragma mark - RequestAddressObjectDelegate
- (void)requestKeywordsPOISuccess:(AMapPOISearchResponse *)response
{
//第二處地址搜索成功的回調(diào)
}
- (void)requestKeywordsPOIfailed:(NSString *)sError
{
//第二處地址搜索失敗的回調(diào)
}