Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 91 additions & 47 deletions AFNetworking/AFURLSessionManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// THE SOFTWARE.

#import "AFURLSessionManager.h"
#import <objc/runtime.h>

#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1090)

Expand Down Expand Up @@ -255,6 +256,65 @@ - (void)URLSession:(__unused NSURLSession *)session

#pragma mark -

static NSString * const AFNetworkingUnknownTaskDidResumeNotification = @"AFNetworkingUnknownTaskDidResumeNotification";
static NSString * const AFNetworkingUnknownTaskDidSuspendNotification = @"AFNetworkingUnknownTaskDidSuspendNotification";

/**
A workaround for crashes while key-value observing the state of an \c NSURLSessionTask.

The right thing to do is to watch that state with KVO and post the suspend and resume
notifications from there. Unfortunately, there's a crashing bug in KVO in at least
iOS 7.0.4, 7.1.1, 7.1.2, 8.0, and 8.0.2. For more, see http://openradar.appspot.com/18419882
*/
@implementation NSURLSessionTask (AFStateObserving)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class class = [self class];

void (^swizzle)(SEL, SEL) = ^(SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

BOOL didAddMethod = class_addMethod(class,
originalSelector,
method_getImplementation(swizzledMethod),
method_getTypeEncoding(swizzledMethod));

if (didAddMethod) {
class_replaceMethod(class,
swizzledSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
};

swizzle(@selector(resume), @selector(af_resume));
swizzle(@selector(suspend), @selector(af_suspend));
});
}

- (void)af_resume {
NSURLSessionTaskState state = self.state;
[self af_resume];
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingUnknownTaskDidResumeNotification object:self];
}
}

- (void)af_suspend {
NSURLSessionTaskState state = self.state;
[self af_suspend];
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingUnknownTaskDidSuspendNotification object:self];
}
}
@end

#pragma mark -

@interface AFURLSessionManager ()
@property (readwrite, nonatomic, strong) NSURLSessionConfiguration *sessionConfiguration;
@property (readwrite, nonatomic, strong) NSOperationQueue *operationQueue;
Expand Down Expand Up @@ -325,10 +385,40 @@ - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)config
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNetworkingUnknownTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidSuspend:) name:AFNetworkingUnknownTaskDidSuspendNotification object:nil];

return self;
}

- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (void)taskDidResume:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task isKindOfClass:[NSURLSessionTask class]]) {
AFURLSessionManager *manager = [self delegateForTask:task].manager;
if ([manager isEqual:self]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidResumeNotification object:task];
});
}
}
}

- (void)taskDidSuspend:(NSNotification *)notification {
NSURLSessionTask *task = notification.object;
if ([task isKindOfClass:[NSURLSessionTask class]]) {
AFURLSessionManager *manager = [self delegateForTask:task].manager;
if ([manager isEqual:self]) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidSuspendNotification object:task];
});
}
}
}

#pragma mark -

- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
Expand All @@ -348,7 +438,6 @@ - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
NSParameterAssert(task);
NSParameterAssert(delegate);

[task addObserver:self forKeyPath:NSStringFromSelector(@selector(state)) options:(NSKeyValueObservingOptions)(NSKeyValueObservingOptionOld |NSKeyValueObservingOptionNew) context:AFTaskStateChangedContext];
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
[self.lock unlock];
Expand Down Expand Up @@ -420,7 +509,6 @@ - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
- (void)removeDelegateForTask:(NSURLSessionTask *)task {
NSParameterAssert(task);

[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(state)) context:AFTaskStateChangedContext];
[self.lock lock];
[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
[self.lock unlock];
Expand Down Expand Up @@ -689,42 +777,6 @@ - (BOOL)respondsToSelector:(SEL)selector {
return [[self class] instancesRespondToSelector:selector];
}

#pragma mark - NSKeyValueObserving

- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFTaskStateChangedContext && [keyPath isEqualToString:@"state"]) {
if (change[NSKeyValueChangeOldKey] && change[NSKeyValueChangeNewKey] && [change[NSKeyValueChangeNewKey] isEqual:change[NSKeyValueChangeOldKey]]) {
return;
}

NSString *notificationName = nil;
switch ([(NSURLSessionTask *)object state]) {
case NSURLSessionTaskStateRunning:
notificationName = AFNetworkingTaskDidResumeNotification;
break;
case NSURLSessionTaskStateSuspended:
notificationName = AFNetworkingTaskDidSuspendNotification;
break;
case NSURLSessionTaskStateCompleted:
// AFNetworkingTaskDidFinishNotification posted by task completion handlers
default:
break;
}

if (notificationName) {
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:object];
});
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}

#pragma mark - NSURLSessionDelegate

- (void)URLSession:(NSURLSession *)session
Expand All @@ -734,15 +786,7 @@ - (void)URLSession:(NSURLSession *)session
self.sessionDidBecomeInvalid(session, error);
}

[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
NSArray *tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
for (NSURLSessionTask *task in tasks) {
[task removeObserver:self forKeyPath:NSStringFromSelector(@selector(state)) context:AFTaskStateChangedContext];
}

[self removeAllDelegates];
}];

[self removeAllDelegates];
[[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
}

Expand Down