From 9732a8e9c03562c3f331da202e5b7f932bcd6130 Mon Sep 17 00:00:00 2001 From: Eugene Nikolskyi Date: Mon, 22 Jun 2015 12:18:45 -0700 Subject: [PATCH] DiscoveryManager: pause and resume discovery providers on app state change --- .../Discovery/DiscoveryManagerTests.m | 129 ++++++++++++++++++ Discovery/DiscoveryManager.m | 85 +++++++----- Discovery/DiscoveryManager_Private.h | 11 ++ Discovery/DiscoveryProvider.h | 12 ++ Discovery/DiscoveryProvider.m | 8 ++ 5 files changed, 210 insertions(+), 35 deletions(-) create mode 100644 ConnectSDKTests/Discovery/DiscoveryManagerTests.m diff --git a/ConnectSDKTests/Discovery/DiscoveryManagerTests.m b/ConnectSDKTests/Discovery/DiscoveryManagerTests.m new file mode 100644 index 0000000..83f8920 --- /dev/null +++ b/ConnectSDKTests/Discovery/DiscoveryManagerTests.m @@ -0,0 +1,129 @@ +// +// DiscoveryManagerTests.m +// ConnectSDK +// +// Created by Eugene Nikolskyi on 2015-06-19. +// Copyright (c) 2015 LG Electronics. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#import "DiscoveryManager_Private.h" + +#import "AppStateChangeNotifier.h" +#import "DiscoveryProvider.h" + +#import "OCMArg+ArgumentCaptor.h" + +@interface MockDeviceService : DeviceService @end +@implementation MockDeviceService + ++ (NSDictionary *)discoveryParameters { + return @{@"serviceId": @"mockService"}; +} + +@end + + + +@interface DiscoveryManagerTests : XCTestCase + +@property (nonatomic, strong) DiscoveryManager *discoveryManager; +@property (nonatomic, strong) id /*AppStateChangeNotifier **/ stateNotifierMock; +@property (nonatomic, strong) id /*DiscoveryProvider **/ discoveryProviderMock; + +@end + +@implementation DiscoveryManagerTests + +#pragma mark - Setup + +- (void)setUp { + [super setUp]; + + self.stateNotifierMock = OCMClassMock([AppStateChangeNotifier class]); + self.discoveryProviderMock = OCMClassMock([DiscoveryProvider class]); + self.discoveryManager = [self createDiscoveryManager]; +} + +- (DiscoveryManager *)createDiscoveryManager { + DiscoveryManager *discoveryManager = [[DiscoveryManager alloc] + initWithAppStateChangeNotifier:self.stateNotifierMock]; + [discoveryManager registerDeviceService:[MockDeviceService class] + withDiscoveryProviderFactory:^{ + return self.discoveryProviderMock; + }]; + + return discoveryManager; +} + +- (void)tearDown { + [self.discoveryManager stopDiscovery]; + self.discoveryManager = nil; + self.stateNotifierMock = nil; + + [super tearDown]; +} + +#pragma mark - UIApplication State Change Tests + +- (void)testDefaultStateNotifierShouldBeCreated { + DiscoveryManager *discoveryManager = [DiscoveryManager new]; + XCTAssertNotNil(discoveryManager.appStateChangeNotifier, + @"a real AppStateChangeNotifier should be created"); +} + +- (void)testStartDiscoveryShouldStartListeningStateNotifier { + OCMExpect([self.stateNotifierMock startListening]); + [self.discoveryManager startDiscovery]; + OCMVerifyAll(self.stateNotifierMock); +} + +- (void)testStopDiscoveryShouldStopListeningStateNotifier { + OCMExpect([self.stateNotifierMock stopListening]); + [self.discoveryManager startDiscovery]; + [self.discoveryManager stopDiscovery]; + OCMVerifyAll(self.stateNotifierMock); +} + +- (void)testBackgroundingShouldPauseDiscoveryProviders { + AppStateChangeBlock backgroundStateBlock; + OCMExpect([self.stateNotifierMock setDidBackgroundBlock: + [OCMArg captureBlockTo:&backgroundStateBlock]]); + + DiscoveryManager *discoveryManager = [self createDiscoveryManager]; + [discoveryManager startDiscovery]; + + OCMExpect([self.discoveryProviderMock pauseDiscovery]); + backgroundStateBlock(); + OCMVerifyAll(self.discoveryProviderMock); +} + +- (void)testForegroundingShouldResumeDiscoveryProviders { + AppStateChangeBlock backgroundStateBlock; + AppStateChangeBlock foregroundStateBlock; + OCMExpect([self.stateNotifierMock setDidBackgroundBlock: + [OCMArg captureBlockTo:&backgroundStateBlock]]); + OCMExpect([self.stateNotifierMock setDidForegroundBlock: + [OCMArg captureBlockTo:&foregroundStateBlock]]); + + DiscoveryManager *discoveryManager = [self createDiscoveryManager]; + [discoveryManager startDiscovery]; + + backgroundStateBlock(); + OCMExpect([self.discoveryProviderMock resumeDiscovery]); + foregroundStateBlock(); + OCMVerifyAll(self.discoveryProviderMock); +} + +@end diff --git a/Discovery/DiscoveryManager.m b/Discovery/DiscoveryManager.m index c68f0c1..0472d00 100644 --- a/Discovery/DiscoveryManager.m +++ b/Discovery/DiscoveryManager.m @@ -35,6 +35,8 @@ #import "ServiceConfigDelegate.h" #import "CapabilityFilter.h" +#import "AppStateChangeNotifier.h" + #import @interface DiscoveryManager() @@ -96,7 +98,13 @@ - (void) setDeviceStore:(id )deviceStore _useDeviceStore = (_deviceStore != nil); } -- (instancetype) init +- (instancetype)init { + return [self initWithAppStateChangeNotifier:nil]; +} + +#pragma mark - Private Init + +- (instancetype) initWithAppStateChangeNotifier:(nullable AppStateChangeNotifier *)stateNotifier { self = [super init]; @@ -112,6 +120,17 @@ - (instancetype) init _allDevices = [[NSMutableDictionary alloc] init]; _compatibleDevices = [[NSMutableDictionary alloc] init]; + _appStateChangeNotifier = stateNotifier ?: [AppStateChangeNotifier new]; + __weak typeof(self) wself = self; + _appStateChangeNotifier.didBackgroundBlock = ^{ + typeof(self) sself = wself; + [sself pauseDiscovery]; + }; + _appStateChangeNotifier.didForegroundBlock = ^{ + typeof(self) sself = wself; + [sself resumeDiscovery]; + }; + [self startSSIDTimer]; } @@ -445,8 +464,7 @@ - (void) startDiscovery [service startDiscovery]; }]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hAppDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(hAppDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil]; + [self.appStateChangeNotifier startListening]; } - (void) stopDiscovery @@ -462,8 +480,35 @@ - (void) stopDiscovery if (!_shouldResumeSearch) { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [self.appStateChangeNotifier stopListening]; + } +} + +/// Pauses all discovery providers and the SSID change timer. +- (void)pauseDiscovery { + // moved from -hAppDidEnterBackground: + [self stopSSIDTimer]; + + if (_searching) + { + _searching = NO; + _shouldResumeSearch = YES; + + [self.discoveryProviders makeObjectsPerformSelector:@selector(pauseDiscovery)]; + } +} + +/// Resumes all discovery providers and the SSID change timer. +- (void)resumeDiscovery { + // moved from -hAppDidBecomeActive: + [self startSSIDTimer]; + + if (_shouldResumeSearch) + { + _searching = YES; + _shouldResumeSearch = NO; + + [self.discoveryProviders makeObjectsPerformSelector:@selector(resumeDiscovery)]; } } @@ -654,36 +699,6 @@ - (ConnectableDevice *) lookupMatchingDeviceForDeviceStore:(ServiceConfig *)serv return foundDevice; } -#pragma mark - Handle background state - -- (void) hAppDidEnterBackground:(NSNotification *)notification -{ - [self stopSSIDTimer]; - - if (_searching) - { - _shouldResumeSearch = YES; - [self stopDiscovery]; - } -} - -- (void) hAppDidBecomeActive:(NSNotification *)notification -{ - [self startSSIDTimer]; - - if (_shouldResumeSearch) - { - _shouldResumeSearch = NO; - [self startDiscovery]; - } -} - -- (void) dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; -} - #pragma mark - Device Picker creation - (DevicePicker *) devicePicker diff --git a/Discovery/DiscoveryManager_Private.h b/Discovery/DiscoveryManager_Private.h index eba9dfe..e8787ba 100644 --- a/Discovery/DiscoveryManager_Private.h +++ b/Discovery/DiscoveryManager_Private.h @@ -20,10 +20,20 @@ #import "DiscoveryManager.h" +@class AppStateChangeNotifier; @class DiscoveryProvider; +NS_ASSUME_NONNULL_BEGIN @interface DiscoveryManager () +/// An @c AppStateChangeNotifier that allows to track app state changes. +@property (nonatomic, readonly) AppStateChangeNotifier *appStateChangeNotifier; + + +/// Initializes the instance with the given @c AppStateChangeNotifier. Using +/// @c nil parameter will create real object. +- (instancetype)initWithAppStateChangeNotifier:(nullable AppStateChangeNotifier *)stateNotifier; + /** * Registers a service with the given @c deviceClass and a @c DiscoveryProvider * created by the @c providerFactory. @@ -32,3 +42,4 @@ withDiscoveryProviderFactory:(DiscoveryProvider *(^)(void))providerFactory; @end +NS_ASSUME_NONNULL_END diff --git a/Discovery/DiscoveryProvider.h b/Discovery/DiscoveryProvider.h index e09ff10..5c15730 100644 --- a/Discovery/DiscoveryProvider.h +++ b/Discovery/DiscoveryProvider.h @@ -70,4 +70,16 @@ */ - (void) stopDiscovery; +/** + * Pauses the discovery while the app is in background. Calls @c -stopDiscovery + * by default. + */ +- (void)pauseDiscovery; + +/** + * Resumes the discovery when the app has foregrounded. Calls @c -startDiscovery + * be default. + */ +- (void)resumeDiscovery; + @end diff --git a/Discovery/DiscoveryProvider.m b/Discovery/DiscoveryProvider.m index 8d679e7..396c077 100644 --- a/Discovery/DiscoveryProvider.m +++ b/Discovery/DiscoveryProvider.m @@ -29,4 +29,12 @@ - (BOOL)isEmpty { return YES; } - (void)startDiscovery { } - (void)stopDiscovery { } +- (void)pauseDiscovery { + [self stopDiscovery]; +} + +- (void)resumeDiscovery { + [self startDiscovery]; +} + @end