From eab8378f9fe413c9d62655ff45df4c931fc53abd Mon Sep 17 00:00:00 2001 From: Fabian Guerra Date: Thu, 26 Jul 2018 11:11:44 -0700 Subject: [ios] Aspect's preprocessing test. --- platform/ios/app/Aspects.h | 83 +++ platform/ios/app/Aspects.m | 945 +++++++++++++++++++++++++++++ platform/ios/app/MBXViewController.m | 17 +- platform/ios/app/chinapolyline.geojson | 62 ++ platform/ios/ios.xcodeproj/project.pbxproj | 18 + 5 files changed, 1124 insertions(+), 1 deletion(-) create mode 100755 platform/ios/app/Aspects.h create mode 100755 platform/ios/app/Aspects.m create mode 100644 platform/ios/app/chinapolyline.geojson 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 + +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 + +/// 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 + +/// 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`, 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`. +/// +/// @note Hooking static methods is not supported. +/// @return A token which allows to later deregister the aspect. ++ (id)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)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 +#import +#import + +#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 +- (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)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)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)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. 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 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)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)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 @@ -227,6 +228,20 @@ CLLocationCoordinate2D randomWorldCoordinate() { { [super viewDidLoad]; + [MGLMapView aspect_hookSelector:@selector(addAnnotations:) + withOptions:AspectPositionInstead + usingBlock:^(id aspectInfo, NSArray> *annotations) { + NSLog(@"annotations: %@", annotations); + NSInvocation *invocation = aspectInfo.originalInvocation; + // preprocessing + NSArray> *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 = ""; }; 1F26B6C020E189C9007BCC21 /* MBXCustomLocationViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MBXCustomLocationViewController.m; sourceTree = ""; }; 1F26B6C220E1A351007BCC21 /* simple_route.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = simple_route.json; sourceTree = ""; }; + 1F4F0CDD21068FC30088FDE6 /* Aspects.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Aspects.h; sourceTree = ""; }; + 1F4F0CDE21068FC30088FDE6 /* Aspects.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Aspects.m; sourceTree = ""; }; + 1F4F0CE22107D3AF0088FDE6 /* chinapolyline.geojson */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = chinapolyline.geojson; sourceTree = ""; }; 1F7454941ECD450D00021D39 /* MGLLight_Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLLight_Private.h; sourceTree = ""; }; 1F7454A61ED08AB400021D39 /* MGLLightTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLLightTest.mm; path = ../../darwin/test/MGLLightTest.mm; sourceTree = ""; }; 1F95931C1E6DE2E900D5B294 /* MGLNSDateAdditionsTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLNSDateAdditionsTests.mm; path = ../../darwin/test/MGLNSDateAdditionsTests.mm; sourceTree = ""; }; @@ -1389,6 +1394,15 @@ path = "Integration Test Harness"; sourceTree = ""; }; + 1F4F0CDC21068F9A0088FDE6 /* Aspects */ = { + isa = PBXGroup; + children = ( + 1F4F0CDD21068FC30088FDE6 /* Aspects.h */, + 1F4F0CDE21068FC30088FDE6 /* Aspects.m */, + ); + name = Aspects; + sourceTree = ""; + }; 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; }; -- cgit v1.2.1