preface

App in the function of more and more rely on the user’s actual location, such as provide recommendations based on user location data, based on the position to determine whether a certain functions are available, but not in the development and debugging XCode provides custom simulation positioning function, so the purpose of this paper is a reality can at any time during the process of development and debugging simulation positioning function.

PS: When your app is out of the XCode debug testing phase and still want to change the positioning.

Train of thought

In iOS app development, we usually use CLLocationManager to obtain the current location of the user. Of course, we can also use MKMapView showUserLocation to obtain the location of the user. Therefore, we analyze these two cases respectively.

CLLocationManager

When using CLLocationManager to get positioning, Is the – (void) according to the CLLocationManagerDelegate locationManager: (CLLocationManager *) manager didUpdateLocations: (NSArray < CLLocation *> *) callbacks to locations to get the location. All we need to do is insert some code in the middle of the system callback method passed to the business code to modify the Locations parameter. The original logic for the system callback -> business code, now into the system callback -> simulation positioning module -> business code, to achieve non-invasive simulation positioning function. To implement this logic, there are several ideas.

1, the Runtime swizzle

– (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray

*) The Runtime swizzle method can be used to simulate the location of all the locations of the Runtime swizzle class. Judge whether the method in the list of the current class has a locationManager: didUpdateLocations: this way, if there is a swizzle.

  • Advantages: Easy to understand.
  • Disadvantages: Need to traverse all classes and class method list.

2. Intermediate proxy objects

The idea is to Swizzle the setDelegate: method of CLLocationManager, which saves the real Delegate object when calling setDelegate, Then we define the middle of the proxy class swizzle delegate object is set to the CLLocationManager delegate, such as system callback CLLocationManagerDelegate, A callback to the middle delegate class Swizzle Delegate, which then passes the event to the real Delegate Object.

  • Advantages: Compared to the first method, there is no need to traverse the class and the method list of the class, justswizzle CLLocationManagerIn thesetDelegate:Method can.
  • Disadvantages: Proxy classes in the middleswizzle delegateNeed to implement all ofCLLocationManagerDelegateMethod, and you still need to modify this class if you add proxy methods later.

3. Use NSProxy to realize the intermediate proxy object

There are two base classes in Objective-C, and the one you use is NSObject, and the other one is NSProxy, and NSProxy is mainly used for message forwarding, so you can use NSProxy to better deal with the weaknesses of method two.

Create a new class MockLocationProxy that integrates with NSProxy.

// MockLocationProxy.h
#import <CoreLocation/CoreLocation.h>

@interface MockLocationProxy : NSProxy

@property (nonatomic, weak, readonly, nullable) id <CLLocationManagerDelegate> target;

- (instancetype)initWithTarget:(id <CLLocationManagerDelegate>)target;

@end
Copy the code
// MockLocationProxy.m
#import "MockLocationProxy.h"

@implementation MockLocationProxy

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

@end
Copy the code

Then forward to process the message logic, first we need to know what we want is to effect, system callback to MockLocationProxy MockLocationProxy deals only with locationManager: didUpdateLocations:, All other messages are still sent to the original target.

So we add the following methods to 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: longitude 39.908722:116.397499]; locations = @[mockLocation]; [self.target locationManager:manager didUpdateLocations:locations]; } } @endCopy the code

When a message is sent to MockLocationProxy, judge whether the current method is locationManager: didUpdateLocations: and if it is, is MockLocationProxy respond to events, or directly to the original target. At this point you are ready to handle analog positioning. You just need to do some work in the simulated location code, and you can modify the location at any time.

One more.

The above method can simulate positioning, but every time you change the simulation value, you need to rebuild. Is there any way to change this value at run time?

LLDebugTool

Sure, you just need to integrate LLDebugTool into your project and call the Location module. LLDebugTool provides a UI to modify the simulation value at any time, allowing you to simulate the Location at any time during debugging. LLDebugTool also provides many other functions. If all you need is the ability to simulate Location, just integrate the LLDebugTool/Location subspec.

Afterword.

In addition to CLLocationManager, MKMapView showUserLocation can also obtain location information. How to solve this problem? You can check the answer in LLDebugTool/Location.

Don’t forget to hit Star! ✨ ✨ ✨