LocationManager定位、地址搜索相关痛点解决(Base on高德)

地图选址

项目中对定位、地址搜索等功能采用了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
{
    //第二处地址搜索失败的回调
}

输入选址
小结:两个小痛点都是如何对数据进行严格的区分问题。第一个是通过对request进行动态绑定属性来进行区分;第二个是通过用不同的请求方式(VC直接发起请求、间接通过object发起请求)来回避数据区分的问题。两个问题刚好是从不同的角度去解决问题,一个正面交锋;一个迂回战术。方案本无优劣,具体问题具体分析才是王道!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 175,528评论 25 709
  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 14,216评论 4 61
  • 雨殁空阶 断青丝成乱香销疾风 裹一枝霜残春晖秋月 轮回重楼巅看花零泪 不共楚王一言 箜篌声声 只为镜中笑颜倾杯独步...
    梦饮千樽月阅读 3,644评论 0 48
  • 看慕芝评: 看标题还以为是撩妹高手 点进来一看 wtf…这还要你说? 周一,告诉我一个让自己打满鸡血的办法。。
    Graceland阅读 1,326评论 1 2
  • 亲子日记第八天,自从报名参加女儿的六一亲子广场舞我就开始愁,因为从来没跳过,看了视频后觉得不难,可是跳了几下...
    AA稳稳阅读 1,475评论 0 0