作者:HDB_Li
鏈接:https://juejin.im/post/5dd7553b51882573530949fe
前言
App中越來越多的功能依賴用戶實(shí)際的位置囊卜,例如基于用戶位置提供推薦數(shù)據(jù)厨钻、基于定位判斷某些功能是否可用,但是在開發(fā)調(diào)試中XCode卻沒有提供自定義的模擬定位的功能激蹲,所以本文主要的目的是現(xiàn)實(shí)一個(gè)可以在開發(fā)調(diào)試過程中隨時(shí)模擬定位的功能扫沼。
PS: 當(dāng)你的app在脫離了XCode的調(diào)試測(cè)試階段崇呵,仍想修改定位的話缤剧。
思路
我們?cè)趇OS的app開發(fā)中通常采用的是CLLocationManager
來獲取用戶當(dāng)前的位置,當(dāng)然也可以采用MKMapView
的showUserLocation
來獲取用戶的位置域慷,所以我們分別針對(duì)這兩種情況分析荒辕。
CLLocationManager
采用CLLocationManager
獲取定位時(shí),是根據(jù)CLLocationManagerDelegate
中- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
的回調(diào)來獲取到定位的犹褒。我們只需要在系統(tǒng)回調(diào)這個(gè)方法傳遞給業(yè)務(wù)代碼的中間抵窒,插入一部分代碼,來修改locations參數(shù)叠骑。原本的邏輯為系統(tǒng)回調(diào)
->業(yè)務(wù)代碼
李皇,現(xiàn)在變?yōu)?code>系統(tǒng)回調(diào)->模擬定位模塊
->業(yè)務(wù)代碼
,就實(shí)現(xiàn)了無侵入式的實(shí)現(xiàn)模擬定位功能宙枷。為了實(shí)現(xiàn)這個(gè)邏輯掉房,可以有以下幾個(gè)思路。
1慰丛、 Runtime swizzle
因?yàn)?code>業(yè)務(wù)代碼是根據(jù)- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations
方法來接受回調(diào)的卓囚,所以可以采用Runtime swizzle
這個(gè)方法,來實(shí)現(xiàn)模擬定位的功能璧帝,但是我們中間組件是不知道業(yè)務(wù)代碼
中具體是哪個(gè)類捍岳,所以無法具體指定runtime swizzle
哪個(gè)類,所以只能遍歷所有的類睬隶,判斷當(dāng)前類的方法列表中是否有locationManager:didUpdateLocations:
這個(gè)方法,如果存在則swizzle
页徐。
- 優(yōu)點(diǎn):便于理解苏潜。
- 缺點(diǎn):需要遍歷所有的類和類的方法列表。
2变勇、中間代理對(duì)象
這種思路是Swizzle了CLLocationManager
的setDelegate:
方法恤左,當(dāng)調(diào)用setDelegate
時(shí),將真實(shí)的delegate object
保存下來搀绣,再將我們定義的中間代理類swizzle delegate
對(duì)象設(shè)置為CLLocationManager
的delegate
飞袋,這樣當(dāng)系統(tǒng)回調(diào)CLLocationManagerDelegate
,會(huì)先回調(diào)到中間代理類swizzle delegate
中链患,再由swizzle delegate
將事件傳遞到真實(shí)的delegate object
巧鸭。
- 優(yōu)點(diǎn):相對(duì)于第一種方法,不需要遍歷類和類的方法列表麻捻,只需
swizzle
CLLocationManager
中的setDelegate:
方法即可纲仍。 - 缺點(diǎn):在中間代理類
swizzle delegate
中需要實(shí)現(xiàn)全部的CLLocationManagerDelegate
方法呀袱,如果后續(xù)增加代理方法,仍需要修改這個(gè)類郑叠。
3夜赵、采用NSProxy實(shí)現(xiàn)中間代理對(duì)象
Objective-C中有2個(gè)基類,常用的就是NSObject
乡革,另一個(gè)就是NSProxy
寇僧,NSProxy
主要用于消息轉(zhuǎn)發(fā)處理,所以采用NSProxy
我們可以更好的處理方法二中的缺點(diǎn)沸版。
創(chuàng)建一個(gè)新的類MockLocationProxy
嘁傀,集成自NSProxy
。
// MockLocationProxy.h
#import <CoreLocation/CoreLocation.h>
@interface MockLocationProxy : NSProxy
@property (nonatomic, weak, readonly, nullable) id <CLLocationManagerDelegate> target;
- (instancetype)initWithTarget:(id <CLLocationManagerDelegate>)target;
@end
復(fù)制代碼
// MockLocationProxy.m
#import "MockLocationProxy.h"
@implementation MockLocationProxy
- (instancetype)initWithTarget:(id<CLLocationManagerDelegate>)target {
_target = target;
return self;
}
@end
復(fù)制代碼
接著就來處理消息轉(zhuǎn)發(fā)的邏輯推穷,首先我們要知道我們想要的是什么效果心包,系統(tǒng)回調(diào)給MockLocationProxy
,MockLocationProxy
只處理locationManager:didUpdateLocations:
馒铃,其他的消息都仍然交給原target蟹腾。
所以我們?cè)?code>MockLocationProxy.m中添加以下方法:
// MockLocationProxy.m
@implementation MockLocationProxy
- (instancetype)initWithTarget:(id<CLLocationManagerDelegate>)target {
_target = target;
return self;
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if (aSelector == @selector(locationManager:didUpdateLocations:)) {
return YES;
}
return [self.target respondsToSelector:aSelector];
}
- (void)forwardInvocation:(NSInvocation *)invocation {
SEL sel = invocation.selector;
if ([self.target respondsToSelector:sel]) {
[invocation invokeWithTarget:self.target];
}
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel {
return [self.target methodSignatureForSelector:sel];
}
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations {
if ([self.target respondsToSelector:_cmd]) {
// 模擬定位代碼
CLLocation *mockLocation = [[CLLocation alloc] initWithLatitude:39.908722 longitude:116.397499];
locations = @[mockLocation];
[self.target locationManager:manager didUpdateLocations:locations];
}
}
@end
復(fù)制代碼
當(dāng)消息發(fā)送給MockLocationProxy
時(shí),判斷當(dāng)前方法是否是locationManager:didUpdateLocations:
区宇,如果是娃殖,則MockLocationProxy
響應(yīng)事件,否則直接傳遞給原本的target议谷。到此已經(jīng)可以隨時(shí)處理模擬定位炉爆。你只需要在模擬定位的代碼做一些處理,就可以隨時(shí)修改定位卧晓。
One more.
上述方法雖然可以模擬定位芬首,但是每次修改模擬值都需重新build,那么有沒有辦法在運(yùn)行時(shí)隨時(shí)修改這個(gè)值呢逼裆?
LLDebugTool
當(dāng)然可以郁稍,你只需要在你的項(xiàng)目中集成LLDebugTool
,調(diào)用其中的Location模塊胜宇,LLDebugTool
提供了一個(gè)UI來隨時(shí)修改這個(gè)模擬值耀怜,讓你在調(diào)試時(shí),隨時(shí)模擬定位桐愉,LLDebugTool
仍提供了很多其他的功能财破,如果你只需要模擬定位的功能,則只需要集成LLDebugTool/Location
這個(gè)subspec就可以了从诲。