summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Guerra <fabian.guerra@mapbox.com>2018-07-26 11:11:44 -0700
committerFabian Guerra <fabian.guerra@mapbox.com>2018-07-26 11:11:44 -0700
commiteab8378f9fe413c9d62655ff45df4c931fc53abd (patch)
tree3b76c1733f8839804b4fdb8642012e7d63c81028
parent870d7fda4d5b65d9a4993f35322678bc6f50767c (diff)
downloadqtlocation-mapboxgl-upstream/fabian-cn-aspects.tar.gz
[ios] Aspect's preprocessing test.upstream/fabian-cn-aspects
-rwxr-xr-xplatform/ios/app/Aspects.h83
-rwxr-xr-xplatform/ios/app/Aspects.m945
-rw-r--r--platform/ios/app/MBXViewController.m17
-rw-r--r--platform/ios/app/chinapolyline.geojson62
-rw-r--r--platform/ios/ios.xcodeproj/project.pbxproj18
5 files changed, 1124 insertions, 1 deletions
diff --git a/platform/ios/app/Aspects.h b/platform/ios/app/Aspects.h
new file mode 100755
index 0000000000..5508f8629d
--- /dev/null
+++ b/platform/ios/app/Aspects.h
@@ -0,0 +1,83 @@
+//
+// Aspects.h
+// Aspects - A delightful, simple library for aspect oriented programming.
+//
+// Copyright (c) 2014 Peter Steinberger. Licensed under the MIT license.
+//
+
+#import <Foundation/Foundation.h>
+
+typedef NS_OPTIONS(NSUInteger, AspectOptions) {
+ AspectPositionAfter = 0, /// Called after the original implementation (default)
+ AspectPositionInstead = 1, /// Will replace the original implementation.
+ AspectPositionBefore = 2, /// Called before the original implementation.
+
+ AspectOptionAutomaticRemoval = 1 << 3 /// Will remove the hook after the first execution.
+};
+
+/// Opaque Aspect Token that allows to deregister the hook.
+@protocol AspectToken <NSObject>
+
+/// Deregisters an aspect.
+/// @return YES if deregistration is successful, otherwise NO.
+- (BOOL)remove;
+
+@end
+
+/// The AspectInfo protocol is the first parameter of our block syntax.
+@protocol AspectInfo <NSObject>
+
+/// The instance that is currently hooked.
+- (id)instance;
+
+/// The original invocation of the hooked method.
+- (NSInvocation *)originalInvocation;
+
+/// All method arguments, boxed. This is lazily evaluated.
+- (NSArray *)arguments;
+
+@end
+
+/**
+ Aspects uses Objective-C message forwarding to hook into messages. This will create some overhead. Don't add aspects to methods that are called a lot. Aspects is meant for view/controller code that is not called a 1000 times per second.
+
+ Adding aspects returns an opaque token which can be used to deregister again. All calls are thread safe.
+ */
+@interface NSObject (Aspects)
+
+/// Adds a block of code before/instead/after the current `selector` for a specific class.
+///
+/// @param block Aspects replicates the type signature of the method being hooked.
+/// The first parameter will be `id<AspectInfo>`, followed by all parameters of the method.
+/// These parameters are optional and will be filled to match the block signature.
+/// You can even use an empty block, or one that simple gets `id<AspectInfo>`.
+///
+/// @note Hooking static methods is not supported.
+/// @return A token which allows to later deregister the aspect.
++ (id<AspectToken>)aspect_hookSelector:(SEL)selector
+ withOptions:(AspectOptions)options
+ usingBlock:(id)block
+ error:(NSError **)error;
+
+/// Adds a block of code before/instead/after the current `selector` for a specific instance.
+- (id<AspectToken>)aspect_hookSelector:(SEL)selector
+ withOptions:(AspectOptions)options
+ usingBlock:(id)block
+ error:(NSError **)error;
+
+@end
+
+
+typedef NS_ENUM(NSUInteger, AspectErrorCode) {
+ AspectErrorSelectorBlacklisted, /// Selectors like release, retain, autorelease are blacklisted.
+ AspectErrorDoesNotRespondToSelector, /// Selector could not be found.
+ AspectErrorSelectorDeallocPosition, /// When hooking dealloc, only AspectPositionBefore is allowed.
+ AspectErrorSelectorAlreadyHookedInClassHierarchy, /// Statically hooking the same method in subclasses is not allowed.
+ AspectErrorFailedToAllocateClassPair, /// The runtime failed creating a class pair.
+ AspectErrorMissingBlockSignature, /// The block misses compile time signature info and can't be called.
+ AspectErrorIncompatibleBlockSignature, /// The block signature does not match the method or is too large.
+
+ AspectErrorRemoveObjectAlreadyDeallocated = 100 /// (for removing) The object hooked is already deallocated.
+};
+
+extern NSString *const AspectErrorDomain;
diff --git a/platform/ios/app/Aspects.m b/platform/ios/app/Aspects.m
new file mode 100755
index 0000000000..c90706651a
--- /dev/null
+++ b/platform/ios/app/Aspects.m
@@ -0,0 +1,945 @@
+//
+// Aspects.m
+// Aspects - A delightful, simple library for aspect oriented programming.
+//
+// Copyright (c) 2014 Peter Steinberger. Licensed under the MIT license.
+//
+
+#import "Aspects.h"
+#import <libkern/OSAtomic.h>
+#import <objc/runtime.h>
+#import <objc/message.h>
+
+#define AspectLog(...)
+//#define AspectLog(...) do { NSLog(__VA_ARGS__); }while(0)
+#define AspectLogError(...) do { NSLog(__VA_ARGS__); }while(0)
+
+// Block internals.
+typedef NS_OPTIONS(int, AspectBlockFlags) {
+ AspectBlockFlagsHasCopyDisposeHelpers = (1 << 25),
+ AspectBlockFlagsHasSignature = (1 << 30)
+};
+typedef struct _AspectBlock {
+ __unused Class isa;
+ AspectBlockFlags flags;
+ __unused int reserved;
+ void (__unused *invoke)(struct _AspectBlock *block, ...);
+ struct {
+ unsigned long int reserved;
+ unsigned long int size;
+ // requires AspectBlockFlagsHasCopyDisposeHelpers
+ void (*copy)(void *dst, const void *src);
+ void (*dispose)(const void *);
+ // requires AspectBlockFlagsHasSignature
+ const char *signature;
+ const char *layout;
+ } *descriptor;
+ // imported variables
+} *AspectBlockRef;
+
+@interface AspectInfo : NSObject <AspectInfo>
+- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation;
+@property (nonatomic, unsafe_unretained, readonly) id instance;
+@property (nonatomic, strong, readonly) NSArray *arguments;
+@property (nonatomic, strong, readonly) NSInvocation *originalInvocation;
+@end
+
+// Tracks a single aspect.
+@interface AspectIdentifier : NSObject
++ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error;
+- (BOOL)invokeWithInfo:(id<AspectInfo>)info;
+@property (nonatomic, assign) SEL selector;
+@property (nonatomic, strong) id block;
+@property (nonatomic, strong) NSMethodSignature *blockSignature;
+@property (nonatomic, weak) id object;
+@property (nonatomic, assign) AspectOptions options;
+@end
+
+// Tracks all aspects for an object/class.
+@interface AspectsContainer : NSObject
+- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)injectPosition;
+- (BOOL)removeAspect:(id)aspect;
+- (BOOL)hasAspects;
+@property (atomic, copy) NSArray *beforeAspects;
+@property (atomic, copy) NSArray *insteadAspects;
+@property (atomic, copy) NSArray *afterAspects;
+@end
+
+@interface AspectTracker : NSObject
+- (id)initWithTrackedClass:(Class)trackedClass;
+@property (nonatomic, strong) Class trackedClass;
+@property (nonatomic, readonly) NSString *trackedClassName;
+@property (nonatomic, strong) NSMutableSet *selectorNames;
+@property (nonatomic, strong) NSMutableDictionary *selectorNamesToSubclassTrackers;
+- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
+- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName;
+- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName;
+- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName;
+@end
+
+@interface NSInvocation (Aspects)
+- (NSArray *)aspects_arguments;
+@end
+
+#define AspectPositionFilter 0x07
+
+#define AspectError(errorCode, errorDescription) do { \
+AspectLogError(@"Aspects: %@", errorDescription); \
+if (error) { *error = [NSError errorWithDomain:AspectErrorDomain code:errorCode userInfo:@{NSLocalizedDescriptionKey: errorDescription}]; }}while(0)
+
+NSString *const AspectErrorDomain = @"AspectErrorDomain";
+static NSString *const AspectsSubclassSuffix = @"_Aspects_";
+static NSString *const AspectsMessagePrefix = @"aspects_";
+
+@implementation NSObject (Aspects)
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Public Aspects API
+
++ (id<AspectToken>)aspect_hookSelector:(SEL)selector
+ withOptions:(AspectOptions)options
+ usingBlock:(id)block
+ error:(NSError **)error {
+ return aspect_add((id)self, selector, options, block, error);
+}
+
+/// @return A token which allows to later deregister the aspect.
+- (id<AspectToken>)aspect_hookSelector:(SEL)selector
+ withOptions:(AspectOptions)options
+ usingBlock:(id)block
+ error:(NSError **)error {
+ return aspect_add(self, selector, options, block, error);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Private Helper
+
+static id aspect_add(id self, SEL selector, AspectOptions options, id block, NSError **error) {
+ NSCParameterAssert(self);
+ NSCParameterAssert(selector);
+ NSCParameterAssert(block);
+
+ __block AspectIdentifier *identifier = nil;
+ aspect_performLocked(^{
+ if (aspect_isSelectorAllowedAndTrack(self, selector, options, error)) {
+ AspectsContainer *aspectContainer = aspect_getContainerForObject(self, selector);
+ identifier = [AspectIdentifier identifierWithSelector:selector object:self options:options block:block error:error];
+ if (identifier) {
+ [aspectContainer addAspect:identifier withOptions:options];
+
+ // Modify the class to allow message interception.
+ aspect_prepareClassAndHookSelector(self, selector, error);
+ }
+ }
+ });
+ return identifier;
+}
+
+static BOOL aspect_remove(AspectIdentifier *aspect, NSError **error) {
+ NSCAssert([aspect isKindOfClass:AspectIdentifier.class], @"Must have correct type.");
+
+ __block BOOL success = NO;
+ aspect_performLocked(^{
+ id self = aspect.object; // strongify
+ if (self) {
+ AspectsContainer *aspectContainer = aspect_getContainerForObject(self, aspect.selector);
+ success = [aspectContainer removeAspect:aspect];
+
+ aspect_cleanupHookedClassAndSelector(self, aspect.selector);
+ // destroy token
+ aspect.object = nil;
+ aspect.block = nil;
+ aspect.selector = NULL;
+ }else {
+ NSString *errrorDesc = [NSString stringWithFormat:@"Unable to deregister hook. Object already deallocated: %@", aspect];
+ AspectError(AspectErrorRemoveObjectAlreadyDeallocated, errrorDesc);
+ }
+ });
+ return success;
+}
+
+static void aspect_performLocked(dispatch_block_t block) {
+ static OSSpinLock aspect_lock = OS_SPINLOCK_INIT;
+ OSSpinLockLock(&aspect_lock);
+ block();
+ OSSpinLockUnlock(&aspect_lock);
+}
+
+static SEL aspect_aliasForSelector(SEL selector) {
+ NSCParameterAssert(selector);
+ return NSSelectorFromString([AspectsMessagePrefix stringByAppendingFormat:@"_%@", NSStringFromSelector(selector)]);
+}
+
+static NSMethodSignature *aspect_blockMethodSignature(id block, NSError **error) {
+ AspectBlockRef layout = (__bridge void *)block;
+ if (!(layout->flags & AspectBlockFlagsHasSignature)) {
+ NSString *description = [NSString stringWithFormat:@"The block %@ doesn't contain a type signature.", block];
+ AspectError(AspectErrorMissingBlockSignature, description);
+ return nil;
+ }
+ void *desc = layout->descriptor;
+ desc += 2 * sizeof(unsigned long int);
+ if (layout->flags & AspectBlockFlagsHasCopyDisposeHelpers) {
+ desc += 2 * sizeof(void *);
+ }
+ if (!desc) {
+ NSString *description = [NSString stringWithFormat:@"The block %@ doesn't has a type signature.", block];
+ AspectError(AspectErrorMissingBlockSignature, description);
+ return nil;
+ }
+ const char *signature = (*(const char **)desc);
+ return [NSMethodSignature signatureWithObjCTypes:signature];
+}
+
+static BOOL aspect_isCompatibleBlockSignature(NSMethodSignature *blockSignature, id object, SEL selector, NSError **error) {
+ NSCParameterAssert(blockSignature);
+ NSCParameterAssert(object);
+ NSCParameterAssert(selector);
+
+ BOOL signaturesMatch = YES;
+ NSMethodSignature *methodSignature = [[object class] instanceMethodSignatureForSelector:selector];
+ if (blockSignature.numberOfArguments > methodSignature.numberOfArguments) {
+ signaturesMatch = NO;
+ }else {
+ if (blockSignature.numberOfArguments > 1) {
+ const char *blockType = [blockSignature getArgumentTypeAtIndex:1];
+ if (blockType[0] != '@') {
+ signaturesMatch = NO;
+ }
+ }
+ // Argument 0 is self/block, argument 1 is SEL or id<AspectInfo>. We start comparing at argument 2.
+ // The block can have less arguments than the method, that's ok.
+ if (signaturesMatch) {
+ for (NSUInteger idx = 2; idx < blockSignature.numberOfArguments; idx++) {
+ const char *methodType = [methodSignature getArgumentTypeAtIndex:idx];
+ const char *blockType = [blockSignature getArgumentTypeAtIndex:idx];
+ // Only compare parameter, not the optional type data.
+ if (!methodType || !blockType || methodType[0] != blockType[0]) {
+ signaturesMatch = NO; break;
+ }
+ }
+ }
+ }
+
+ if (!signaturesMatch) {
+ NSString *description = [NSString stringWithFormat:@"Block signature %@ doesn't match %@.", blockSignature, methodSignature];
+ AspectError(AspectErrorIncompatibleBlockSignature, description);
+ return NO;
+ }
+ return YES;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Class + Selector Preparation
+
+static BOOL aspect_isMsgForwardIMP(IMP impl) {
+ return impl == _objc_msgForward
+#if !defined(__arm64__)
+ || impl == (IMP)_objc_msgForward_stret
+#endif
+ ;
+}
+
+static IMP aspect_getMsgForwardIMP(NSObject *self, SEL selector) {
+ IMP msgForwardIMP = _objc_msgForward;
+#if !defined(__arm64__)
+ // As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id.
+ // https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
+ // https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783
+ // http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4)
+ Method method = class_getInstanceMethod(self.class, selector);
+ const char *encoding = method_getTypeEncoding(method);
+ BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
+ if (methodReturnsStructValue) {
+ @try {
+ NSUInteger valueSize = 0;
+ NSGetSizeAndAlignment(encoding, &valueSize, NULL);
+
+ if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
+ methodReturnsStructValue = NO;
+ }
+ } @catch (__unused NSException *e) {}
+ }
+ if (methodReturnsStructValue) {
+ msgForwardIMP = (IMP)_objc_msgForward_stret;
+ }
+#endif
+ return msgForwardIMP;
+}
+
+static void aspect_prepareClassAndHookSelector(NSObject *self, SEL selector, NSError **error) {
+ NSCParameterAssert(selector);
+ Class klass = aspect_hookClass(self, error);
+ Method targetMethod = class_getInstanceMethod(klass, selector);
+ IMP targetMethodIMP = method_getImplementation(targetMethod);
+ if (!aspect_isMsgForwardIMP(targetMethodIMP)) {
+ // Make a method alias for the existing method implementation, it not already copied.
+ const char *typeEncoding = method_getTypeEncoding(targetMethod);
+ SEL aliasSelector = aspect_aliasForSelector(selector);
+ if (![klass instancesRespondToSelector:aliasSelector]) {
+ __unused BOOL addedAlias = class_addMethod(klass, aliasSelector, method_getImplementation(targetMethod), typeEncoding);
+ NSCAssert(addedAlias, @"Original implementation for %@ is already copied to %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
+ }
+
+ // We use forwardInvocation to hook in.
+ class_replaceMethod(klass, selector, aspect_getMsgForwardIMP(self, selector), typeEncoding);
+ AspectLog(@"Aspects: Installed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
+ }
+}
+
+// Will undo the runtime changes made.
+static void aspect_cleanupHookedClassAndSelector(NSObject *self, SEL selector) {
+ NSCParameterAssert(self);
+ NSCParameterAssert(selector);
+
+ Class klass = object_getClass(self);
+ BOOL isMetaClass = class_isMetaClass(klass);
+ if (isMetaClass) {
+ klass = (Class)self;
+ }
+
+ // Check if the method is marked as forwarded and undo that.
+ Method targetMethod = class_getInstanceMethod(klass, selector);
+ IMP targetMethodIMP = method_getImplementation(targetMethod);
+ if (aspect_isMsgForwardIMP(targetMethodIMP)) {
+ // Restore the original method implementation.
+ const char *typeEncoding = method_getTypeEncoding(targetMethod);
+ SEL aliasSelector = aspect_aliasForSelector(selector);
+ Method originalMethod = class_getInstanceMethod(klass, aliasSelector);
+ IMP originalIMP = method_getImplementation(originalMethod);
+ NSCAssert(originalMethod, @"Original implementation for %@ not found %@ on %@", NSStringFromSelector(selector), NSStringFromSelector(aliasSelector), klass);
+
+ class_replaceMethod(klass, selector, originalIMP, typeEncoding);
+ AspectLog(@"Aspects: Removed hook for -[%@ %@].", klass, NSStringFromSelector(selector));
+ }
+
+ // Deregister global tracked selector
+ aspect_deregisterTrackedSelector(self, selector);
+
+ // Get the aspect container and check if there are any hooks remaining. Clean up if there are not.
+ AspectsContainer *container = aspect_getContainerForObject(self, selector);
+ if (!container.hasAspects) {
+ // Destroy the container
+ aspect_destroyContainerForObject(self, selector);
+
+ // Figure out how the class was modified to undo the changes.
+ NSString *className = NSStringFromClass(klass);
+ if ([className hasSuffix:AspectsSubclassSuffix]) {
+ Class originalClass = NSClassFromString([className stringByReplacingOccurrencesOfString:AspectsSubclassSuffix withString:@""]);
+ NSCAssert(originalClass != nil, @"Original class must exist");
+ object_setClass(self, originalClass);
+ AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(originalClass));
+
+ // We can only dispose the class pair if we can ensure that no instances exist using our subclass.
+ // Since we don't globally track this, we can't ensure this - but there's also not much overhead in keeping it around.
+ //objc_disposeClassPair(object.class);
+ }else {
+ // Class is most likely swizzled in place. Undo that.
+ if (isMetaClass) {
+ aspect_undoSwizzleClassInPlace((Class)self);
+ }else if (self.class != klass) {
+ aspect_undoSwizzleClassInPlace(klass);
+ }
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Hook Class
+
+static Class aspect_hookClass(NSObject *self, NSError **error) {
+ NSCParameterAssert(self);
+ Class statedClass = self.class;
+ Class baseClass = object_getClass(self);
+ NSString *className = NSStringFromClass(baseClass);
+
+ // Already subclassed
+ if ([className hasSuffix:AspectsSubclassSuffix]) {
+ return baseClass;
+
+ // We swizzle a class object, not a single object.
+ }else if (class_isMetaClass(baseClass)) {
+ return aspect_swizzleClassInPlace((Class)self);
+ // Probably a KVO'ed class. Swizzle in place. Also swizzle meta classes in place.
+ }else if (statedClass != baseClass) {
+ return aspect_swizzleClassInPlace(baseClass);
+ }
+
+ // Default case. Create dynamic subclass.
+ const char *subclassName = [className stringByAppendingString:AspectsSubclassSuffix].UTF8String;
+ Class subclass = objc_getClass(subclassName);
+
+ if (subclass == nil) {
+ subclass = objc_allocateClassPair(baseClass, subclassName, 0);
+ if (subclass == nil) {
+ NSString *errrorDesc = [NSString stringWithFormat:@"objc_allocateClassPair failed to allocate class %s.", subclassName];
+ AspectError(AspectErrorFailedToAllocateClassPair, errrorDesc);
+ return nil;
+ }
+
+ aspect_swizzleForwardInvocation(subclass);
+ aspect_hookedGetClass(subclass, statedClass);
+ aspect_hookedGetClass(object_getClass(subclass), statedClass);
+ objc_registerClassPair(subclass);
+ }
+
+ object_setClass(self, subclass);
+ return subclass;
+}
+
+static NSString *const AspectsForwardInvocationSelectorName = @"__aspects_forwardInvocation:";
+static void aspect_swizzleForwardInvocation(Class klass) {
+ NSCParameterAssert(klass);
+ // If there is no method, replace will act like class_addMethod.
+ IMP originalImplementation = class_replaceMethod(klass, @selector(forwardInvocation:), (IMP)__ASPECTS_ARE_BEING_CALLED__, "v@:@");
+ if (originalImplementation) {
+ class_addMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName), originalImplementation, "v@:@");
+ }
+ AspectLog(@"Aspects: %@ is now aspect aware.", NSStringFromClass(klass));
+}
+
+static void aspect_undoSwizzleForwardInvocation(Class klass) {
+ NSCParameterAssert(klass);
+ Method originalMethod = class_getInstanceMethod(klass, NSSelectorFromString(AspectsForwardInvocationSelectorName));
+ Method objectMethod = class_getInstanceMethod(NSObject.class, @selector(forwardInvocation:));
+ // There is no class_removeMethod, so the best we can do is to retore the original implementation, or use a dummy.
+ IMP originalImplementation = method_getImplementation(originalMethod ?: objectMethod);
+ class_replaceMethod(klass, @selector(forwardInvocation:), originalImplementation, "v@:@");
+
+ AspectLog(@"Aspects: %@ has been restored.", NSStringFromClass(klass));
+}
+
+static void aspect_hookedGetClass(Class class, Class statedClass) {
+ NSCParameterAssert(class);
+ NSCParameterAssert(statedClass);
+ Method method = class_getInstanceMethod(class, @selector(class));
+ IMP newIMP = imp_implementationWithBlock(^(id self) {
+ return statedClass;
+ });
+ class_replaceMethod(class, @selector(class), newIMP, method_getTypeEncoding(method));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Swizzle Class In Place
+
+static void _aspect_modifySwizzledClasses(void (^block)(NSMutableSet *swizzledClasses)) {
+ static NSMutableSet *swizzledClasses;
+ static dispatch_once_t pred;
+ dispatch_once(&pred, ^{
+ swizzledClasses = [NSMutableSet new];
+ });
+ @synchronized(swizzledClasses) {
+ block(swizzledClasses);
+ }
+}
+
+static Class aspect_swizzleClassInPlace(Class klass) {
+ NSCParameterAssert(klass);
+ NSString *className = NSStringFromClass(klass);
+
+ _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
+ if (![swizzledClasses containsObject:className]) {
+ aspect_swizzleForwardInvocation(klass);
+ [swizzledClasses addObject:className];
+ }
+ });
+ return klass;
+}
+
+static void aspect_undoSwizzleClassInPlace(Class klass) {
+ NSCParameterAssert(klass);
+ NSString *className = NSStringFromClass(klass);
+
+ _aspect_modifySwizzledClasses(^(NSMutableSet *swizzledClasses) {
+ if ([swizzledClasses containsObject:className]) {
+ aspect_undoSwizzleForwardInvocation(klass);
+ [swizzledClasses removeObject:className];
+ }
+ });
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Aspect Invoke Point
+
+// This is a macro so we get a cleaner stack trace.
+#define aspect_invoke(aspects, info) \
+for (AspectIdentifier *aspect in aspects) {\
+ [aspect invokeWithInfo:info];\
+ if (aspect.options & AspectOptionAutomaticRemoval) { \
+ aspectsToRemove = [aspectsToRemove?:@[] arrayByAddingObject:aspect]; \
+ } \
+}
+
+// This is the swizzled forwardInvocation: method.
+static void __ASPECTS_ARE_BEING_CALLED__(__unsafe_unretained NSObject *self, SEL selector, NSInvocation *invocation) {
+ NSCParameterAssert(self);
+ NSCParameterAssert(invocation);
+ SEL originalSelector = invocation.selector;
+ SEL aliasSelector = aspect_aliasForSelector(invocation.selector);
+ invocation.selector = aliasSelector;
+ AspectsContainer *objectContainer = objc_getAssociatedObject(self, aliasSelector);
+ AspectsContainer *classContainer = aspect_getContainerForClass(object_getClass(self), aliasSelector);
+ AspectInfo *info = [[AspectInfo alloc] initWithInstance:self invocation:invocation];
+ NSArray *aspectsToRemove = nil;
+
+ // Before hooks.
+ aspect_invoke(classContainer.beforeAspects, info);
+ aspect_invoke(objectContainer.beforeAspects, info);
+
+ // Instead hooks.
+ BOOL respondsToAlias = YES;
+ if (objectContainer.insteadAspects.count || classContainer.insteadAspects.count) {
+ aspect_invoke(classContainer.insteadAspects, info);
+ aspect_invoke(objectContainer.insteadAspects, info);
+ }else {
+ Class klass = object_getClass(invocation.target);
+ do {
+ if ((respondsToAlias = [klass instancesRespondToSelector:aliasSelector])) {
+ [invocation invoke];
+ break;
+ }
+ }while (!respondsToAlias && (klass = class_getSuperclass(klass)));
+ }
+
+ // After hooks.
+ aspect_invoke(classContainer.afterAspects, info);
+ aspect_invoke(objectContainer.afterAspects, info);
+
+ // If no hooks are installed, call original implementation (usually to throw an exception)
+ if (!respondsToAlias) {
+ invocation.selector = originalSelector;
+ SEL originalForwardInvocationSEL = NSSelectorFromString(AspectsForwardInvocationSelectorName);
+ if ([self respondsToSelector:originalForwardInvocationSEL]) {
+ ((void( *)(id, SEL, NSInvocation *))objc_msgSend)(self, originalForwardInvocationSEL, invocation);
+ }else {
+ [self doesNotRecognizeSelector:invocation.selector];
+ }
+ }
+
+ // Remove any hooks that are queued for deregistration.
+ [aspectsToRemove makeObjectsPerformSelector:@selector(remove)];
+}
+#undef aspect_invoke
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Aspect Container Management
+
+// Loads or creates the aspect container.
+static AspectsContainer *aspect_getContainerForObject(NSObject *self, SEL selector) {
+ NSCParameterAssert(self);
+ SEL aliasSelector = aspect_aliasForSelector(selector);
+ AspectsContainer *aspectContainer = objc_getAssociatedObject(self, aliasSelector);
+ if (!aspectContainer) {
+ aspectContainer = [AspectsContainer new];
+ objc_setAssociatedObject(self, aliasSelector, aspectContainer, OBJC_ASSOCIATION_RETAIN);
+ }
+ return aspectContainer;
+}
+
+static AspectsContainer *aspect_getContainerForClass(Class klass, SEL selector) {
+ NSCParameterAssert(klass);
+ AspectsContainer *classContainer = nil;
+ do {
+ classContainer = objc_getAssociatedObject(klass, selector);
+ if (classContainer.hasAspects) break;
+ }while ((klass = class_getSuperclass(klass)));
+
+ return classContainer;
+}
+
+static void aspect_destroyContainerForObject(id<NSObject> self, SEL selector) {
+ NSCParameterAssert(self);
+ SEL aliasSelector = aspect_aliasForSelector(selector);
+ objc_setAssociatedObject(self, aliasSelector, nil, OBJC_ASSOCIATION_RETAIN);
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - Selector Blacklist Checking
+
+static NSMutableDictionary *aspect_getSwizzledClassesDict() {
+ static NSMutableDictionary *swizzledClassesDict;
+ static dispatch_once_t pred;
+ dispatch_once(&pred, ^{
+ swizzledClassesDict = [NSMutableDictionary new];
+ });
+ return swizzledClassesDict;
+}
+
+static BOOL aspect_isSelectorAllowedAndTrack(NSObject *self, SEL selector, AspectOptions options, NSError **error) {
+ static NSSet *disallowedSelectorList;
+ static dispatch_once_t pred;
+ dispatch_once(&pred, ^{
+ disallowedSelectorList = [NSSet setWithObjects:@"retain", @"release", @"autorelease", @"forwardInvocation:", nil];
+ });
+
+ // Check against the blacklist.
+ NSString *selectorName = NSStringFromSelector(selector);
+ if ([disallowedSelectorList containsObject:selectorName]) {
+ NSString *errorDescription = [NSString stringWithFormat:@"Selector %@ is blacklisted.", selectorName];
+ AspectError(AspectErrorSelectorBlacklisted, errorDescription);
+ return NO;
+ }
+
+ // Additional checks.
+ AspectOptions position = options&AspectPositionFilter;
+ if ([selectorName isEqualToString:@"dealloc"] && position != AspectPositionBefore) {
+ NSString *errorDesc = @"AspectPositionBefore is the only valid position when hooking dealloc.";
+ AspectError(AspectErrorSelectorDeallocPosition, errorDesc);
+ return NO;
+ }
+
+ if (![self respondsToSelector:selector] && ![self.class instancesRespondToSelector:selector]) {
+ NSString *errorDesc = [NSString stringWithFormat:@"Unable to find selector -[%@ %@].", NSStringFromClass(self.class), selectorName];
+ AspectError(AspectErrorDoesNotRespondToSelector, errorDesc);
+ return NO;
+ }
+
+ // Search for the current class and the class hierarchy IF we are modifying a class object
+ if (class_isMetaClass(object_getClass(self))) {
+ Class klass = [self class];
+ NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
+ Class currentClass = [self class];
+
+ AspectTracker *tracker = swizzledClassesDict[currentClass];
+ if ([tracker subclassHasHookedSelectorName:selectorName]) {
+ NSSet *subclassTracker = [tracker subclassTrackersHookingSelectorName:selectorName];
+ NSSet *subclassNames = [subclassTracker valueForKey:@"trackedClassName"];
+ NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked subclasses: %@. A method can only be hooked once per class hierarchy.", selectorName, subclassNames];
+ AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
+ return NO;
+ }
+
+ do {
+ tracker = swizzledClassesDict[currentClass];
+ if ([tracker.selectorNames containsObject:selectorName]) {
+ if (klass == currentClass) {
+ // Already modified and topmost!
+ return YES;
+ }
+ NSString *errorDescription = [NSString stringWithFormat:@"Error: %@ already hooked in %@. A method can only be hooked once per class hierarchy.", selectorName, NSStringFromClass(currentClass)];
+ AspectError(AspectErrorSelectorAlreadyHookedInClassHierarchy, errorDescription);
+ return NO;
+ }
+ } while ((currentClass = class_getSuperclass(currentClass)));
+
+ // Add the selector as being modified.
+ currentClass = klass;
+ AspectTracker *subclassTracker = nil;
+ do {
+ tracker = swizzledClassesDict[currentClass];
+ if (!tracker) {
+ tracker = [[AspectTracker alloc] initWithTrackedClass:currentClass];
+ swizzledClassesDict[(id<NSCopying>)currentClass] = tracker;
+ }
+ if (subclassTracker) {
+ [tracker addSubclassTracker:subclassTracker hookingSelectorName:selectorName];
+ } else {
+ [tracker.selectorNames addObject:selectorName];
+ }
+
+ // All superclasses get marked as having a subclass that is modified.
+ subclassTracker = tracker;
+ }while ((currentClass = class_getSuperclass(currentClass)));
+ } else {
+ return YES;
+ }
+
+ return YES;
+}
+
+static void aspect_deregisterTrackedSelector(id self, SEL selector) {
+ if (!class_isMetaClass(object_getClass(self))) return;
+
+ NSMutableDictionary *swizzledClassesDict = aspect_getSwizzledClassesDict();
+ NSString *selectorName = NSStringFromSelector(selector);
+ Class currentClass = [self class];
+ AspectTracker *subclassTracker = nil;
+ do {
+ AspectTracker *tracker = swizzledClassesDict[currentClass];
+ if (subclassTracker) {
+ [tracker removeSubclassTracker:subclassTracker hookingSelectorName:selectorName];
+ } else {
+ [tracker.selectorNames removeObject:selectorName];
+ }
+ if (tracker.selectorNames.count == 0 && tracker.selectorNamesToSubclassTrackers) {
+ [swizzledClassesDict removeObjectForKey:currentClass];
+ }
+ subclassTracker = tracker;
+ }while ((currentClass = class_getSuperclass(currentClass)));
+}
+
+@end
+
+@implementation AspectTracker
+
+- (id)initWithTrackedClass:(Class)trackedClass {
+ if (self = [super init]) {
+ _trackedClass = trackedClass;
+ _selectorNames = [NSMutableSet new];
+ _selectorNamesToSubclassTrackers = [NSMutableDictionary new];
+ }
+ return self;
+}
+
+- (BOOL)subclassHasHookedSelectorName:(NSString *)selectorName {
+ return self.selectorNamesToSubclassTrackers[selectorName] != nil;
+}
+
+- (void)addSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
+ NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
+ if (!trackerSet) {
+ trackerSet = [NSMutableSet new];
+ self.selectorNamesToSubclassTrackers[selectorName] = trackerSet;
+ }
+ [trackerSet addObject:subclassTracker];
+}
+- (void)removeSubclassTracker:(AspectTracker *)subclassTracker hookingSelectorName:(NSString *)selectorName {
+ NSMutableSet *trackerSet = self.selectorNamesToSubclassTrackers[selectorName];
+ [trackerSet removeObject:subclassTracker];
+ if (trackerSet.count == 0) {
+ [self.selectorNamesToSubclassTrackers removeObjectForKey:selectorName];
+ }
+}
+- (NSSet *)subclassTrackersHookingSelectorName:(NSString *)selectorName {
+ NSMutableSet *hookingSubclassTrackers = [NSMutableSet new];
+ for (AspectTracker *tracker in self.selectorNamesToSubclassTrackers[selectorName]) {
+ if ([tracker.selectorNames containsObject:selectorName]) {
+ [hookingSubclassTrackers addObject:tracker];
+ }
+ [hookingSubclassTrackers unionSet:[tracker subclassTrackersHookingSelectorName:selectorName]];
+ }
+ return hookingSubclassTrackers;
+}
+- (NSString *)trackedClassName {
+ return NSStringFromClass(self.trackedClass);
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<%@: %@, trackedClass: %@, selectorNames:%@, subclass selector names: %@>", self.class, self, NSStringFromClass(self.trackedClass), self.selectorNames, self.selectorNamesToSubclassTrackers.allKeys];
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - NSInvocation (Aspects)
+
+@implementation NSInvocation (Aspects)
+
+// Thanks to the ReactiveCocoa team for providing a generic solution for this.
+- (id)aspect_argumentAtIndex:(NSUInteger)index {
+ const char *argType = [self.methodSignature getArgumentTypeAtIndex:index];
+ // Skip const type qualifier.
+ if (argType[0] == _C_CONST) argType++;
+
+#define WRAP_AND_RETURN(type) do { type val = 0; [self getArgument:&val atIndex:(NSInteger)index]; return @(val); } while (0)
+ if (strcmp(argType, @encode(id)) == 0 || strcmp(argType, @encode(Class)) == 0) {
+ __autoreleasing id returnObj;
+ [self getArgument:&returnObj atIndex:(NSInteger)index];
+ return returnObj;
+ } else if (strcmp(argType, @encode(SEL)) == 0) {
+ SEL selector = 0;
+ [self getArgument:&selector atIndex:(NSInteger)index];
+ return NSStringFromSelector(selector);
+ } else if (strcmp(argType, @encode(Class)) == 0) {
+ __autoreleasing Class theClass = Nil;
+ [self getArgument:&theClass atIndex:(NSInteger)index];
+ return theClass;
+ // Using this list will box the number with the appropriate constructor, instead of the generic NSValue.
+ } else if (strcmp(argType, @encode(char)) == 0) {
+ WRAP_AND_RETURN(char);
+ } else if (strcmp(argType, @encode(int)) == 0) {
+ WRAP_AND_RETURN(int);
+ } else if (strcmp(argType, @encode(short)) == 0) {
+ WRAP_AND_RETURN(short);
+ } else if (strcmp(argType, @encode(long)) == 0) {
+ WRAP_AND_RETURN(long);
+ } else if (strcmp(argType, @encode(long long)) == 0) {
+ WRAP_AND_RETURN(long long);
+ } else if (strcmp(argType, @encode(unsigned char)) == 0) {
+ WRAP_AND_RETURN(unsigned char);
+ } else if (strcmp(argType, @encode(unsigned int)) == 0) {
+ WRAP_AND_RETURN(unsigned int);
+ } else if (strcmp(argType, @encode(unsigned short)) == 0) {
+ WRAP_AND_RETURN(unsigned short);
+ } else if (strcmp(argType, @encode(unsigned long)) == 0) {
+ WRAP_AND_RETURN(unsigned long);
+ } else if (strcmp(argType, @encode(unsigned long long)) == 0) {
+ WRAP_AND_RETURN(unsigned long long);
+ } else if (strcmp(argType, @encode(float)) == 0) {
+ WRAP_AND_RETURN(float);
+ } else if (strcmp(argType, @encode(double)) == 0) {
+ WRAP_AND_RETURN(double);
+ } else if (strcmp(argType, @encode(BOOL)) == 0) {
+ WRAP_AND_RETURN(BOOL);
+ } else if (strcmp(argType, @encode(bool)) == 0) {
+ WRAP_AND_RETURN(BOOL);
+ } else if (strcmp(argType, @encode(char *)) == 0) {
+ WRAP_AND_RETURN(const char *);
+ } else if (strcmp(argType, @encode(void (^)(void))) == 0) {
+ __unsafe_unretained id block = nil;
+ [self getArgument:&block atIndex:(NSInteger)index];
+ return [block copy];
+ } else {
+ NSUInteger valueSize = 0;
+ NSGetSizeAndAlignment(argType, &valueSize, NULL);
+
+ unsigned char valueBytes[valueSize];
+ [self getArgument:valueBytes atIndex:(NSInteger)index];
+
+ return [NSValue valueWithBytes:valueBytes objCType:argType];
+ }
+ return nil;
+#undef WRAP_AND_RETURN
+}
+
+- (NSArray *)aspects_arguments {
+ NSMutableArray *argumentsArray = [NSMutableArray array];
+ for (NSUInteger idx = 2; idx < self.methodSignature.numberOfArguments; idx++) {
+ [argumentsArray addObject:[self aspect_argumentAtIndex:idx] ?: NSNull.null];
+ }
+ return [argumentsArray copy];
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - AspectIdentifier
+
+@implementation AspectIdentifier
+
++ (instancetype)identifierWithSelector:(SEL)selector object:(id)object options:(AspectOptions)options block:(id)block error:(NSError **)error {
+ NSCParameterAssert(block);
+ NSCParameterAssert(selector);
+ NSMethodSignature *blockSignature = aspect_blockMethodSignature(block, error); // TODO: check signature compatibility, etc.
+ if (!aspect_isCompatibleBlockSignature(blockSignature, object, selector, error)) {
+ return nil;
+ }
+
+ AspectIdentifier *identifier = nil;
+ if (blockSignature) {
+ identifier = [AspectIdentifier new];
+ identifier.selector = selector;
+ identifier.block = block;
+ identifier.blockSignature = blockSignature;
+ identifier.options = options;
+ identifier.object = object; // weak
+ }
+ return identifier;
+}
+
+- (BOOL)invokeWithInfo:(id<AspectInfo>)info {
+ NSInvocation *blockInvocation = [NSInvocation invocationWithMethodSignature:self.blockSignature];
+ NSInvocation *originalInvocation = info.originalInvocation;
+ NSUInteger numberOfArguments = self.blockSignature.numberOfArguments;
+
+ // Be extra paranoid. We already check that on hook registration.
+ if (numberOfArguments > originalInvocation.methodSignature.numberOfArguments) {
+ AspectLogError(@"Block has too many arguments. Not calling %@", info);
+ return NO;
+ }
+
+ // The `self` of the block will be the AspectInfo. Optional.
+ if (numberOfArguments > 1) {
+ [blockInvocation setArgument:&info atIndex:1];
+ }
+
+ void *argBuf = NULL;
+ for (NSUInteger idx = 2; idx < numberOfArguments; idx++) {
+ const char *type = [originalInvocation.methodSignature getArgumentTypeAtIndex:idx];
+ NSUInteger argSize;
+ NSGetSizeAndAlignment(type, &argSize, NULL);
+
+ if (!(argBuf = reallocf(argBuf, argSize))) {
+ AspectLogError(@"Failed to allocate memory for block invocation.");
+ return NO;
+ }
+
+ [originalInvocation getArgument:argBuf atIndex:idx];
+ [blockInvocation setArgument:argBuf atIndex:idx];
+ }
+
+ [blockInvocation invokeWithTarget:self.block];
+
+ if (argBuf != NULL) {
+ free(argBuf);
+ }
+ return YES;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<%@: %p, SEL:%@ object:%@ options:%tu block:%@ (#%tu args)>", self.class, self, NSStringFromSelector(self.selector), self.object, self.options, self.block, self.blockSignature.numberOfArguments];
+}
+
+- (BOOL)remove {
+ return aspect_remove(self, NULL);
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - AspectsContainer
+
+@implementation AspectsContainer
+
+- (BOOL)hasAspects {
+ return self.beforeAspects.count > 0 || self.insteadAspects.count > 0 || self.afterAspects.count > 0;
+}
+
+- (void)addAspect:(AspectIdentifier *)aspect withOptions:(AspectOptions)options {
+ NSParameterAssert(aspect);
+ NSUInteger position = options&AspectPositionFilter;
+ switch (position) {
+ case AspectPositionBefore: self.beforeAspects = [(self.beforeAspects ?:@[]) arrayByAddingObject:aspect]; break;
+ case AspectPositionInstead: self.insteadAspects = [(self.insteadAspects?:@[]) arrayByAddingObject:aspect]; break;
+ case AspectPositionAfter: self.afterAspects = [(self.afterAspects ?:@[]) arrayByAddingObject:aspect]; break;
+ }
+}
+
+- (BOOL)removeAspect:(id)aspect {
+ for (NSString *aspectArrayName in @[NSStringFromSelector(@selector(beforeAspects)),
+ NSStringFromSelector(@selector(insteadAspects)),
+ NSStringFromSelector(@selector(afterAspects))]) {
+ NSArray *array = [self valueForKey:aspectArrayName];
+ NSUInteger index = [array indexOfObjectIdenticalTo:aspect];
+ if (array && index != NSNotFound) {
+ NSMutableArray *newArray = [NSMutableArray arrayWithArray:array];
+ [newArray removeObjectAtIndex:index];
+ [self setValue:newArray forKey:aspectArrayName];
+ return YES;
+ }
+ }
+ return NO;
+}
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"<%@: %p, before:%@, instead:%@, after:%@>", self.class, self, self.beforeAspects, self.insteadAspects, self.afterAspects];
+}
+
+@end
+
+///////////////////////////////////////////////////////////////////////////////////////////
+#pragma mark - AspectInfo
+
+@implementation AspectInfo
+
+@synthesize arguments = _arguments;
+
+- (id)initWithInstance:(__unsafe_unretained id)instance invocation:(NSInvocation *)invocation {
+ NSCParameterAssert(instance);
+ NSCParameterAssert(invocation);
+ if (self = [super init]) {
+ _instance = instance;
+ _originalInvocation = invocation;
+ }
+ return self;
+}
+
+- (NSArray *)arguments {
+ // Lazily evaluate arguments, boxing is expensive.
+ if (!_arguments) {
+ _arguments = self.originalInvocation.aspects_arguments;
+ }
+ return _arguments;
+}
+
+@end
diff --git a/platform/ios/app/MBXViewController.m b/platform/ios/app/MBXViewController.m
index 71bad66aee..f7f6dac332 100644
--- a/platform/ios/app/MBXViewController.m
+++ b/platform/ios/app/MBXViewController.m
@@ -7,6 +7,7 @@
#import "MBXUserLocationAnnotationView.h"
#import "LimeGreenStyleLayer.h"
#import "MBXEmbeddedMapViewController.h"
+#import "Aspects.h"
#import <Mapbox/Mapbox.h>
@@ -227,6 +228,20 @@ CLLocationCoordinate2D randomWorldCoordinate() {
{
[super viewDidLoad];
+ [MGLMapView aspect_hookSelector:@selector(addAnnotations:)
+ withOptions:AspectPositionInstead
+ usingBlock:^(id<AspectInfo> aspectInfo, NSArray<id <MGLAnnotation>> *annotations) {
+ NSLog(@"annotations: %@", annotations);
+ NSInvocation *invocation = aspectInfo.originalInvocation;
+ // preprocessing
+ NSArray<id <MGLAnnotation>> *newannotations = @[annotations.firstObject];
+ [invocation setArgument:&newannotations atIndex:2];
+ [invocation invoke];
+
+ }
+ error:NULL];
+
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(restoreState:) name:UIApplicationWillEnterForegroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveState:) name:UIApplicationWillTerminateNotification object:nil];
@@ -832,7 +847,7 @@ CLLocationCoordinate2D randomWorldCoordinate() {
//
NSDictionary *hike = [NSJSONSerialization JSONObjectWithData:
[NSData dataWithContentsOfFile:
- [[NSBundle mainBundle] pathForResource:@"polyline" ofType:@"geojson"]]
+ [[NSBundle mainBundle] pathForResource:@"chinapolyline" ofType:@"geojson"]]
options:0
error:nil];
diff --git a/platform/ios/app/chinapolyline.geojson b/platform/ios/app/chinapolyline.geojson
new file mode 100644
index 0000000000..764bedacde
--- /dev/null
+++ b/platform/ios/app/chinapolyline.geojson
@@ -0,0 +1,62 @@
+{
+ "type": "FeatureCollection",
+ "features": [
+ {
+ "type": "Feature",
+ "properties": {},
+ "geometry": {
+ "type": "LineString",
+ "coordinates": [
+ [
+ 121.44351482391356,
+ 31.226160509717573
+ ],
+ [
+ 121.44576787948607,
+ 31.22720639970393
+ ],
+ [
+ 121.44460916519166,
+ 31.22995868646408
+ ],
+ [
+ 121.44681930541991,
+ 31.230912793829667
+ ],
+ [
+ 121.44531726837158,
+ 31.23320628173707
+ ],
+ [
+ 121.44744157791138,
+ 31.234142008812917
+ ],
+ [
+ 121.4461326599121,
+ 31.235829963629865
+ ],
+ [
+ 121.44503831863402,
+ 31.236637235707626
+ ],
+ [
+ 121.44349336624146,
+ 31.23832514594256
+ ],
+ [
+ 121.44184112548828,
+ 31.240434991324243
+ ],
+ [
+ 121.4397382736206,
+ 31.242379676633526
+ ],
+ [
+ 121.44132614135742,
+ 31.24333365856142
+ ]
+ ]
+ }
+ }
+ ]
+}
diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj
index 2a9c9b5761..a3465bd317 100644
--- a/platform/ios/ios.xcodeproj/project.pbxproj
+++ b/platform/ios/ios.xcodeproj/project.pbxproj
@@ -32,6 +32,8 @@
1F06668D1EC64F8E001C16D7 /* MGLLight.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F0666891EC64F8E001C16D7 /* MGLLight.mm */; };
1F26B6C120E189C9007BCC21 /* MBXCustomLocationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F26B6C020E189C9007BCC21 /* MBXCustomLocationViewController.m */; };
1F26B6C320E1A351007BCC21 /* simple_route.json in Resources */ = {isa = PBXBuildFile; fileRef = 1F26B6C220E1A351007BCC21 /* simple_route.json */; };
+ 1F4F0CDF21068FC40088FDE6 /* Aspects.m in Sources */ = {isa = PBXBuildFile; fileRef = 1F4F0CDE21068FC30088FDE6 /* Aspects.m */; };
+ 1F4F0CE32107D3AF0088FDE6 /* chinapolyline.geojson in Resources */ = {isa = PBXBuildFile; fileRef = 1F4F0CE22107D3AF0088FDE6 /* chinapolyline.geojson */; };
1F7454921ECBB42C00021D39 /* MGLLight.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1F0666891EC64F8E001C16D7 /* MGLLight.mm */; };
1F7454931ECBB43F00021D39 /* MGLLight.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F0666881EC64F8E001C16D7 /* MGLLight.h */; settings = {ATTRIBUTES = (Public, ); }; };
1F7454961ECD450D00021D39 /* MGLLight_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 1F7454941ECD450D00021D39 /* MGLLight_Private.h */; };
@@ -763,6 +765,9 @@
1F26B6BF20E189C9007BCC21 /* MBXCustomLocationViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MBXCustomLocationViewController.h; sourceTree = "<group>"; };
1F26B6C020E189C9007BCC21 /* MBXCustomLocationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MBXCustomLocationViewController.m; sourceTree = "<group>"; };
1F26B6C220E1A351007BCC21 /* simple_route.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = simple_route.json; sourceTree = "<group>"; };
+ 1F4F0CDD21068FC30088FDE6 /* Aspects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Aspects.h; sourceTree = "<group>"; };
+ 1F4F0CDE21068FC30088FDE6 /* Aspects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Aspects.m; sourceTree = "<group>"; };
+ 1F4F0CE22107D3AF0088FDE6 /* chinapolyline.geojson */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = chinapolyline.geojson; sourceTree = "<group>"; };
1F7454941ECD450D00021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = "<group>"; };
1F7454A61ED08AB400021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLLightTest.mm; path = ../../darwin/test/MGLLightTest.mm; sourceTree = "<group>"; };
1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = "<group>"; };
@@ -1389,6 +1394,15 @@
path = "Integration Test Harness";
sourceTree = "<group>";
};
+ 1F4F0CDC21068F9A0088FDE6 /* Aspects */ = {
+ isa = PBXGroup;
+ children = (
+ 1F4F0CDD21068FC30088FDE6 /* Aspects.h */,
+ 1F4F0CDE21068FC30088FDE6 /* Aspects.m */,
+ );
+ name = Aspects;
+ sourceTree = "<group>";
+ };
35136D491D4277EA00C20EFD /* Sources */ = {
isa = PBXGroup;
children = (
@@ -1771,6 +1785,7 @@
DA1DC94C1CB6C1C2006E619F /* Demo App */ = {
isa = PBXGroup;
children = (
+ 1F4F0CDC21068F9A0088FDE6 /* Aspects */,
3E6465D52065767A00685536 /* LimeGreenStyleLayer.h */,
3E6465D42065767A00685536 /* LimeGreenStyleLayer.m */,
DA1DC9501CB6C1C2006E619F /* MBXAppDelegate.h */,
@@ -1806,6 +1821,7 @@
DA1DC94D1CB6C1C2006E619F /* Supporting Files */ = {
isa = PBXGroup;
children = (
+ 1F4F0CE22107D3AF0088FDE6 /* chinapolyline.geojson */,
DA1DC9961CB6E046006E619F /* main.m */,
);
name = "Supporting Files";
@@ -2754,6 +2770,7 @@
DD4823771D94AE6C00EB71B7 /* numeric_filter_style.json in Resources */,
DA1DC9701CB6C6CE006E619F /* points.geojson in Resources */,
353BAEF61D646370009A8DA9 /* amsterdam.geojson in Resources */,
+ 1F4F0CE32107D3AF0088FDE6 /* chinapolyline.geojson in Resources */,
DA1DC9711CB6C6CE006E619F /* polyline.geojson in Resources */,
DD4823761D94AE6C00EB71B7 /* line_filter_style.json in Resources */,
DA821D071CCC6D59007508D4 /* Main.storyboard in Resources */,
@@ -2864,6 +2881,7 @@
1F26B6C120E189C9007BCC21 /* MBXCustomLocationViewController.m in Sources */,
3E6465D62065767A00685536 /* LimeGreenStyleLayer.m in Sources */,
632281DF1E6F855900D75A5D /* MBXEmbeddedMapViewController.m in Sources */,
+ 1F4F0CDF21068FC40088FDE6 /* Aspects.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};