目的:
将第三方地图隔离,与项目代码解耦和,实现项目内各地图(百度、高德等)的快捷切换。
思路:
(1)集成百度地图。
(2)实现百度地图集成设计。
(3)实现百度地图工厂设计。
(4)实现工厂管理。
(5)配置文件实现一键切换
1、集成百度地图
查看文档配置SDK(此处省略),调用方法如下:
// 创建百度地图
BMKMapView *mapView = [[BMKMapView alloc] initWithFrame:self.view.frame];
// 添加绑定
[self.view addSubview:mapView];
问题分析:切换地图时,所有用到百度地图的模块都需要修改。耦合度高,不易修改。
2、实现百度地图集成设计
分析百度地图和高德地图的API:所有地图SDK-MapView都是UIView子类。定义MapView标准(协议)。 实现百度地图集成封装
(1)定义地图协议:
#import <UIKit/UIKit.h>
// 地图协议(标准)
@protocol IMapView <NSObject>
// 具体的标准
// 返回地图MapView
- (UIView *)getView;
// 初始化需要指定地图的大小
- (instancetype)initWithFrame:(CGRect)frame;
@end
(2)百度地图类实现(1)中的协议方法
#import "BaiduMapView.h"
#import <BaiduMapAPI_Base/BMKBaseComponent.h>//引入base相关所有的头文件
#import <BaiduMapAPI_Map/BMKMapComponent.h>//引入地图功能所有的头文件
@interface BaiduMapView ()
@property (nonatomic) BMKMapView *mapView;
@end
@implementation BaiduMapView
// 初始化地图
- (instancetype)initWithFrame:(CGRect)frame {
self = [super init];
if (self) {
// 初始化百度地图
_mapView = [[BMKMapView alloc] initWithFrame:frame];
}
return self;
}
// 返回地图
- (UIView *)getView {
return _mapView;
}
@end
高德等地图封装同上
以上封装后的调用方法:
id<IMapView> mapView = [[BaiduMapView alloc] initWithFrame:self.view.frame]; // id<IMapView> mapView = [[GaodeMapView alloc] initWithFrame:self.view.frame];
[self.view addSubview:[mapView getView]];
3、实现百度地图工厂设计
创建百度地图使用工厂模式进一步封装,调用者无需关心是哪一个地图
创建百度工厂类,创建方法返回协议实现类实例(.h文件声明方法):
@implementation BaiduMapFactory
- (id<IMapView>)getMapView:(CGRect)frame {
// 返回具体的实现类
return [[BaiduMapView alloc] initWithFrame:frame];
}
@end
高德等地图工厂方法封装同上
调用方法:
// 创建工厂 // id<IMapFactory> factory = [[GaodeMapFactory alloc] init];
id<IMapFactory> factory = [[BaiduMapFactory alloc] init];
// 创建MapView
id<IMapView> mapView = [factory getMapView:self.view.frame];
// 绑定
[self.view addSubview:[mapView getView]];
4、实现工厂管理
对不同地图的工厂方法进行封装,定义规范(使用协议实现)
创建工厂方法的协议:
#import <Foundation/Foundation.h>
#import "IMapView.h"
@protocol IMapFactory <NSObject>
// 创建地图的规范
- (id<IMapView>)getMapView:(CGRect)frame;
// 创建定位的规范 。。。
// 创建导航的规范 。。。
@end
修改百度工厂方法(将方法改为实现协议的方法):
#import <Foundation/Foundation.h>
#import "IMapFactory.h"
// 百度工厂,遵循工厂标准
@interface BaiduMapFactory : NSObject<IMapFactory>
@end
#import "BaiduMapFactory.h"
#import "BaiduMapView.h"
@implementation BaiduMapFactory
- (id<IMapView>)getMapView:(CGRect)frame {
// 返回具体的实现类
return [[BaiduMapView alloc] initWithFrame:frame];
}
@end
调用方法:
// 创建工厂
id<IMapFactory> factory = [[BaiduMapFactory alloc] init];
// 创建MapView
id<IMapView> mapView = [factory getMapView:self.view.frame];
// 绑定
[self.view addSubview:[mapView getView]];
5、配置文件实现一键切换
创建地图引擎工厂(管理多个工厂:工厂嵌套)
#import <Foundation/Foundation.h>
#import "IMapFactory.h"
// 地图引擎类
@interface MapEngine : NSObject
// 定义规范
- (id<IMapFactory>)getFactory;
@end
#import "MapEngine.h"
#import "BaiduMapFactory.h"
#import "PlatformXmlParser.h"
#import "Platform.h"
@implementation MapEngine {
id<IMapFactory> _fac;
}
- (void)initMap{
PlatformXmlParser* parser = [[PlatformXmlParser alloc] init];
NSMutableArray *array = [parser parser];
//动态创建实例对象(Runtime动态创建)
for (Platform *model in array) {
if ([model.isOpen isEqualToString:@"YES"]) {
Class c = NSClassFromString(model.factoryName);
_fac = [[c alloc] init];
}
}
}
- (id<IMapFactory>)getFactory {
[self initMap];
// 返回具体工厂
return _fac;
}
@end
此步骤中还有一些类和配置文件需要创建:
xml文件:配置需要加载的地图种类
<?xml version="1.0" encoding="UTF-8" ?>
<!--如何定义?-->
<!--map标签:表示地图-->
<map>
<!--platform标签:表示平台(例如:百度SDK平台、高德SDK、GoogleSDK等等...)-->
<!--appKey标签:表示注册Key)-->
<!--factoryName标签:表示地图平台封装具体工厂类)-->
<!--isOpen标签:表示是否开启当前这个地图(开关))-->
<platform id="1" appKey="otAWxmzvBUVU8RQOaUll73YZowNpiRed" factoryName="BaiduMapFactory" isOpen="YES"/>
<platform id="2" appKey="7525205809e3cb826ee1718e3adf440a" factoryName="GaodeMapFactory" isOpen="NO"/>
</map>
解析XML文件的模型类:
#import <Foundation/Foundation.h>
@interface Platform : NSObject
@property (nonatomic,strong) NSString* mapId;
@property (nonatomic,strong) NSString* appKey;
@property (nonatomic,strong) NSString* factoryName;
@property (nonatomic,strong) NSString* isOpen;
@end
解析XML文件的方法类:
#import "PlatformXmlParser.h"
#include "Platform.h"
//解析工厂
@interface PlatformXmlParser()<NSXMLParserDelegate>
@property (nonatomic) NSMutableArray* array;
@end
@implementation PlatformXmlParser
- (instancetype)init
{
self = [super init];
if (self) {
_array = [[NSMutableArray alloc] init];
}
return self;
}
/** 解析方法,返回数组*/
-(NSMutableArray*)parser{
//加载XML配置文件
//绑定delegate
NSString* filePath = [[NSBundle mainBundle] pathForResource:@"Config" ofType:@"map.xml"];
NSURL* url = [[NSURL alloc] initFileURLWithPath:filePath];
NSXMLParser* xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
xmlParser.delegate = self;
//解析
[xmlParser parse];
return _array;
}
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(nullable NSString *)namespaceURI qualifiedName:(nullable NSString *)qName attributes:(NSDictionary<NSString *, NSString *> *)attributeDict{
//解析xml
if([elementName isEqualToString:@"platform"]){
NSString* mapId = attributeDict[@"id"];
NSString* appKey = attributeDict[@"appKey"];
NSString* factoryName = attributeDict[@"factoryName"];
NSString* isOpen = attributeDict[@"isOpen"];
Platform* platform = [[Platform alloc] init];
platform.mapId = mapId;
platform.appKey = appKey;
platform.factoryName =factoryName;
platform.isOpen = isOpen;
//保存
[_array addObject:platform];
}
}
@end
此时再看 MapEngine 类,通过解析的配置文件,判断后加载不同的地图SDK,实现在配置文件中地图的一键切换。
地图创建使用的最终调用方法:
// 地图引擎(只关心功能和API即可,解耦和)
MapEngine *engine = [[MapEngine alloc] init];
id<IMapFactory> factory = [engine getFactory];
// 创建MapView
id<IMapView> mapView = [factory getMapView:self.view.frame];
//绑定
[self.view addSubview:[mapView getView]];
总结:
该设计可以在不关系API的情况下,仅通过xml配置文件动态加载不同的地图。
这是一种面向协议的编程思想。用协议定义标准(接口)适合多人开发,规范开发。
同时,该设计也有可读性差的缺点。需要有一定开发经验方可架构实现。