iOS實現模擬定位功能

前言

App中愈來愈多的功能依賴用戶實際的位置,例如基於用戶位置提供推薦數據、基於定位判斷某些功能是否可用,可是在開發調試中XCode卻沒有提供自定義的模擬定位的功能,因此本文主要的目的是現實一個能夠在開發調試過程當中隨時模擬定位的功能。git

思路

咱們在iOS的app開發中一般採用的是CLLocationManager來獲取用戶當前的位置,固然也能夠採用MKMapViewshowUserLocation來獲取用戶的位置,因此咱們分別針對這兩種狀況分析。github

CLLocationManager

採用CLLocationManager獲取定位時,是根據CLLocationManagerDelegate中`- (void)locationManager:(CLLocationManager *)managerapp

didUpdateLocations:(NSArray<CLLocation *> *)locations`的回調來獲取到定位的。咱們只須要在系統回調這個方法傳遞給業務代碼的中間,插入一部分代碼,來修改locations參數。本來的邏輯爲`系統回調`->`業務代碼`,如今變爲`系統回調`->`模擬定位模塊`->`業務代碼`,就實現了無侵入式的實現模擬定位功能。爲了實現這個邏輯,能夠有如下幾個思路。

一、 Runtime swizzle

由於業務代碼是根據`- (void)locationManager:(CLLocationManager *)managerui

didUpdateLocations:(NSArray<CLLocation *> *)locations`方法來接受回調的,因此能夠採用`Runtime swizzle`這個方法,來實現模擬定位的功能,可是咱們中間組件是不知道`業務代碼`中具體是哪一個類,因此沒法具體指定`runtime swizzle`哪一個類,因此只能遍歷全部的類,判斷當前類的方法列表中是否有`locationManager:didUpdateLocations:`這個方法,若是存在則`swizzle`。
  • 優勢:便於理解。
  • 缺點:須要遍歷全部的類和類的方法列表。

二、中間代理對象

這種思路是Swizzle了CLLocationManagersetDelegate:方法,當調用setDelegate時,將真實的delegate object保存下來,再將咱們定義的中間代理類swizzle delegate對象設置爲CLLocationManagerdelegate,這樣當系統回調CLLocationManagerDelegate,會先回調到中間代理類swizzle delegate中,再由swizzle delegate將事件傳遞到真實的delegate objectatom

  • 優勢:相對於第一種方法,不須要遍歷類和類的方法列表,只需swizzle CLLocationManager中的setDelegate:方法便可。
  • 缺點:在中間代理類swizzle delegate中須要實現所有的CLLocationManagerDelegate方法,若是後續增長代理方法,仍須要修改這個類。

三、採用NSProxy實現中間代理對象

Objective-C中有2個基類,經常使用的就是NSObject,另外一個就是NSProxyNSProxy主要用於消息轉發處理,因此採用NSProxy咱們能夠更好的處理方法二中的缺點。代理

3.1

建立一個新的類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
// MockLocationProxy.m
#import "MockLocationProxy.h"

@implementation MockLocationProxy

- (instancetype)initWithTarget:(id<CLLocationManagerDelegate>)target {
    _target = target;
    return self;
}

@end

接着就來處理消息轉發的邏輯,首先咱們要知道咱們想要的是什麼效果,系統回調給MockLocationProxyMockLocationProxy只處理locationManager:didUpdateLocations:,其餘的消息都仍然交給原target。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

當消息發送給MockLocationProxy時,判斷當前方法是不是locationManager:didUpdateLocations:,若是是,則MockLocationProxy響應事件,不然直接傳遞給本來的target。到此已經能夠隨時處理模擬定位。你只須要在模擬定位的代碼作一些處理,就能夠隨時修改定位。事件

One more.

上述方法雖然能夠模擬定位,可是每次修改模擬值都需從新build,那麼有沒有辦法在運行時隨時修改這個值呢?

固然能夠,你只須要在你的項目中集成LLDebugTool,調用其中的Location模塊,LLDebugTool提供了一個UI來隨時修改這個模擬值,讓你在調試時,隨時模擬定位,LLDebugTool仍提供了不少其餘的功能,若是你只須要模擬定位的功能,則只須要集成LLDebugTool/Location這個subspec就能夠了。

後記

前言說過,定位除了CLLocationManager以外,MKMapViewshowUserLocation也能夠獲取定位信息,那麼如何解決這個問題呢? 你能夠在LLDebugTool/Location中查看答案。

不要忘記點Star! ✨✨✨

相關文章
相關標籤/搜索