1. 程式人生 > 程式設計 >iOS實現模擬定位功能的示例程式碼

iOS實現模擬定位功能的示例程式碼

前言

App中越來越多的功能依賴使用者實際的位置,例如基於使用者位置提供推薦資料、基於定位判斷某些功能是否可用,但是在開發除錯中XCode卻沒有提供自定義的模擬定位的功能,所以本文主要的目的是現實一個可以在開發除錯過程中隨時模擬定位的功能。

思路

我們在iOS的app開發中通常採用的是CLLocationManager來獲取使用者當前的位置,當然也可以採用MKMapView的showUserLocation來獲取使用者的位置,所以我們分別針對這兩種情況分析。

CLLocationManager

採用CLLocationManager獲取定位時,是根據CLLocationManagerDelegate中- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations的回撥來獲取到定位的。我們只需要在系統回撥這個方法傳遞給業務程式碼的中間,插入一部分程式碼,來修改locations引數。原本的邏輯為系統回撥->業務程式碼,現在變為系統回撥->模擬定位模組->業務程式碼,就實現了無侵入式的實現模擬定位功能。為了實現這個邏輯,可以有以下幾個思路。

1、 Runtime swizzle

因為業務程式碼是根據- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray<CLLocation *> *)locations方法來接受回撥的,所以可以採用Runtime swizzle這個方法,來實現模擬定位的功能,但是我們中間元件是不知道業務程式碼中具體是哪個類,所以無法具體指定runtime swizzle哪個類,所以只能遍歷所有的類,判斷當前類的方法列表中是否有locationManager:didUpdateLocations:這個方法,如果存在則swizzle。

  • 優點:便於理解。
  • 缺點:需要遍歷所有的類和類的方法列表。

2、中間代理物件

這種思路是Swizzle了CLLocationManager的setDelegate:方法,當呼叫setDelegate時,將真實的delegate object儲存下來,再將我們定義的中間代理類swizzle delegate物件設定為CLLocationManager的delegate,這樣當系統回撥CLLocationManagerDelegate,會先回調到中間代理類swizzle delegate中,再由swizzle delegate將事件傳遞到真實的delegate object。

  • 優點:相對於第一種方法,不需要遍歷類和類的方法列表,只需swizzle CLLocationManager中的setDelegate:方法即可。
  • 缺點:在中間代理類swizzle delegate中需要實現全部的CLLocationManagerDelegate方法,如果後續增加代理方法,仍需要修改這個類。

3、採用NSProxy實現中間代理物件

Objective-C中有2個基類,常用的就是NSObject,另一個就是NSProxy,NSProxy主要用於訊息轉發處理,所以採用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

接著就來處理訊息轉發的邏輯,首先我們要知道我們想要的是什麼效果,系統回撥給MockLocationProxy,MockLocationProxy只處理locationManager:didUpdateLocations:,其他的訊息都仍然交給原target。

所以我們在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

當然可以,你只需要在你的專案中整合LLDebugTool,呼叫其中的Location模組,LLDebugTool提供了一個UI來隨時修改這個模擬值,讓你在除錯時,隨時模擬定位,LLDebugTool仍提供了很多其他的功能,如果你只需要模擬定位的功能,則只需要整合LLDebugTool/Location這個subspec就可以了。

後記

前言說過,定位除了CLLocationManager之外,MKMapView的showUserLocation也可以獲取定位資訊,那麼如何解決這個問題呢? 你可以在LLDebugTool/Location中檢視答案。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。