一、前言
我們在項目開發中經常會用到第三方的SDK等來實現項目所需要的功能,很多時候拿到別人的東西并不是所有的都適合我們自己的項目,為了方便使用或者擴展更多功能,或者為了對第三方的代碼進行更合理的管理,經常要對他們的代碼和功能進行封裝。以此來達到方便管理,更便于閱讀,功能更適合自己項目等功能,而且有些東西進行一次封裝,多處可以使用,這樣就很方便。
我們以高德地圖搜索類AMapSearchAPI為例子,進行該功能的封裝和在項目中的使用。
二、高德地圖搜索管理類AMapSearchAPI
AMapSearchAPI里提供了各種搜索功能的Api,比如關鍵字查詢Api:
/**
* @brief POI 關鍵字查詢接口
* @param request 查詢選項。具體屬性字段請參考 AMapPOIKeywordsSearchRequest 類。
*/
- (void)AMapPOIKeywordsSearch:(AMapPOIKeywordsSearchRequest *)request;
還有各種周邊查詢、逆地理編碼查詢等等,這些Api都有各自的代理回調,比如關鍵字檢索回調代理:
/**
* @brief POI查詢回調函數
* @param request 發起的請求,具體字段參考 AMapPOISearchBaseRequest 及其子類。
* @param response 響應結果,具體字段參考 AMapPOISearchResponse 。
*/
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response;
提供了各種搜索功能和回調代理。
三、功能實現
這里考慮到項目中經常使用,所以創建一個單例作為項目中高德地圖搜索管理類。
1、實現代碼
DDSearchManager.h
#import <Foundation/Foundation.h>
typedef void(^keyWordSearchBlock)(NSMutableArray *pointAnnotaions);//用于關鍵字檢索回調數據
typedef void(^tipsSearchBlock)(NSArray *tips);//用于tip搜索回調數據
@interface DDSearchManager : NSObject
+ (instancetype)sharedManager;
/* 2比1查詢速度快;1的數據量比2大 。*/
/*
1、關鍵字檢索
keyword為檢索關鍵字;city可為空;block返回檢索完后的數組,數組中是MAPointAnnotation的對象
注意:關鍵字未設置城市信息(默認為全國搜索)時,如果涉及多個城市數據返回,僅會返回建議城市,請根據APP需求,選取城市進行搜索。
*/
- (void)keyWordsSearch:(NSString *)keyword city:(NSString *)city returnBlock:(keyWordSearchBlock)block;
/*
2、輸入提示查詢
block回調查詢后的數組,該數組里是AMapTip對象
*/
- (void)inputTipsSearch:(NSString *)tips city:(NSString *)city returnBlock:(tipsSearchBlock)block;
@end
DDSearchManager.m
#import "DDSearchManager.h"
#import <AMapSearchKit/AMapSearchKit.h>
#import <MAMapKit/MAMapKit.h>
@interface DDSearchManager ()<AMapSearchDelegate>
//高德地圖搜索管理類
@property (nonatomic, strong) AMapSearchAPI *searchAPI;
@property (nonatomic, copy) keyWordSearchBlock keyWordSearchBlock;
@property (nonatomic, copy) tipsSearchBlock tipSearchBlock;
@end
@implementation DDSearchManager
+ (instancetype)sharedManager {
static DDSearchManager *manager = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
manager = [[self alloc]init];
});
return manager;
}
/// 關鍵字查詢
- (void)keyWordsSearch:(NSString *)keyword city:(NSString *)city returnBlock:(keyWordSearchBlock)block
{
if (keyword.length) {
self.keyWordSearchBlock = block;
AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc]init];
request.keywords = keyword;
if (city.length) {
request.city = city;
/* 搜索SDK 3.2.0 中新增加的功能,只搜索本城市的POI。*/
request.cityLimit = YES;
}
/*返回擴展信息*/
request.requireExtension = YES;
request.requireSubPOIs = YES;
/*發起關鍵字搜索*/
[self.searchAPI AMapPOIKeywordsSearch:request];
}
}
/// 輸入提示查詢
- (void)inputTipsSearch:(NSString *)tips city:(NSString *)city returnBlock:(tipsSearchBlock)block {
if (tips.length) {
self.tipSearchBlock = block;
AMapInputTipsSearchRequest *request = [[AMapInputTipsSearchRequest alloc]init];
request.keywords = tips;
if (city.length) {
request.city = city;
request.cityLimit = YES;
}
/*發起關鍵字搜索*/
[self.searchAPI AMapInputTipsSearch:request];
}
}
#pragma mark delegate
#pragma AMapSearchDelegate
//關鍵字查詢代理回調
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response
{
if (response.pois.count == 0) {
return;
}
NSMutableArray *poiAnnitations = [[NSMutableArray alloc]init];
[response.pois enumerateObjectsUsingBlock:^(AMapPOI * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
MAPointAnnotation *annotation = [[MAPointAnnotation alloc] init];
[annotation setCoordinate:CLLocationCoordinate2DMake(obj.location.latitude, obj.location.longitude)];
[annotation setTitle:obj.name];
[annotation setSubtitle:obj.address];
[poiAnnitations addObject:annotation];
}];
//回調檢索結果,回調數組是MAPointAnnotation的對象集合
if (self.keyWordSearchBlock) {
self.keyWordSearchBlock (poiAnnitations);
}
}
//輸入提示查詢代理回調
-(void)onInputTipsSearchDone:(AMapInputTipsSearchRequest *)request response:(AMapInputTipsSearchResponse *)response
{
if (self.tipSearchBlock)
{
NSMutableArray *arr = [NSMutableArray array];
for (int i=0; i<response.tips.count; i++)
{
//輸入提示類
AMapTip *tip = [response.tips objectAtIndex:i];
if (tip.location.latitude!=0 && ![tip.uid isEqualToString:@""])
{
[arr addObject:tip];
}
}
//回調tip檢索結果,回調數組中是AMapTip對象的集合。
self.tipSearchBlock (arr);
}
}
#pragma mark set/get
- (AMapSearchAPI *)searchAPI {
if (!_searchAPI) {
_searchAPI = [[AMapSearchAPI alloc]init];
_searchAPI.delegate = self;
}
return _searchAPI;
}
@end
2、調用
這里隨便截取了一段示例代碼,發起tip檢索,然后回調數據,更新數據源,然后刷新tableView的列表功能。
[[DDSearchManager searchManager] inputTipsSearch:text city:self.selectCityBtn.titleLabel.text returnBlock:^(NSArray *tips) {
[_searchArr removeAllObjects];
[_searchArr addObjectsFromArray:tips]];
[_tableView reloadData];
}];
這里有一個問題就是刷新tableView的時候如果給cell賦值,你得知道回調數組里面是什么類型的數據,不然不知道怎么賦值。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellId = kCellReuseIdentifier;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellReuseIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellReuseIdentifier];
}
AMapPOI *poi = self.dataSource[indexPath.row];
NSLog(@"poi.name :%@ poi.address:%@ ",poi.name,poi.address);
cell.textLabel.text = poi.name;
cell.detailTextLabel.text = poi.address;
return cell;
}
四、問題思考
以上一個高德地圖的搜索管理類就簡單的完成了,但是就這樣能算是封裝嗎?我覺得不能算完全封裝,原因有如下:
1、結構都通過數據回調,使用的人怎么會知道數組里是什么呢?
2、封裝的話一般不應該將原來的東西暴露在外,這里雖然DDSearchManager.h沒有暴露有關于高德地圖SDK,AMapSearchAPI等,但是在使用的時候,還得用高德SDK中的類AMapTip和MAPointAnnotation,這樣的封裝不夠徹底。
所以以上封裝還算不到封裝,如果算也只是個半成品,不夠徹底。所以我們需要繼續優化,繼續封裝。
五、繼續封裝
1、創建一個定義搜索結果的基礎數據類型的類DDSearchObj
DDSearchObj.h
//
// DDSearchObj.h
// GDAddressSelected
// 該文件定義了搜索結果的基礎數據類型。
//
#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
@interface DDSearchObj : NSObject
@end
///輸入提示
@interface DDSearchTip : DDSearchObj
///名稱
@property (nonatomic, copy) NSString *name;
///區域編碼
@property (nonatomic, copy) NSString *adcode;
///所屬區域
@property (nonatomic, copy) NSString *district;
///地址
@property (nonatomic, copy) NSString *address;
///經緯度
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
@end
///點標注數據
@interface DDSearchPointAnnotation : DDSearchObj
///經緯度
@property (nonatomic, assign) CLLocationCoordinate2D coordinate;
///標題
@property (nonatomic, copy) NSString *title;
///副標題
@property (nonatomic, copy) NSString *subtitle;
@end
這個類里面有DDSearchTip和DDSearchPointAnnotation,這分別是tip搜索回調數據類型的類和關鍵字搜索回調數據的類,這兩個類有各自的屬性,視項目情況自己添加更多屬性等。
DDSearchObj.m
#import "DDSearchObj.h"
@implementation DDSearchObj
@end
@implementation DDSearchPointAnnotation
@end
@implementation DDSearchTip
@end
2、優化DDSearchManager類
DDSearchManager.h
#import <Foundation/Foundation.h>
#import "DDSearchObj.h"
//關鍵字搜索數據回調block
typedef void(^keyWordSearchBlock)(NSArray <__kindof DDSearchPointAnnotation*> *pointAnnotaions);
//tip搜索數據回調block
typedef void(^tipsSearchBlock)(NSArray <__kindof DDSearchTip*> *tips);
@interface DDSearchManager : NSObject
+ (instancetype)sharedManager;
/* 2比1查詢速度快;1的數據量比2大 。*/
/*
1、關鍵字檢索
keyword為檢索關鍵字;city可為空;block返回檢索完后的數組,數組中是MAPointAnnotation的對象
注意:關鍵字未設置城市信息(默認為全國搜索)時,如果涉及多個城市數據返回,僅會返回建議城市,請根據APP需求,選取城市進行搜索。
*/
- (void)keyWordsSearch:(NSString *)keyword city:(NSString *)city returnBlock:(keyWordSearchBlock)block;
/*
2、輸入提示查詢
block回調查詢后的數組,該數組里是AMapTip對象
*/
- (void)inputTipsSearch:(NSString *)tips city:(NSString *)city returnBlock:(tipsSearchBlock)block;
@end
引入DDSearchObj.h頭文件,將數據回調的block用<__kindof DDSearchPointAnnotation*>修飾,表示該數組里面只能是DDSearchPointAnnotation類型的數據,這樣的話,當使用該類的人用此方法的時候block回調里面就會知道,這個回調數據是DDSearchPointAnnotation的數組,而不是其他什么東西的數據。
DDSearchManager.m
#import "DDSearchManager.h"
#import <AMapSearchKit/AMapSearchKit.h>
#import <MAMapKit/MAMapKit.h>
@interface DDSearchManager ()<AMapSearchDelegate>
@property (nonatomic, strong) AMapSearchAPI *searchAPI;
@property (nonatomic, copy) keyWordSearchBlock keyWordSearchBlock;
@property (nonatomic, copy) tipsSearchBlock tipSearchBlock;
@end
@implementation DDSearchManager
+ (instancetype)sharedManager {
static DDSearchManager *manager = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
manager = [[self alloc]init];
});
return manager;
}
/// 關鍵字查詢
- (void)keyWordsSearch:(NSString *)keyword city:(NSString *)city returnBlock:(keyWordSearchBlock)block
{
if (keyword.length) {
self.keyWordSearchBlock = block;
AMapPOIKeywordsSearchRequest *request = [[AMapPOIKeywordsSearchRequest alloc]init];
request.keywords = keyword;
if (city.length) {
request.city = city;
/* 搜索SDK 3.2.0 中新增加的功能,只搜索本城市的POI。*/
request.cityLimit = YES;
}
/*返回擴展信息*/
request.requireExtension = YES;
request.requireSubPOIs = YES;
/*發起關鍵字搜索*/
[self.searchAPI AMapPOIKeywordsSearch:request];
}
}
/// 輸入提示查詢
- (void)inputTipsSearch:(NSString *)tips city:(NSString *)city returnBlock:(tipsSearchBlock)block {
if (tips.length) {
self.tipSearchBlock = block;
AMapInputTipsSearchRequest *request = [[AMapInputTipsSearchRequest alloc]init];
request.keywords = tips;
if (city.length) {
request.city = city;
request.cityLimit = YES;
}
/*發起關鍵字搜索*/
[self.searchAPI AMapInputTipsSearch:request];
}
}
#pragma mark delegate
#pragma AMapSearchDelegate
//關鍵字查詢代理回調
- (void)onPOISearchDone:(AMapPOISearchBaseRequest *)request response:(AMapPOISearchResponse *)response
{
if (response.pois.count == 0) {
return;
}
NSMutableArray *poiAnnitations = [[NSMutableArray alloc]init];
[response.pois enumerateObjectsUsingBlock:^(AMapPOI * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// MAPointAnnotation *annotation = [[MAPointAnnotation alloc] init];
// [annotation setCoordinate:CLLocationCoordinate2DMake(obj.location.latitude, obj.location.longitude)];
// [annotation setTitle:obj.name];
// [annotation setSubtitle:obj.address];
DDSearchPointAnnotation *annotation = [[DDSearchPointAnnotation alloc] init];
[annotation setCoordinate:CLLocationCoordinate2DMake(obj.location.latitude, obj.location.longitude)];
[annotation setTitle:obj.name];
[annotation setSubtitle:obj.address];
[poiAnnitations addObject:annotation];
}];
if (self.keyWordSearchBlock) {
self.keyWordSearchBlock (poiAnnitations);
}
}
//輸入提示查詢代理回調
-(void)onInputTipsSearchDone:(AMapInputTipsSearchRequest *)request response:(AMapInputTipsSearchResponse *)response
{
if (self.tipSearchBlock)
{
NSMutableArray *arr = [NSMutableArray array];
for (int i=0; i<response.tips.count; i++)
{
AMapTip *tip = [response.tips objectAtIndex:i];
// if (tip.location.latitude!=0 && ![tip.uid isEqualToString:@""])
// {
// [arr addObject:tip];
// }
if (tip.location.latitude!=0 && ![tip.uid isEqualToString:@""])
{
DDSearchTip *ddTip = [[DDSearchTip alloc] init];
ddTip.name = tip.name;
ddTip.adcode = tip.adcode;
ddTip.district = tip.name;
ddTip.address = tip.address;
ddTip.coordinate = CLLocationCoordinate2DMake(tip.location.latitude, tip.location.longitude);
[arr addObject:tip];
}
}
self.tipSearchBlock (arr);
}
}
#pragma mark set/get
- (AMapSearchAPI *)searchAPI {
if (!_searchAPI) {
_searchAPI = [[AMapSearchAPI alloc]init];
_searchAPI.delegate = self;
}
return _searchAPI;
}
@end
這里我將原來的寫法注釋掉,稍作了變動。將高德地圖的數據轉化成我們自己的數據存儲,并回調。這樣的話,在外部使用的時候,用的人不用關心高德地圖類里面的實現過程,中需要知道當發起檢索時回調,回調的數據在相應的地方去拿數據就好了。
3、發起檢索
[[DDSearchManager sharedManager] keyWordsSearch:@"北京大學" city:@"北京" returnBlock:^(NSArray<__kindof DDSearchPointAnnotation *> *pointAnnotaions) {
for (DDSearchPointAnnotation *annotation in pointAnnotaions)
{
NSLog(@"%@,%@,%f,%f",annotation.title,annotation.subtitle,annotation.coordinate.latitude,annotation.coordinate.longitude);
}
}];
這里當我們調用該方法進行關鍵字檢索的時候,在block回調中就能知道回調的數據是DDSearchPointAnnotation對象的數組,不用關心高德地圖內部檢索的結果是什么,只需要進入DDSearchPointAnnotation類里,就可以知道回調的數據你需要取哪些值。
這樣避免了高德SDK類的外部暴露出來,這就簡單的封裝了一個高德地圖檢索的管理類,這里是用block進行數據回調的,當然小伙伴們也可以用代理的方式。
六、總結
當我們封裝一個第三方的功能的時候,封裝使得它能夠更好的服務于我們自己的項目。
如果要封裝盡量封裝的徹底一點,盡量不要暴露第三方的東西,你的封裝類依賴于三方,實現的時候還依賴于三方類,這樣的封裝總感覺不是很完美。
當然有的小伙伴說了不進行上面的優化封裝部分也行,也能用,就看個人怎么理解了。