// AFNetworkActivityIndicatorManager.m // Copyright (c) 2011–2016 Alamofire Software Foundation ( http://alamofire.org/ ) // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. #import "AFNetworkActivityIndicatorManager.h" #if TARGET_OS_IOS #import "AFURLSessionManager.h" typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) { AFNetworkActivityManagerStateNotActive, AFNetworkActivityManagerStateDelayingStart, AFNetworkActivityManagerStateActive, AFNetworkActivityManagerStateDelayingEnd }; static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0; static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17; static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notification) { if ([[notification object] respondsToSelector:@selector(originalRequest)]) { return [(NSURLSessionTask *)[notification object] originalRequest]; } else { return nil; } } typedef void (^AFNetworkActivityActionBlock)(BOOL networkActivityIndicatorVisible); @interface AFNetworkActivityIndicatorManager () @property (readwrite, nonatomic, assign) NSInteger activityCount; @property (readwrite, nonatomic, strong) NSTimer *activationDelayTimer; @property (readwrite, nonatomic, strong) NSTimer *completionDelayTimer; @property (readonly, nonatomic, getter = isNetworkActivityOccurring) BOOL networkActivityOccurring; @property (nonatomic, copy) AFNetworkActivityActionBlock networkActivityActionBlock; @property (nonatomic, assign) AFNetworkActivityManagerState currentState; @property (nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; - (void)updateCurrentStateForNetworkActivityChange; @end @implementation AFNetworkActivityIndicatorManager + (instancetype)sharedManager { static AFNetworkActivityIndicatorManager *_sharedManager = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _sharedManager = [[self alloc] init]; }); return _sharedManager; } - (instancetype)init { self = [super init]; if (!self) { return nil; } self.currentState = AFNetworkActivityManagerStateNotActive; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil]; self.activationDelay = kDefaultAFNetworkActivityManagerActivationDelay; self.completionDelay = kDefaultAFNetworkActivityManagerCompletionDelay; return self; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; [_activationDelayTimer invalidate]; [_completionDelayTimer invalidate]; } - (void)setEnabled:(BOOL)enabled { _enabled = enabled; if (enabled == NO) { [self setCurrentState:AFNetworkActivityManagerStateNotActive]; } } - (void)setNetworkingActivityActionWithBlock:(void (^)(BOOL networkActivityIndicatorVisible))block { self.networkActivityActionBlock = block; } - (BOOL)isNetworkActivityOccurring { @synchronized(self) { return self.activityCount > 0; } } - (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible { if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) { [self willChangeValueForKey:@"networkActivityIndicatorVisible"]; @synchronized(self) { _networkActivityIndicatorVisible = networkActivityIndicatorVisible; } [self didChangeValueForKey:@"networkActivityIndicatorVisible"]; if (self.networkActivityActionBlock) { self.networkActivityActionBlock(networkActivityIndicatorVisible); } else { [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible]; } } } - (void)setActivityCount:(NSInteger)activityCount { @synchronized(self) { _activityCount = activityCount; } dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; }); } - (void)incrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { _activityCount++; } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; }); } - (void)decrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" _activityCount = MAX(_activityCount - 1, 0); #pragma clang diagnostic pop } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; }); } - (void)networkRequestDidStart:(NSNotification *)notification { if ([AFNetworkRequestFromNotification(notification) URL]) { [self incrementActivityCount]; } } - (void)networkRequestDidFinish:(NSNotification *)notification { if ([AFNetworkRequestFromNotification(notification) URL]) { [self decrementActivityCount]; } } #pragma mark - Internal State Management - (void)setCurrentState:(AFNetworkActivityManagerState)currentState { @synchronized(self) { if (_currentState != currentState) { [self willChangeValueForKey:@"currentState"]; _currentState = currentState; switch (currentState) { case AFNetworkActivityManagerStateNotActive: [self cancelActivationDelayTimer]; [self cancelCompletionDelayTimer]; [self setNetworkActivityIndicatorVisible:NO]; break; case AFNetworkActivityManagerStateDelayingStart: [self startActivationDelayTimer]; break; case AFNetworkActivityManagerStateActive: [self cancelCompletionDelayTimer]; [self setNetworkActivityIndicatorVisible:YES]; break; case AFNetworkActivityManagerStateDelayingEnd: [self startCompletionDelayTimer]; break; } } [self didChangeValueForKey:@"currentState"]; } } - (void)updateCurrentStateForNetworkActivityChange { if (self.enabled) { switch (self.currentState) { case AFNetworkActivityManagerStateNotActive: if (self.isNetworkActivityOccurring) { [self setCurrentState:AFNetworkActivityManagerStateDelayingStart]; } break; case AFNetworkActivityManagerStateDelayingStart: //No op. Let the delay timer finish out. break; case AFNetworkActivityManagerStateActive: if (!self.isNetworkActivityOccurring) { [self setCurrentState:AFNetworkActivityManagerStateDelayingEnd]; } break; case AFNetworkActivityManagerStateDelayingEnd: if (self.isNetworkActivityOccurring) { [self setCurrentState:AFNetworkActivityManagerStateActive]; } break; } } } - (void)startActivationDelayTimer { self.activationDelayTimer = [NSTimer timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes]; } - (void)activationDelayTimerFired { if (self.networkActivityOccurring) { [self setCurrentState:AFNetworkActivityManagerStateActive]; } else { [self setCurrentState:AFNetworkActivityManagerStateNotActive]; } } - (void)startCompletionDelayTimer { [self.completionDelayTimer invalidate]; self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes]; } - (void)completionDelayTimerFired { [self setCurrentState:AFNetworkActivityManagerStateNotActive]; } - (void)cancelActivationDelayTimer { [self.activationDelayTimer invalidate]; } - (void)cancelCompletionDelayTimer { [self.completionDelayTimer invalidate]; } @end #endif