diff options
author | Julian Rex <julian.rex@mapbox.com> | 2018-03-05 13:15:20 -0500 |
---|---|---|
committer | Julian Rex <julian.rex@mapbox.com> | 2018-03-05 13:16:02 -0500 |
commit | 6fb2a94c5e1be320cc88bf9ed822fbd297223c4b (patch) | |
tree | 2c87aa4638883272223bffcc9859251c1b9ffbb4 | |
parent | ad323ded0cd2ae4e0987ee8a783f1bb65435f4b8 (diff) | |
download | qtlocation-mapboxgl-6fb2a94c5e1be320cc88bf9ed822fbd297223c4b.tar.gz |
[ios, macos, core] Added retain/release manager that holds on to the MGLOpenGLStyleLayers for the 2 trips through the render loop that are needed before they can be deallocated. Added additional tests.
-rw-r--r-- | include/mbgl/style/layers/custom_layer.hpp | 15 | ||||
-rw-r--r-- | platform/darwin/src/MGLOpenGLStyleLayer.mm | 44 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyle.mm | 21 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyleLayer.mm | 21 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyleLayerRetentionManager.mm | 53 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyleLayerRetentionManager_Private.h | 16 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyleLayer_Private.h | 8 | ||||
-rw-r--r-- | platform/darwin/src/MGLStyle_Private.h | 3 | ||||
-rw-r--r-- | platform/ios/Integration Tests/MBGLIntegrationTests.m | 247 | ||||
-rw-r--r-- | platform/ios/Integration Tests/MBGLIntegrationTests.mm | 459 | ||||
-rw-r--r-- | platform/ios/ios.xcodeproj/project.pbxproj | 36 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView.mm | 16 | ||||
-rw-r--r-- | platform/ios/src/MGLMapView_Private.h | 4 | ||||
-rw-r--r-- | src/mbgl/style/layers/custom_layer.cpp | 14 |
14 files changed, 664 insertions, 293 deletions
diff --git a/include/mbgl/style/layers/custom_layer.hpp b/include/mbgl/style/layers/custom_layer.hpp index bf3387f95b..cd54c54ffb 100644 --- a/include/mbgl/style/layers/custom_layer.hpp +++ b/include/mbgl/style/layers/custom_layer.hpp @@ -54,6 +54,17 @@ using CustomLayerContextLostFunction = void (*)(void* context); */ using CustomLayerDeinitializeFunction = void (*)(void* context); +/** + * Called from `CustomLayer`'s destructor. + * This provides a mechanism to handle any necessary clean-up using the provided `peer` object. + * For example, if a platform-native peer object has a raw pointer to the CustomLayer it could be + * set to NULL. + * + * This function is called from CustomLayer, unlike the above functions that are passed into the + * private implementation. + */ +using CustomLayerDeallocationFunction = void (*)(util::unique_any *peer); + class CustomLayer : public Layer { public: CustomLayer(const std::string& id, @@ -61,12 +72,14 @@ public: CustomLayerRenderFunction, CustomLayerContextLostFunction, CustomLayerDeinitializeFunction, + CustomLayerDeallocationFunction, void* context); CustomLayer(const std::string& id, CustomLayerInitializeFunction, CustomLayerRenderFunction, CustomLayerDeinitializeFunction, + CustomLayerDeallocationFunction, void* context); ~CustomLayer() final; @@ -87,6 +100,8 @@ public: std::unique_ptr<Layer> cloneRef(const std::string& id) const final; CustomLayer(const CustomLayer&) = delete; + + CustomLayerDeallocationFunction deallocationFn = nullptr; }; template <> diff --git a/platform/darwin/src/MGLOpenGLStyleLayer.mm b/platform/darwin/src/MGLOpenGLStyleLayer.mm index 80805b001b..a5f8b574a0 100644 --- a/platform/darwin/src/MGLOpenGLStyleLayer.mm +++ b/platform/darwin/src/MGLOpenGLStyleLayer.mm @@ -50,13 +50,43 @@ void MGLDrawCustomStyleLayer(void *context, const mbgl::style::CustomLayerRender when creating an OpenGL style layer. */ void MGLFinishCustomStyleLayer(void *context) { - // Note, that the layer is retained/released by MGLStyle, ensuring that the layer // is alive during rendering MGLOpenGLStyleLayer *layer = (__bridge MGLOpenGLStyleLayer*)context; + [layer willMoveFromMapView:layer.style.mapView]; } + +/** + Function to be called when the core `CustomLayer` (not the Impl) gets deallocated. + It's possible taht at this stage the Obj-C style layer is being deallocated (but that case is detected). + */ +void MGLDeallocateCustomStyleLayer(mbgl::util::unique_any *peer) { + + // We know that the peer object contains a LayerWrapper with a weak pointer to + // our custom layer. We can use this to safely access the layer, and clear out the + // raw pointer. + // + // If we don't do this rawLayer can become a dangling pointer (which was previously being + // accessed via the description method) + + if (!(peer && peer->has_value())) + return; + + LayerWrapper *wrapper = mbgl::util::any_cast<LayerWrapper>(peer); + + if (!wrapper) + return; + + // If the MGLStyleLayer is currently being dealloc'd (and trigger the CustomLayer destructor, and + // this function) then layer here will be nil (even though wrapper->layer may appear to be non-nil) + MGLStyleLayer *layer = wrapper->layer; + + layer.rawLayer = NULL; +} + + /** An `MGLOpenGLStyleLayer` is a style layer that is rendered by OpenGL code that you provide. @@ -108,7 +138,9 @@ void MGLFinishCustomStyleLayer(void *context) { MGLPrepareCustomStyleLayer, MGLDrawCustomStyleLayer, MGLFinishCustomStyleLayer, + MGLDeallocateCustomStyleLayer, (__bridge void*)self); + return self = [super initWithPendingLayer:std::move(layer)]; } @@ -128,11 +160,21 @@ void MGLFinishCustomStyleLayer(void *context) { - (void)addToStyle:(MGLStyle *)style belowLayer:(MGLStyleLayer *)otherLayer { self.style = style; + + // We need to ensure that this layer is retained, so that any references from layer impl's + // e.g. contexts) are still valid + [style addToManagedLayers:self]; + [super addToStyle:style belowLayer:otherLayer]; } - (void)removeFromStyle:(MGLStyle *)style { [super removeFromStyle:style]; + + // We need to ensure that this layer is now released (however, if this layer is about to be + // used by the renderer then it will released once rendering is complete) + [style removeFromManagedLayers:self]; + self.style = nil; } diff --git a/platform/darwin/src/MGLStyle.mm b/platform/darwin/src/MGLStyle.mm index 695f8b55e7..3d503d77aa 100644 --- a/platform/darwin/src/MGLStyle.mm +++ b/platform/darwin/src/MGLStyle.mm @@ -84,10 +84,7 @@ @property (nonatomic, readonly) mbgl::style::Style *rawStyle; @property (readonly, copy, nullable) NSURL *URL; @property (nonatomic) NS_MUTABLE_DICTIONARY_OF(NSString *, NS_DICTIONARY_OF(NSObject *, MGLTextLanguage *) *) *localizedLayersByIdentifier; - -// Used for retain/release management -@property (nonatomic) NSMutableSet *layersForUpdating; -@property (nonatomic) NSSet *layersForRendering; +@property (nonatomic, readwrite) NSMutableSet *managedLayers; @end @@ -176,8 +173,7 @@ static NSURL *MGLStyleURL_trafficNight; - (instancetype)initWithRawStyle:(mbgl::style::Style *)rawStyle mapView:(MGLMapView *)mapView { if (self = [super init]) { - _layersForUpdating = [NSMutableSet set]; - + _managedLayers = [NSMutableSet set]; _mapView = mapView; _rawStyle = rawStyle; _localizedLayersByIdentifier = [NSMutableDictionary dictionary]; @@ -543,22 +539,13 @@ static NSURL *MGLStyleURL_trafficNight; #pragma mark - Layer retain/release management - (void)addToManagedLayers:(MGLStyleLayer*)layer { - [self.layersForUpdating addObject:layer]; + [self.managedLayers addObject:layer]; } - (void)removeFromManagedLayers:(MGLStyleLayer*)layer { - [self.layersForUpdating removeObject:layer]; -} - -- (void)retainLayersUsedDuringRendering { - self.layersForRendering = [self.layersForUpdating copy]; + [self.managedLayers removeObject:layer]; } -- (void)releaseLayersUsedDuringRendering { - self.layersForRendering = nil; -} - - #pragma mark Style classes - (NS_ARRAY_OF(NSString *) *)styleClasses diff --git a/platform/darwin/src/MGLStyleLayer.mm b/platform/darwin/src/MGLStyleLayer.mm index 444dbaeea3..f29142c0c7 100644 --- a/platform/darwin/src/MGLStyleLayer.mm +++ b/platform/darwin/src/MGLStyleLayer.mm @@ -5,9 +5,7 @@ #include <mbgl/style/layer.hpp> @interface MGLStyleLayer () - -@property (nonatomic, readonly) mbgl::style::Layer *rawLayer; - +@property (nonatomic, readwrite, nullable) mbgl::style::Layer *rawLayer; @end @implementation MGLStyleLayer { @@ -38,10 +36,6 @@ "to the style more than once is invalid.", self, style]; } - // We need to ensure that this layer is retained, so that any references from layer impl's - // e.g. contexts) are still valid - [style addToManagedLayers:self]; - if (otherLayer) { const mbgl::optional<std::string> belowLayerId{otherLayer.identifier.UTF8String}; style.rawStyle->addLayer(std::move(_pendingLayer), belowLayerId); @@ -54,10 +48,6 @@ { if (self.rawLayer == style.rawStyle->getLayer(self.identifier.UTF8String)) { _pendingLayer = style.rawStyle->removeLayer(self.identifier.UTF8String); - - // We need to ensure that this layer is now released (however, if this layer is about to be - // used by the renderer then it will released once rendering is complete) - [style removeFromManagedLayers:self]; } } @@ -111,7 +101,14 @@ { return [NSString stringWithFormat:@"<%@: %p; identifier = %@; visible = %@>", NSStringFromClass([self class]), (void *)self, self.identifier, - self.visible ? @"YES" : @"NO"]; + self.rawLayer ? (self.visible ? @"YES" : @"NO") : @"(No raw layer)"]; +} + +#pragma mark - Debug methods + +- (void)debugResetRawLayerPeer { + MGLAssertStyleLayerIsValid(); + self.rawLayer->peer.reset(); } @end diff --git a/platform/darwin/src/MGLStyleLayerRetentionManager.mm b/platform/darwin/src/MGLStyleLayerRetentionManager.mm new file mode 100644 index 0000000000..71ebd574ad --- /dev/null +++ b/platform/darwin/src/MGLStyleLayerRetentionManager.mm @@ -0,0 +1,53 @@ +#import "MGLStyle.h" +#import "MGLStyleLayer.h" +#import "MGLStyleLayerRetentionManager_Private.h" +#include <mbgl/style/style.hpp> +#include <mbgl/style/layers/custom_layer.hpp> + +static const NSUInteger MGLStyleLayerRetentionManagerCapacityHint = 100; + +@interface MGLStyleLayerRetentionManager () +@property (nonatomic) NSMapTable<MGLStyleLayer*, NSNumber*> *retentionTable; +@end + +@implementation MGLStyleLayerRetentionManager + +- (instancetype)init { + if ((self = [super init])) { + _retentionTable = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality + valueOptions:NSPointerFunctionsStrongMemory + capacity:MGLStyleLayerRetentionManagerCapacityHint]; + } + return self; +} + +- (BOOL)isManagedLayer:(nullable MGLStyleLayer*)styleLayer { + return [self.retentionTable objectForKey:styleLayer] != nil; +} + +- (void)updateRetainedLayers:(nonnull NSSet<MGLStyleLayer*>*)sourceObjects { + + // Add/update the objects from the source set, with a default lifetime + for (id layer in sourceObjects) { + [self.retentionTable setObject:@(MGLStyleLayerRetentionManagerDefaultLifetime) forKey:layer]; + } +} + +- (void)decrementLifetimes { + // Consider double-buffering the two tables, so we don't keep allocing/deallocing tables. + NSMapTable *retentionTable = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality + valueOptions:NSPointerFunctionsStrongMemory + capacity:MGLStyleLayerRetentionManagerCapacityHint]; + + for (MGLStyleLayer *layer in self.retentionTable) { + NSInteger lifeTime = [[self.retentionTable objectForKey:layer] integerValue]; + + if (lifeTime > 0) { + [retentionTable setObject:@(lifeTime - 1) forKey:layer]; + } + } + + self.retentionTable = retentionTable; +} + +@end diff --git a/platform/darwin/src/MGLStyleLayerRetentionManager_Private.h b/platform/darwin/src/MGLStyleLayerRetentionManager_Private.h new file mode 100644 index 0000000000..0063d342a5 --- /dev/null +++ b/platform/darwin/src/MGLStyleLayerRetentionManager_Private.h @@ -0,0 +1,16 @@ +@class MGLStyle; + +static const NSInteger MGLStyleLayerRetentionManagerDefaultLifetime = 2; + +/** + Object use to manage the retain/release of `MGLStyleLayer`s (currently only `MGLOpenGLStyleLayer`. + Managed layers are given a "lifetime", and this is reset everytime `-updateRetainedLayers:` is called. + The lifetime is decremented with each call to `-decrementLifetimes`, when it reaches 0 the layer + is removed from the manager (potentially releasing it) + */ +@interface MGLStyleLayerRetentionManager : NSObject +- (BOOL)isManagedLayer:(nullable MGLStyleLayer*)styleLayer; +- (void)updateRetainedLayers:(nonnull NSSet<MGLStyleLayer*>*)sourceObjects; +- (void)decrementLifetimes; +@end + diff --git a/platform/darwin/src/MGLStyleLayer_Private.h b/platform/darwin/src/MGLStyleLayer_Private.h index 9bee013c3d..9c93f4c0c9 100644 --- a/platform/darwin/src/MGLStyleLayer_Private.h +++ b/platform/darwin/src/MGLStyleLayer_Private.h @@ -59,7 +59,7 @@ struct LayerWrapper { pointer value stays even after ownership of the object is transferred via `mbgl::Map addLayer`. */ -@property (nonatomic, readonly) mbgl::style::Layer *rawLayer; +@property (nonatomic, readwrite, nullable) mbgl::style::Layer *rawLayer; /** Adds the mbgl style layer that this object represents to the mbgl map below the @@ -80,6 +80,12 @@ struct LayerWrapper { */ - (void)removeFromStyle:(MGLStyle *)style; + +/** + Debug method used for testing - it resets the peer object, essentially disconnecting the raw Layer + from the peer MGLStyleLayer. + */ +- (void)debugResetRawLayerPeer; @end NS_ASSUME_NONNULL_END diff --git a/platform/darwin/src/MGLStyle_Private.h b/platform/darwin/src/MGLStyle_Private.h index cfde78fd06..00985403f5 100644 --- a/platform/darwin/src/MGLStyle_Private.h +++ b/platform/darwin/src/MGLStyle_Private.h @@ -23,6 +23,7 @@ namespace mbgl { @property (nonatomic, readonly, weak) MGLMapView *mapView; @property (nonatomic, readonly) mbgl::style::Style *rawStyle; +@property (nonatomic, readonly) NSMutableSet *managedLayers; - (nullable NS_ARRAY_OF(MGLAttributionInfo *) *)attributionInfosWithFontSize:(CGFloat)fontSize linkColor:(nullable MGLColor *)linkColor; @@ -30,8 +31,6 @@ namespace mbgl { - (void)addToManagedLayers:(MGLStyleLayer*)layer; - (void)removeFromManagedLayers:(MGLStyleLayer*)layer; -- (void)retainLayersUsedDuringRendering; -- (void)releaseLayersUsedDuringRendering; @end @interface MGLStyle (MGLStreetsAdditions) diff --git a/platform/ios/Integration Tests/MBGLIntegrationTests.m b/platform/ios/Integration Tests/MBGLIntegrationTests.m deleted file mode 100644 index f79fac3dcd..0000000000 --- a/platform/ios/Integration Tests/MBGLIntegrationTests.m +++ /dev/null @@ -1,247 +0,0 @@ -#import <XCTest/XCTest.h> - -@import Mapbox; - -@interface MBGLIntegrationTests : XCTestCase <MGLMapViewDelegate> - -@property (nonatomic) MGLMapView *mapView; -@property (nonatomic) MGLStyle *style; - -@end - -@implementation MBGLIntegrationTests { - XCTestExpectation *_styleLoadingExpectation; -} - -- (void)setUp { - [super setUp]; - - [MGLAccountManager setAccessToken:@"pk.feedcafedeadbeefbadebede"]; - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - self.mapView = [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; - self.mapView.delegate = self; - if (!self.mapView.style) { - _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; - [self waitForExpectationsWithTimeout:1 handler:nil]; - } - - UIView *superView = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds]; - [superView addSubview:self.mapView]; - UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; - [window addSubview:superView]; - [window makeKeyAndVisible]; -} - -- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style { - XCTAssertNotNil(mapView.style); - XCTAssertEqual(mapView.style, style); - - [_styleLoadingExpectation fulfill]; -} - -- (void)tearDown { - _styleLoadingExpectation = nil; - self.mapView = nil; - self.style = nil; - - [super tearDown]; -} - -- (MGLStyle *)style { - return self.mapView.style; -} - -- (void)testAddingRemovingOpenGLLayer { - XCTAssertNotNil(self.style); - - // Test fails with 0.1 (presumably because it's < one frame, ie. 1/60) - NSTimeInterval waitInterval = 0.02; - - void(^addRemoveGLLayer)(void) = ^{ - - MGLOpenGLStyleLayer *layer = nil; - __weak id retrievedLayer = nil; - - @autoreleasepool { - layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer atIndex:0]; - layer = nil; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - - retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"]; - XCTAssertNotNil(retrievedLayer); - [self.style removeLayer:retrievedLayer]; - - // We need to run the runloop for a little while so that the following assert will be correct - // this is because although the layer has been removed from the style, there's still a pending - // render (deinitialize) call, that will needs to be handled, which will finally release the - // layer (and then the layer will be dealloc'd when the autorelease pool drains) - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - } - - XCTAssertNil(retrievedLayer); - }; - - addRemoveGLLayer(); - addRemoveGLLayer(); - addRemoveGLLayer(); -} - -- (void)testAddingRemovingOpenGLLayerWithoutRendering { - XCTAssertNotNil(self.style); - - void(^addRemoveGLLayer)(void) = ^{ - MGLOpenGLStyleLayer *layer = nil; - __weak id weakLayer = nil; - - @autoreleasepool { - - layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer atIndex:0]; - weakLayer = layer; - layer = nil; - [self.style removeLayer:weakLayer]; - } - - XCTAssertNil(weakLayer); - }; - - addRemoveGLLayer(); - addRemoveGLLayer(); - addRemoveGLLayer(); -} - -- (void)testReusingOpenGLLayer { - NSTimeInterval waitInterval = 0.02; - - MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer atIndex:0]; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - - [self.style removeLayer:layer]; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - - [self.style insertLayer:layer atIndex:0]; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - - [self.style removeLayer:layer]; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; -} - -// This test does not strictly need to be in this test file/target. Including here for convenience. -- (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle { - MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - __weak id weakLayer = layer; - layer = nil; - - XCTAssertNil(weakLayer); -} - -- (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs { - NSTimeInterval waitInterval = 0.02; - __weak id weakLayer; - - @autoreleasepool { - - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - MGLMapView *mapView2 = [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; - mapView2.delegate = self; - - XCTAssertNil(mapView2.style); - - _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; - [self waitForExpectationsWithTimeout:1 handler:nil]; - -// UIView *superView = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds]; -// [superView addSubview:self.mapView]; -// UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; -// [window addSubview:superView]; -// [window makeKeyAndVisible]; - - MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - weakLayer = layer; - [mapView2.style insertLayer:layer atIndex:0]; - layer = nil; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - [self.mapView setNeedsDisplay]; - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - } - XCTAssertNil(weakLayer); -} - -- (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle { - NSTimeInterval waitInterval = 0.02; - - MGLOpenGLStyleLayer *layer; - __weak id weakLayer; - @autoreleasepool { - layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - weakLayer = layer; - [self.style insertLayer:layer atIndex:0]; - layer = nil; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - - [self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]]; - [self.mapView setNeedsDisplay]; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - } - XCTAssertNil(weakLayer); -} - -- (void)testOpenGLLayerDoesNotLeakWhenStyleChanged { - NSTimeInterval waitInterval = 0.02; - __weak id weakLayer; - - @autoreleasepool { - MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - weakLayer = layer; - [self.style insertLayer:layer atIndex:0]; - layer = nil; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - - NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; - _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; - [self.mapView setStyleURL:styleURL]; - - [self waitForExpectationsWithTimeout:1 handler:nil]; - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - } - XCTAssertNil(weakLayer); -} - -- (void)testReusingOpenGLLayerIdentifier { - NSTimeInterval waitInterval = 0.02; - MGLOpenGLStyleLayer *layer1, *layer2; - @autoreleasepool { - layer1 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer1 atIndex:0]; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - - [self.style removeLayer:layer1]; - - layer2 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; - [self.style insertLayer:layer2 atIndex:0]; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - - [self.style removeLayer:layer2]; - - [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; - } - XCTAssertNotNil(layer1); - XCTAssertNotNil(layer2); - XCTAssertNil([layer1 style]); - XCTAssertNil([layer2 style]); -} - -@end diff --git a/platform/ios/Integration Tests/MBGLIntegrationTests.mm b/platform/ios/Integration Tests/MBGLIntegrationTests.mm new file mode 100644 index 0000000000..5e390bb8e6 --- /dev/null +++ b/platform/ios/Integration Tests/MBGLIntegrationTests.mm @@ -0,0 +1,459 @@ +#import <XCTest/XCTest.h> +#import <objc/message.h> +#import <Mapbox/Mapbox.h> + +#import "src/MGLStyleLayerRetentionManager_Private.h" +#import "src/MGLStyleLayer_Private.h" +#import "../ios/src/MGLMapView_Private.h" + +@interface MBGLIntegrationTests : XCTestCase <MGLMapViewDelegate> + +@property (nonatomic) MGLMapView *mapView; +@property (nonatomic) MGLStyle *style; + +@end + +@implementation MBGLIntegrationTests { + XCTestExpectation *_styleLoadingExpectation; + XCTestExpectation *_renderFinishedExpectation; +} + +#pragma mark - Setup/Teardown + +- (void)setUp { + [super setUp]; + + [MGLAccountManager setAccessToken:@"pk.feedcafedeadbeefbadebede"]; + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; + self.mapView = [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; + self.mapView.delegate = self; + if (!self.mapView.style) { + _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + } + + UIView *superView = [[UIView alloc] initWithFrame:UIScreen.mainScreen.bounds]; + [superView addSubview:self.mapView]; + UIWindow *window = [[UIWindow alloc] initWithFrame:UIScreen.mainScreen.bounds]; + [window addSubview:superView]; + [window makeKeyAndVisible]; +} + +- (void)tearDown { + _styleLoadingExpectation = nil; + self.mapView = nil; + self.style = nil; + + [super tearDown]; +} + +#pragma mark - MGLMapViewDelegate + +- (void)mapView:(MGLMapView *)mapView didFinishLoadingStyle:(MGLStyle *)style { + XCTAssertNotNil(mapView.style); + XCTAssertEqual(mapView.style, style); + + [_styleLoadingExpectation fulfill]; +} + +- (void)mapViewDidFinishRenderingFrame:(MGLMapView *)mapView fullyRendered:(__unused BOOL)fullyRendered { + [_renderFinishedExpectation fulfill]; + _renderFinishedExpectation = nil; +} + +#pragma mark - Utilities + +- (void)waitForMapViewToBeRendered { + [self.mapView setNeedsGLDisplay]; + _renderFinishedExpectation = [self expectationWithDescription:@"Map view should be rendered"]; + [self waitForExpectations:@[_renderFinishedExpectation] timeout:1]; +} + +- (void)waitForManagedLayersToExpire { + NSInteger count = MGLStyleLayerRetentionManagerDefaultLifetime; + while (count--) { + [self waitForMapViewToBeRendered]; + } +} + +- (BOOL)isLayerBeingManaged:(MGLStyleLayer*)layer { + return [self.mapView debugIsStyleLayerBeingManaged:layer]; +} + +- (MGLStyle *)style { + return self.mapView.style; +} + +#pragma mark - Tests + +- (void)testStoringOpenGLLayerInCollections { + // If we change the meaning of equality for MGLStyleLayer we want to know about it - since we + // store layers in both a NSMutableSet and in a NSMapTable + MGLStyleLayer *layer1 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + MGLStyleLayer *layer2 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + + XCTAssertNotEqual(layer1, layer2); // Test diff pointers, in case behind the scenes it tries to return the same object. + XCTAssertNotEqualObjects(layer1, layer2); // Although they have the same identifier, we DO NOT consider them equal. + NSMutableSet *set = [NSMutableSet set]; + [set addObject:layer1]; + [set addObject:layer2]; + XCTAssert(set.count == 2); +} + +- (void)testUsingMapTableWithLayersAsKeys { + __weak MGLStyleLayer *weakLayer = nil; + + @autoreleasepool { + NSMapTable *mapTable = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality + valueOptions:NSPointerFunctionsStrongMemory + capacity:100]; + + MGLStyleLayer *layer1 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + + NSInteger (*retainCount)(id, SEL) = (NSInteger (*)(id, SEL))objc_msgSend; + SEL retainCountSelector = NSSelectorFromString(@"retainCount"); + + NSInteger retainCount1 = retainCount(layer1, retainCountSelector); + XCTAssert(retainCount1 == 1); + + [mapTable setObject:@(1) forKey:layer1]; + + NSInteger retainCount2 = retainCount(layer1, retainCountSelector); + XCTAssert(retainCount2 == 2); + + NSNumber *number1 = [mapTable objectForKey:layer1]; + XCTAssertNotNil(number1); + XCTAssert([number1 integerValue] == 1); + + // Try adding a second layer, and ensuring it's there... + MGLStyleLayer *layer2 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [mapTable setObject:@(2) forKey:layer2]; + + NSNumber *number2 = [mapTable objectForKey:layer2]; + XCTAssertNotNil(number1); + XCTAssertNotEqual(number1, number2); + XCTAssertNotEqualObjects(number1, number2); + XCTAssert([number2 integerValue] == 2); + } + + XCTAssertNil(weakLayer); +} + +// This test does not strictly need to be in this test file/target. Including here for convenience. +- (void)testOpenGLLayerDoesNotLeakWhenCreatedAndDestroyedWithoutAddingToStyle { + MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + __weak id weakLayer = layer; + layer = nil; + + XCTAssertNil(weakLayer); +} + +- (void)testAddingRemovingOpenGLLayerWithoutRendering { + XCTAssertNotNil(self.style); + + void(^addRemoveGLLayer)(void) = ^{ + __weak id weakLayer = nil; + + @autoreleasepool { + MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [self.style insertLayer:layer atIndex:0]; + weakLayer = layer; + + // Nil the layer prior to remove to ensure it's being retained + layer = nil; + [self.style removeLayer:weakLayer]; + } + + XCTAssertNil(weakLayer); + }; + + addRemoveGLLayer(); + addRemoveGLLayer(); + addRemoveGLLayer(); +} + +- (void)testReusingOpenGLLayerIdentifier { + __weak MGLOpenGLStyleLayer *weakLayer2; + + @autoreleasepool { + MGLOpenGLStyleLayer *layer1 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [self.style insertLayer:layer1 atIndex:0]; + [self waitForMapViewToBeRendered]; + [self.style removeLayer:layer1]; + + MGLOpenGLStyleLayer *layer2 = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + weakLayer2 = layer2; + + XCTAssertNotNil(layer2); + XCTAssert(layer1 != layer2); + + [self.style insertLayer:layer2 atIndex:0]; + [self waitForMapViewToBeRendered]; + [self.style removeLayer:layer2]; + + XCTAssertNil([layer1 style]); + XCTAssertNil([layer2 style]); + } + + // At this point, layer2 (and layer1) should still be around, since the render process needs to + // keep a reference to them. + XCTAssertNotNil(weakLayer2); + + // Let render loop run enough to release the layers + [self waitForManagedLayersToExpire]; + XCTAssertNil(weakLayer2); +} + +// Unlike MGLOpenGLStyleLayers other layers are NOT retained, and should be re-created when fetching +// with layerWithIdentifier. This test is to check that assumption (and should fire if we change how +// and if other layer types are retained) +- (void)testUserAddedLineLayersAreNotEqual { + NSString *filename = [[NSBundle mainBundle] pathForResource:@"polyline" ofType:@"geojson"]; + NSData *shapeData = [NSData dataWithContentsOfFile:filename]; + MGLShape *shape = [MGLShape shapeWithData:shapeData encoding:NSUTF8StringEncoding error:nil]; + MGLSource *source1 = [[MGLShapeSource alloc] initWithIdentifier:@"polyline" shape:shape options:nil]; + + [self.style addSource:source1]; + + MGLStyleLayer *lineLayer1 = [[MGLLineStyleLayer alloc] initWithIdentifier:@"line-layer" source:source1]; + [self.style insertLayer:lineLayer1 atIndex:0]; + + // Disconnect the raw layer from the MGLStyleLayer, so that -layerWithIdentifier: is forced to + // re-create the Obj-C layer. + [lineLayer1 debugResetRawLayerPeer]; + + MGLStyleLayer *lineLayer2 = [self.style layerWithIdentifier:@"line-layer"]; + XCTAssertNotNil(lineLayer2); + XCTAssert(lineLayer1 != lineLayer2); + XCTAssertEqualObjects(lineLayer1.identifier, lineLayer2.identifier); + XCTAssertNotEqualObjects(lineLayer1, lineLayer2); // Not currently considered equal. + + // Now fetch the source. This time we don't reset the peer ptr, so the following should return the + // original source object + MGLSource *source2 = [self.style sourceWithIdentifier:@"polyline"]; + XCTAssertNotNil(source2); + XCTAssert(source1 == source2); +} + + +- (void)testAddingRemovingOpenGLLayer { + XCTAssertNotNil(self.style); + + void(^addRemoveGLLayer)(void) = ^{ + + __weak id retrievedLayer = nil; + + @autoreleasepool { + MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [self.style insertLayer:layer atIndex:0]; + + NSUInteger address1 = (NSUInteger)layer; + layer = nil; + + // We want to ensure the MGLOpenGLStyleLayer's raw Layer's Impl gets handed to a RenderLayer + // (which in turn has a raw context pointer back to the layer) + [self waitForMapViewToBeRendered]; + + retrievedLayer = [self.style layerWithIdentifier:@"gl-layer"]; + NSUInteger address2 = (NSUInteger)retrievedLayer; + + XCTAssertNotNil(retrievedLayer); + + // These two should be the same object in the case of MGLOpenGLStyleLayer + XCTAssert(address1 == address2); + + [self.style removeLayer:retrievedLayer]; + + // We need to run the runloop for a little while (2 trips through the renderer) to allow + // the managed layer to be released. This is because it takes 2 renders before the RenderLayer + // (and the associated Impl) are destroyed. + [self waitForManagedLayersToExpire]; + } + + XCTAssertNil(retrievedLayer); + }; + + addRemoveGLLayer(); + addRemoveGLLayer(); + addRemoveGLLayer(); +} + +- (void)testReusingOpenGLLayer { + NSTimeInterval waitInterval = 0.02; + + NSLog(@"testReusingOpenGLLayer - init and insert layer"); + MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + [self.style insertLayer:layer atIndex:0]; + + [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; + + NSLog(@"testReusingOpenGLLayer - remove layer %p", layer); + [self.style removeLayer:layer]; + + [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; + + NSLog(@"testReusingOpenGLLayer - re-insert layer %p", layer); + + [self.style insertLayer:layer atIndex:0]; + + [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; + + NSLog(@"testReusingOpenGLLayer - re-remove layer %p", layer); + + [self.style removeLayer:layer]; + + [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; +} + +- (void)testOpenGLLayerDoesNotLeakWhenRemovedFromStyle { + +// MGLOpenGLStyleLayer *layer; + __weak id weakLayer; + @autoreleasepool { + MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + weakLayer = layer; + [self.style insertLayer:layer atIndex:0]; + layer = nil; + + // Run the render loop, so the layer gets used/managed. + [self waitForMapViewToBeRendered]; + // [[NSRunLoop currentRunLoop] runUntilDate:[[NSDate date] dateByAddingTimeInterval:waitInterval]]; + + [self.style removeLayer:[self.style layerWithIdentifier:@"gl-layer"]]; + } + + MGLStyleLayer *layer2 = weakLayer; + + XCTAssertNotNil(weakLayer); + XCTAssert([self isLayerBeingManaged:weakLayer]); + [self waitForMapViewToBeRendered]; + + XCTAssertNotNil(weakLayer); + XCTAssert([self isLayerBeingManaged:weakLayer]); + [self waitForMapViewToBeRendered]; + + XCTAssertNotNil(weakLayer); + XCTAssert(![self isLayerBeingManaged:weakLayer]); + + layer2 = nil; + XCTAssertNil(weakLayer); +} + +- (void)testUserAddedLineLayerIsReleasedWhenStyleChanged { + __weak MGLStyleLayer* weakLayer1 = nil; + __weak MGLStyleLayer* weakLayer2 = nil; + + __unsafe_unretained MGLStyleLayer* unsafeLayer1 = nil; + __unsafe_unretained MGLStyleLayer* unsafeLayer2 = nil; + + // Add a line layer + @autoreleasepool { + NSString *filename = [[NSBundle mainBundle] pathForResource:@"polyline" ofType:@"geojson"]; + NSData *shapeData = [NSData dataWithContentsOfFile:filename]; + MGLShape *shape = [MGLShape shapeWithData:shapeData encoding:NSUTF8StringEncoding error:nil]; + MGLSource *source1 = [[MGLShapeSource alloc] initWithIdentifier:@"polyline" shape:shape options:nil]; + + [self.style addSource:source1]; + MGLStyleLayer *lineLayer1 = [[MGLLineStyleLayer alloc] initWithIdentifier:@"line-layer" source:source1]; + [self.style insertLayer:lineLayer1 atIndex:0]; + + unsafeLayer1 = lineLayer1; + weakLayer1 = lineLayer1; + } + XCTAssertNil(weakLayer1); + + // Look for the same layer by Id + @autoreleasepool { + MGLStyleLayer *lineLayer2 = [self.style layerWithIdentifier:@"line-layer"]; + MGLSource *source2 = [self.style sourceWithIdentifier:@"polyline"]; + + XCTAssertNotNil(lineLayer2); + XCTAssertNotNil(source2); + + unsafeLayer2 = lineLayer2; + weakLayer2 = lineLayer2; + } + XCTAssertNil(weakLayer2); + + // Since we only add MGLOpenGLStyleLayers to our retain/release management set, the above layers + // will have been released, but we want to check that the second one was re-created. So just + // check pointer values + XCTAssert(unsafeLayer1); + XCTAssert(unsafeLayer2); + XCTAssert(unsafeLayer1 != unsafeLayer2); + + // Swap style + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; + _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; + [self.mapView setStyleURL:styleURL]; + [self waitForExpectations:@[_styleLoadingExpectation] timeout:10]; + + // Asking the style for the layer should return nil + { + MGLStyleLayer *lineLayer = [self.style layerWithIdentifier:@"line-layer"]; + MGLSource *lineSource = [self.style sourceWithIdentifier:@"polyline"]; + XCTAssertNil(lineLayer); + XCTAssertNil(lineSource); + } +} + +- (void)testOpenGLLayerDoesNotLeakWhenStyleChanged { + __weak id weakLayer; + + @autoreleasepool { + { + MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + weakLayer = layer; + [self.style insertLayer:layer atIndex:0]; + layer = nil; + } + // Unlike other MGLStyleLayers, MGLOpenGLStyleLayers are managed for retain/release purposes + XCTAssertNotNil(weakLayer); + + [self waitForMapViewToBeRendered]; + + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; + _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; + [self.mapView setStyleURL:styleURL]; + [self waitForExpectations:@[_styleLoadingExpectation] timeout:10]; + } + + [self waitForManagedLayersToExpire]; + + // Asking the style for the layer should return nil + MGLStyleLayer *layer2 = [self.mapView.style layerWithIdentifier:@"gl-layer"]; + XCTAssertNil(layer2); + XCTAssertNil(weakLayer); +} + + +- (void)testOpenGLLayerDoesNotLeakWhenMapViewDeallocs { + __weak id weakLayer; + + @autoreleasepool { + + NSURL *styleURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"one-liner" withExtension:@"json"]; + MGLMapView *mapView2 = [[MGLMapView alloc] initWithFrame:UIScreen.mainScreen.bounds styleURL:styleURL]; + mapView2.delegate = self; + + XCTAssertNil(mapView2.style); + + _styleLoadingExpectation = [self expectationWithDescription:@"Map view should finish loading style."]; + [self waitForExpectationsWithTimeout:1 handler:nil]; + + MGLOpenGLStyleLayer *layer = [[MGLOpenGLStyleLayer alloc] initWithIdentifier:@"gl-layer"]; + weakLayer = layer; + [mapView2.style insertLayer:layer atIndex:0]; + layer = nil; + + // Let the render process fire, to ensure a RenderLayer gets created with a raw pointer back + // to the layer (and it should be retained). + [self waitForMapViewToBeRendered]; + + // We don't wait for the layers to be released here, we'll just let the MGLMapView dealloc + // so everything should be cleaned up. + } + XCTAssertNil(weakLayer); +} + +@end diff --git a/platform/ios/ios.xcodeproj/project.pbxproj b/platform/ios/ios.xcodeproj/project.pbxproj index af26654380..ff1d447f78 100644 --- a/platform/ios/ios.xcodeproj/project.pbxproj +++ b/platform/ios/ios.xcodeproj/project.pbxproj @@ -20,7 +20,7 @@ 07D947521F67488800E37934 /* MGLAbstractShapeSource.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D9474F1F67487E00E37934 /* MGLAbstractShapeSource.h */; settings = {ATTRIBUTES = (Public, ); }; }; 07D947531F67488E00E37934 /* MGLAbstractShapeSource_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 07D9474E1F67487E00E37934 /* MGLAbstractShapeSource_Private.h */; }; 07D947541F67489200E37934 /* MGLAbstractShapeSource.mm in Sources */ = {isa = PBXBuildFile; fileRef = 07D947501F67487E00E37934 /* MGLAbstractShapeSource.mm */; }; - 16376B0A1FFD9DAF0000563E /* MBGLIntegrationTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 16376B091FFD9DAF0000563E /* MBGLIntegrationTests.m */; }; + 16376B0A1FFD9DAF0000563E /* MBGLIntegrationTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 16376B091FFD9DAF0000563E /* MBGLIntegrationTests.mm */; }; 16376B331FFDB4B40000563E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 16376B321FFDB4B40000563E /* AppDelegate.m */; }; 16376B3B1FFDB4B40000563E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 16376B3A1FFDB4B40000563E /* Assets.xcassets */; }; 16376B3E1FFDB4B40000563E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 16376B3C1FFDB4B40000563E /* LaunchScreen.storyboard */; }; @@ -308,6 +308,13 @@ AC518E04201BB56100EBC820 /* MGLTelemetryConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */; }; CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; CA55CD42202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */ = {isa = PBXBuildFile; fileRef = CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CA67C7FB204CFC9000C1CBB4 /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA8847D21CBAF91600AB86E3 /* Mapbox.framework */; }; + CA67C7FD204CFCAA00C1CBB4 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CA67C7FC204CFCAA00C1CBB4 /* UIKit.framework */; }; + CA7F25182048841E00896957 /* polyline.geojson in Resources */ = {isa = PBXBuildFile; fileRef = DA1DC96D1CB6C6CE006E619F /* polyline.geojson */; }; + CA8F66E62049DD2900B5B9DC /* MGLStyleLayerRetentionManager_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA8F66E42049DD2900B5B9DC /* MGLStyleLayerRetentionManager_Private.h */; }; + CA8F66E72049DD2900B5B9DC /* MGLStyleLayerRetentionManager_Private.h in Headers */ = {isa = PBXBuildFile; fileRef = CA8F66E42049DD2900B5B9DC /* MGLStyleLayerRetentionManager_Private.h */; }; + CA8F66E82049DD2900B5B9DC /* MGLStyleLayerRetentionManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA8F66E52049DD2900B5B9DC /* MGLStyleLayerRetentionManager.mm */; }; + CA8F66E92049DD2900B5B9DC /* MGLStyleLayerRetentionManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = CA8F66E52049DD2900B5B9DC /* MGLStyleLayerRetentionManager.mm */; }; CAAD2BD62041EF05003881EC /* Mapbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA4A26961CB6E795000B7809 /* Mapbox.framework */; }; CAAD2BD72041EF05003881EC /* Mapbox.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA4A26961CB6E795000B7809 /* Mapbox.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; DA00FC8E1D5EEB0D009AABC8 /* MGLAttributionInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -685,7 +692,7 @@ 07D9474F1F67487E00E37934 /* MGLAbstractShapeSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAbstractShapeSource.h; sourceTree = "<group>"; }; 07D947501F67487E00E37934 /* MGLAbstractShapeSource.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAbstractShapeSource.mm; sourceTree = "<group>"; }; 16376B071FFD9DAF0000563E /* integration.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = integration.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 16376B091FFD9DAF0000563E /* MBGLIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MBGLIntegrationTests.m; sourceTree = "<group>"; }; + 16376B091FFD9DAF0000563E /* MBGLIntegrationTests.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MBGLIntegrationTests.mm; sourceTree = "<group>"; }; 16376B0B1FFD9DAF0000563E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; 16376B2F1FFDB4B40000563E /* Integration Test Harness.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Integration Test Harness.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 16376B311FFDB4B40000563E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; @@ -867,6 +874,9 @@ AC518DFD201BB55A00EBC820 /* MGLTelemetryConfig.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLTelemetryConfig.h; sourceTree = "<group>"; }; AC518DFE201BB55A00EBC820 /* MGLTelemetryConfig.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MGLTelemetryConfig.m; sourceTree = "<group>"; }; CA55CD3E202C16AA00CE7095 /* MGLCameraChangeReason.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLCameraChangeReason.h; sourceTree = "<group>"; }; + CA67C7FC204CFCAA00C1CBB4 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; + CA8F66E42049DD2900B5B9DC /* MGLStyleLayerRetentionManager_Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MGLStyleLayerRetentionManager_Private.h; sourceTree = "<group>"; }; + CA8F66E52049DD2900B5B9DC /* MGLStyleLayerRetentionManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLStyleLayerRetentionManager.mm; sourceTree = "<group>"; }; DA00FC8C1D5EEB0D009AABC8 /* MGLAttributionInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MGLAttributionInfo.h; sourceTree = "<group>"; }; DA00FC8D1D5EEB0D009AABC8 /* MGLAttributionInfo.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = MGLAttributionInfo.mm; sourceTree = "<group>"; }; DA0CD58F1CF56F6A00A5F5A5 /* MGLFeatureTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = MGLFeatureTests.mm; path = ../../darwin/test/MGLFeatureTests.mm; sourceTree = "<group>"; }; @@ -1140,6 +1150,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + CA67C7FD204CFCAA00C1CBB4 /* UIKit.framework in Frameworks */, + CA67C7FB204CFC9000C1CBB4 /* Mapbox.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1205,7 +1217,7 @@ 16376B081FFD9DAF0000563E /* Integration Tests */ = { isa = PBXGroup; children = ( - 16376B091FFD9DAF0000563E /* MBGLIntegrationTests.m */, + 16376B091FFD9DAF0000563E /* MBGLIntegrationTests.mm */, 16376B0B1FFD9DAF0000563E /* Info.plist */, ); path = "Integration Tests"; @@ -1496,6 +1508,7 @@ DA1DC9921CB6DF24006E619F /* Frameworks */ = { isa = PBXGroup; children = ( + CA67C7FC204CFCAA00C1CBB4 /* UIKit.framework */, 55D120AD1F791018004B6D81 /* libmbgl-loop-darwin.a */, 55D120AB1F791015004B6D81 /* libmbgl-filesource.a */, 55D120A91F79100C004B6D81 /* libmbgl-filesource.a */, @@ -1612,6 +1625,8 @@ DA8847EE1CBAFA5100AB86E3 /* MGLTypes.h */, DA8848111CBAFA6200AB86E3 /* MGLTypes.m */, 35E1A4D71D74336F007AA97F /* MGLValueEvaluator.h */, + CA8F66E42049DD2900B5B9DC /* MGLStyleLayerRetentionManager_Private.h */, + CA8F66E52049DD2900B5B9DC /* MGLStyleLayerRetentionManager.mm */, ); name = Foundation; path = ../darwin/src; @@ -1924,6 +1939,7 @@ 9654C1261FFC1AB900DB6A19 /* MGLPolyline_Private.h in Headers */, 40F887701D7A1E58008ECB67 /* MGLShapeSource_Private.h in Headers */, 350098DC1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, + CA8F66E62049DD2900B5B9DC /* MGLStyleLayerRetentionManager_Private.h in Headers */, DA8848231CBAFA6200AB86E3 /* MGLOfflineStorage_Private.h in Headers */, 404326891D5B9B27007111BD /* MGLAnnotationContainerView_Private.h in Headers */, CA55CD41202C16AA00CE7095 /* MGLCameraChangeReason.h in Headers */, @@ -2122,6 +2138,7 @@ DABFB8651CBE99E500D62B32 /* MGLOverlay.h in Headers */, 35E79F211D41266300957B9E /* MGLStyleLayer_Private.h in Headers */, 350098DD1D484E60004B2AF0 /* NSValue+MGLStyleAttributeAdditions.h in Headers */, + CA8F66E72049DD2900B5B9DC /* MGLStyleLayerRetentionManager_Private.h in Headers */, DABFB8681CBE99E500D62B32 /* MGLPolyline.h in Headers */, 96E516DF200054FB00A02306 /* MGLShape_Private.h in Headers */, DABFB86F1CBE9A0F00D62B32 /* MGLMapView.h in Headers */, @@ -2449,6 +2466,7 @@ buildActionMask = 2147483647; files = ( 16376B3E1FFDB4B40000563E /* LaunchScreen.storyboard in Resources */, + CA7F25182048841E00896957 /* polyline.geojson in Resources */, 16376B3B1FFDB4B40000563E /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2546,7 +2564,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 16376B0A1FFD9DAF0000563E /* MBGLIntegrationTests.m in Sources */, + 16376B0A1FFD9DAF0000563E /* MBGLIntegrationTests.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -2692,6 +2710,7 @@ 404C26E41D89B877000AA13D /* MGLTileSource.mm in Sources */, 07D947541F67489200E37934 /* MGLAbstractShapeSource.mm in Sources */, 355AE0011E9281DA00F3939D /* MGLScaleBar.mm in Sources */, + CA8F66E82049DD2900B5B9DC /* MGLStyleLayerRetentionManager.mm in Sources */, DA88481D1CBAFA6200AB86E3 /* MGLMapCamera.mm in Sources */, DACA86282019218600E9693A /* MGLRasterDEMSource.mm in Sources */, DA8848261CBAFA6200AB86E3 /* MGLPolygon.mm in Sources */, @@ -2785,6 +2804,7 @@ 355AE0021E9281DA00F3939D /* MGLScaleBar.mm in Sources */, 4018B1C81CDC287F00F666AF /* MGLAnnotationView.mm in Sources */, 07D8C6FB1F67560100381808 /* MGLComputedShapeSource.mm in Sources */, + CA8F66E92049DD2900B5B9DC /* MGLStyleLayerRetentionManager.mm in Sources */, DAA4E4341CBB730400178DFB /* MGLFaux3DUserLocationAnnotationView.m in Sources */, DACA86292019218600E9693A /* MGLRasterDEMSource.mm in Sources */, 35B82BFB1D6C5F8400B1B721 /* NSPredicate+MGLAdditions.mm in Sources */, @@ -3032,10 +3052,10 @@ /* Begin XCBuildConfiguration section */ 16376B0E1FFD9DAF0000563E /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 55D8C9941D0F133500F42F10 /* config.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; @@ -3046,9 +3066,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = "Integration Tests/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_CFLAGS = "-fvisibility=hidden"; PRODUCT_BUNDLE_IDENTIFIER = "com.mapbox.integration-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -3058,10 +3080,10 @@ }; 16376B0F1FFD9DAF0000563E /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 55D8C9941D0F133500F42F10 /* config.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; @@ -3072,9 +3094,11 @@ CODE_SIGN_IDENTITY = "iPhone Developer"; CODE_SIGN_STYLE = Automatic; GCC_C_LANGUAGE_STANDARD = gnu11; + HEADER_SEARCH_PATHS = "$(mbgl_core_INCLUDE_DIRECTORIES)"; INFOPLIST_FILE = "Integration Tests/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 11.2; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_CFLAGS = "-fvisibility=hidden"; PRODUCT_BUNDLE_IDENTIFIER = "com.mapbox.integration-tests"; PRODUCT_NAME = "$(TARGET_NAME)"; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/platform/ios/src/MGLMapView.mm b/platform/ios/src/MGLMapView.mm index 92ee50ce3d..91121670a2 100644 --- a/platform/ios/src/MGLMapView.mm +++ b/platform/ios/src/MGLMapView.mm @@ -65,6 +65,7 @@ #import "MGLScaleBar.h" #import "MGLStyle_Private.h" #import "MGLStyleLayer_Private.h" +#import "MGLStyleLayerRetentionManager_Private.h" #import "MGLMapboxEvents.h" #import "MGLSDKUpdateChecker.h" #import "MGLCompactCalloutView.h" @@ -200,6 +201,7 @@ public: @property (nonatomic) NS_MUTABLE_ARRAY_OF(NSLayoutConstraint *) *attributionButtonConstraints; @property (nonatomic, readwrite) MGLStyle *style; +@property (nonatomic) MGLStyleLayerRetentionManager* styleLayerRetentionManager; @property (nonatomic) UITapGestureRecognizer *singleTapGestureRecognizer; @@ -393,6 +395,8 @@ public: [self createGLView]; } + _styleLayerRetentionManager = [[MGLStyleLayerRetentionManager alloc] init]; + // setup accessibility // // self.isAccessibilityElement = YES; @@ -5557,7 +5561,7 @@ public: return; } - [self.style retainLayersUsedDuringRendering]; + [self.styleLayerRetentionManager updateRetainedLayers:self.style.managedLayers]; if ([self.delegate respondsToSelector:@selector(mapViewWillStartRenderingFrame:)]) { @@ -5577,13 +5581,12 @@ public: } [self updateAnnotationViews]; [self updateCalloutView]; + [self.styleLayerRetentionManager decrementLifetimes]; + if ([self.delegate respondsToSelector:@selector(mapViewDidFinishRenderingFrame:fullyRendered:)]) { [self.delegate mapViewDidFinishRenderingFrame:self fullyRendered:fullyRendered]; } - - [self.style releaseLayersUsedDuringRendering]; - } - (void)mapViewWillStartRenderingMap { @@ -6355,4 +6358,9 @@ private: self.showsUserHeadingIndicator = showsHeading; } +- (BOOL)debugIsStyleLayerBeingManaged:(MGLStyleLayer*)layer { + return [self.styleLayerRetentionManager isManagedLayer:layer]; +} + + @end diff --git a/platform/ios/src/MGLMapView_Private.h b/platform/ios/src/MGLMapView_Private.h index 482ab55c5e..00900c9ef2 100644 --- a/platform/ios/src/MGLMapView_Private.h +++ b/platform/ios/src/MGLMapView_Private.h @@ -5,6 +5,8 @@ namespace mbgl { class Renderer; } +@class MGLStyleLayer; + /// Minimum size of an annotation’s accessibility element. extern const CGSize MGLAnnotationAccessibilityElementMinimumSize; @@ -26,4 +28,6 @@ extern const CGSize MGLAnnotationAccessibilityElementMinimumSize; /** Empties the in-memory tile cache. */ - (void)didReceiveMemoryWarning; +/** Debug method, used in testing, to check if a layer's retain/release is being managed */ +- (BOOL)debugIsStyleLayerBeingManaged:(MGLStyleLayer*)layer; @end diff --git a/src/mbgl/style/layers/custom_layer.cpp b/src/mbgl/style/layers/custom_layer.cpp index 854c771847..40013f3422 100644 --- a/src/mbgl/style/layers/custom_layer.cpp +++ b/src/mbgl/style/layers/custom_layer.cpp @@ -10,19 +10,27 @@ CustomLayer::CustomLayer(const std::string& layerID, CustomLayerRenderFunction render, CustomLayerContextLostFunction contextLost, CustomLayerDeinitializeFunction deinit, + CustomLayerDeallocationFunction deallocation, void* context) - : Layer(makeMutable<Impl>(layerID, init, render, contextLost, deinit, context)) { + : Layer(makeMutable<Impl>(layerID, init, render, contextLost, deinit, context)), + deallocationFn(deallocation) { } CustomLayer::CustomLayer(const std::string& layerID, CustomLayerInitializeFunction init, CustomLayerRenderFunction render, CustomLayerDeinitializeFunction deinit, + CustomLayerDeallocationFunction deallocation, void* context) - : Layer(makeMutable<Impl>(layerID, init, render, nullptr, deinit, context)) { + : Layer(makeMutable<Impl>(layerID, init, render, nullptr, deinit, context)), + deallocationFn(deallocation) { } -CustomLayer::~CustomLayer() = default; +CustomLayer::~CustomLayer() { + if (deallocationFn) { + deallocationFn(&peer); + } +}; const CustomLayer::Impl& CustomLayer::impl() const { return static_cast<const Impl&>(*baseImpl); |