项目中对定位、地址搜索等功能采用了LocationManager单例模式,关于单例的优点在此就不赘述了,这里主要想分享一下单例模式下LocationManager的两个痛点。
首先简单贴一下单例的实现:
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;
}
当然,更推荐这样的写法:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//TO DO
});
接下来,开始分析痛点:
痛点一
项目中某页面需要同时用到定位当前地址+对某坐标进行逆地理编码验证两个功能,高德回调函数难以区分。
当前地址的逻辑大致如下(非完整代码):
//发起定位
[self.lManager startUpdatingLocation];
//定位回调里拿到坐标->发起逆地理编码
-(void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
...
//构造AMapReGeocodeSearchRequest对象,location为必选项,radius为可选项
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];
//发起逆地理编码
[_search AMapReGoecodeSearch:regeoRequest];
}
//逆地理编码的回调函数->数据处理并回调相应VC
- (void)onReGeocodeSearchDone:(AMapReGeocodeSearchRequest *)request response:(AMapReGeocodeSearchResponse *)response
{
//TO DO
}
总结下来定位当前地址的流程:
发起定位->定位回调里拿到坐标->发起逆地理编码-> 逆地理编码回调函数->数据处理并回调相应VC
而对某坐标进行逆地理编码验证则也是通过:
发起逆地理编码-> 逆地理编码回调函数->数据处理并回调相应VC
细心的同学已经发现了:两个不同的功能都会走同一个高德回调函数。这样就有可能造成区分不清对应关系的问题。
- 项目初期是在LocationManger里用一个全局的BOOL值去区分是来自哪种功能(不高频率切换使用两种功能的话,基本能掩盖这个问题)。
- 后来QA同事中度暴力测试发现了这个问题,临上线改变方案:发起对某坐标逆地理编码验证功能时全局保存此坐标,并在逆地理回调函数里用此全局坐标作区分。(相当于用坐标代替BOOL值去作区分,准确度大增)。
- 维持了大概两个版本,QA同事重度暴力测试又发现了这个问题,尽管出现概率不高,但还是在leader的指导下,采用了最后的解决方案。如下:
在发起定位/对某坐标进行逆地理编码验证时分别给request动态绑定一个bSearchAddress属性
//构造AMapReGeocodeSearchRequest对象,location为必选项,radius为可选项
AMapReGeocodeSearchRequest *regeoRequest = [[AMapReGeocodeSearchRequest alloc] init];
[regeoRequest setValue:@"0" forKey:ReGeocodeSearchRequestKey];//标记为定位模式,回调里用于区分
AMapReGeocodeSearchRequest *regeoRequest = [[AMapReGeocodeSearchRequest alloc] init];
[regeoRequest setValue:@"1" forKey:ReGeocodeSearchRequestKey];//标记为地址验证模式,回调里用于区分
这样就能在逆地理回调里进行区分处理:
//实现逆地理编码的回调函数
- (void)onReGeocodeSearchDone:(AMapReGeocodeSearchRequest *)request response:(AMapReGeocodeSearchResponse *)response
{
if(response.regeocode != nil)
{
NSString *bSearchAddress = [request valueForKey:ReGeocodeSearchRequestKey];
//通过AMapReGeocodeSearchResponse对象处理搜索结果
if ([bSearchAddress isEqualToString:@"1"]) {
//地址验证模式
}else {
//定位模式
}
}
}
这里需要提一下,给对象动态绑定属性需要用到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
痛点二
同一页面有两处需要用到同一个地址搜索的功能,如何区分的问题。
在刚开始实现这个需求的时候,我还是很天真的在ViewController中用BOOL值去区分这两处的搜索,以期能在LocationManger回调给页面的方法里作区分。这个问题的解决方案应该更丰富一点,我最后是这么解决的:
- 在第一处直接调用LocationManger中封装好的地址搜索方法:
- (void)startAMapKeyWordsSearchRequest:(AMapPOIKeywordsSearchRequest *)request;//高德地址搜索
- 对于第二处的处理,这里需要定义一个requestAddressObject,在requestAddressObject中封装LocationManger中对应的高德地址搜索方法,成为LocationManger的代理并实现地址搜索的回调函数。
#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去发起第二处的地址搜索请求,在requestAddressObject中实现LocationManger的回调函数并用requestAddressObjectDelegate定义好的方法回调到VC中进行最后的数据处理和页面展示。这样就达到了区分同一页面两处用到同一地址搜索方法的效果。
//ViewController中的第二处的处理
//懒加载
- (requestAddressObject *)reqAddressObj
{
if (!_reqAddressObj) {
_reqAddressObj = [[requestAddressObject alloc] init];
_reqAddressObj.delegate = self;
}
return _reqAddressObj;
}
//第二处地址搜索的调用
[self.reqAddressObj requestPOIKeywordSearch:_requestKeywordsPlaceList];
//LocationManager的回调方法经由RequestAddressObject中转了一次之后回到VC的回调方法
#pragma mark - RequestAddressObjectDelegate
- (void)requestKeywordsPOISuccess:(AMapPOISearchResponse *)response
{
//第二处地址搜索成功的回调
}
- (void)requestKeywordsPOIfailed:(NSString *)sError
{
//第二处地址搜索失败的回调
}
输入选址
网友评论