Skip to content
This repository has been archived by the owner on Jan 17, 2023. It is now read-only.

Commit

Permalink
Patching an error in swizzling for _AFStateObserving (#2702)
Browse files Browse the repository at this point in the history
  • Loading branch information
kcharwood committed May 12, 2015
1 parent 7f997ef commit bc33c6d
Show file tree
Hide file tree
Showing 4 changed files with 389 additions and 35 deletions.
124 changes: 93 additions & 31 deletions AFNetworking/AFURLSessionManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -255,68 +255,130 @@ - (void)URLSession:(__unused NSURLSession *)session

#pragma mark -

/*
A workaround for issues related to key-value observing the `state` of an `NSURLSessionTask`.
See https://github.com/AFNetworking/AFNetworking/issues/1477
/**
* A workaround for issues related to key-value observing the `state` of an `NSURLSessionTask`.
*
* See:
* - https://github.com/AFNetworking/AFNetworking/issues/1477
* - https://github.com/AFNetworking/AFNetworking/issues/2638
* - https://github.com/AFNetworking/AFNetworking/pull/2702
*/

static inline void af_swizzleSelector(Class class, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
if (class_addMethod(class, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))) {
class_replaceMethod(class, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline void af_addMethod(Class class, SEL selector, Method method) {
class_addMethod(class, selector, method_getImplementation(method), method_getTypeEncoding(method));
static inline BOOL af_addMethod(Class class, SEL selector, Method method) {
return class_addMethod(class, selector, method_getImplementation(method), method_getTypeEncoding(method));
}

static NSString * const AFNSURLSessionTaskDidResumeNotification = @"com.alamofire.networking.nsurlsessiontask.resume";
static NSString * const AFNSURLSessionTaskDidSuspendNotification = @"com.alamofire.networking.nsurlsessiontask.suspend";

@interface NSURLSessionTask (_AFStateObserving)
@end
@interface _AFURLSessionTaskSwizzling : NSObject

@implementation NSURLSessionTask (_AFStateObserving)

+ (void)initialize {
if ([NSURLSessionTask class]) {
NSURLSessionDataTask *dataTask = [[NSURLSession sessionWithConfiguration:nil] dataTaskWithURL:nil];
Class taskClass = [dataTask superclass];

af_addMethod(taskClass, @selector(af_resume), class_getInstanceMethod(self, @selector(af_resume)));
af_addMethod(taskClass, @selector(af_suspend), class_getInstanceMethod(self, @selector(af_suspend)));
af_swizzleSelector(taskClass, @selector(resume), @selector(af_resume));
af_swizzleSelector(taskClass, @selector(suspend), @selector(af_suspend));
@end

[dataTask cancel];
@implementation _AFURLSessionTaskSwizzling

+ (void)load {
/**
WARNING: Trouble Ahead
https://github.com/AFNetworking/AFNetworking/pull/2702
*/

if (NSClassFromString(@"NSURLSessionTask")) {
/**
iOS 7 and iOS 8 differ in NSURLSessionTask implementation, which makes the next bit of code a bit tricky.
Many Unit Tests have been built to validate as much of this behavior has possible.
Here is what we know:
- NSURLSessionTasks are implemented with class clusters, meaning the class you request from the API isn't actually the type of class you will get back.
- Simply referencing `[NSURLSessionTask class]` will not work. You need to ask an `NSURLSession` to actually create an object, and grab the class from there.
- On iOS 7, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `__NSCFURLSessionTask`.
- On iOS 8, `localDataTask` is a `__NSCFLocalDataTask`, which inherits from `__NSCFLocalSessionTask`, which inherits from `NSURLSessionTask`.
- On iOS 7, `__NSCFLocalSessionTask` and `__NSCFURLSessionTask` are the only two classes that have their own implementations of `resume` and `suspend`, and `__NSCFLocalSessionTask` DOES NOT CALL SUPER. This means both classes need to be swizzled.
- On iOS 8, `NSURLSessionTask` is the only class that implements `resume` and `suspend`. This means this is the only class that needs to be swizzled.
- Because `NSURLSessionTask` is not involved in the class hierarchy for every version of iOS, its easier to add the swizzled methods to a dummy class and manage them there.
Some Assumptions:
- No implementations of `resume` or `suspend` call super. If this were to change in a future version of iOS, we'd need to handle it.
- No background task classes override `resume` or `suspend`
The current solution:
1) Grab an instance of `__NSCFLocalDataTask` by asking an instance of `NSURLSession` for a data task.
2) Grab a pointer to the original implementation of `af_resume`
3) Check to see if the current class has an implementation of resume. If so, continue to step 4.
4) Grab the super class of the current class.
5) Grab a pointer for the current class to the current implementation of `resume`.
6) Grab a pointer for the super class to the current implementation of `resume`.
7) If the current class implementation of `resume` is not equal to the super class implementation of `resume` AND the current implementation of `resume` is not equal to the original implementation of `af_resume`, THEN swizzle the methods
8) Set the current class to the super class, and repeat steps 3-8
*/
NSURLSessionDataTask *localDataTask = [[NSURLSession sessionWithConfiguration:nil] dataTaskWithURL:nil];
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([_AFURLSessionTaskSwizzling class], @selector(af_resume)));
Class currentClass = [localDataTask class];

while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}

[localDataTask cancel];
}
}

#pragma mark -
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)class {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));

af_addMethod(class, @selector(af_resume), afResumeMethod);
af_addMethod(class, @selector(af_suspend), afSuspendMethod);

af_swizzleSelector(class, @selector(resume), @selector(af_resume));
af_swizzleSelector(class, @selector(suspend), @selector(af_suspend));
}

- (void)af_resume {
NSURLSessionTaskState state = self.state;
NSURLSessionTaskState state;
SEL selector = @selector(state);
NSAssert(selector, @"Does not respond to state");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[[self class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];
[invocation getReturnValue:&state];
[self af_resume];

if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}

- (void)af_suspend {
NSURLSessionTaskState state = self.state;
NSURLSessionTaskState state;
SEL selector = @selector(state);
NSAssert(selector, @"Does not respond to state");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:
[[self class] instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:self];
[invocation invoke];
[invocation getReturnValue:&state];
[self af_suspend];

if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}

@end

#pragma mark -
Expand Down
2 changes: 2 additions & 0 deletions Tests/AFNetworking Tests.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
29CBFC7717DF697C0021AB75 /* HTTPBinOrgServerTrustChain in Resources */ = {isa = PBXBuildFile; fileRef = 29CBFC7517DF697C0021AB75 /* HTTPBinOrgServerTrustChain */; };
29CBFC8717DF74C60021AB75 /* ADNNetServerTrustChain in Resources */ = {isa = PBXBuildFile; fileRef = 29CBFC8617DF74C60021AB75 /* ADNNetServerTrustChain */; };
29CBFC8817DF74C60021AB75 /* ADNNetServerTrustChain in Resources */ = {isa = PBXBuildFile; fileRef = 29CBFC8617DF74C60021AB75 /* ADNNetServerTrustChain */; };
29EAB0D71AFC148200C2C460 /* AFURLSessionManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 943B1F40192E406C00304316 /* AFURLSessionManagerTests.m */; };
36DE264E18053E930062F4E3 /* adn_0.cer in Resources */ = {isa = PBXBuildFile; fileRef = 36DE264B18053E930062F4E3 /* adn_0.cer */; };
36DE264F18053E930062F4E3 /* adn_1.cer in Resources */ = {isa = PBXBuildFile; fileRef = 36DE264C18053E930062F4E3 /* adn_1.cer */; };
36DE265018053E930062F4E3 /* adn_2.cer in Resources */ = {isa = PBXBuildFile; fileRef = 36DE264D18053E930062F4E3 /* adn_2.cer */; };
Expand Down Expand Up @@ -545,6 +546,7 @@
files = (
F837FFB0195744A0009078A0 /* AFHTTPResponseSerializationTests.m in Sources */,
36DE2652180544600062F4E3 /* AFHTTPRequestOperationTests.m in Sources */,
29EAB0D71AFC148200C2C460 /* AFURLSessionManagerTests.m in Sources */,
36DE26511805445B0062F4E3 /* AFSecurityPolicyTests.m in Sources */,
29CBFC4017DF58000021AB75 /* AFHTTPRequestSerializationTests.m in Sources */,
29CBFC3D17DF541F0021AB75 /* AFJSONSerializationTests.m in Sources */,
Expand Down
8 changes: 8 additions & 0 deletions Tests/Tests/AFNetworkActivityManagerTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,14 @@ - (void)setUp {
}] setNetworkActivityIndicatorVisible:YES];
}

- (void)tearDown {
[super tearDown];
[self.mockApplication stopMocking];

self.mockApplication = nil;
self.networkActivityIndicatorManager = nil;
}

#pragma mark -

- (void)testThatNetworkActivityIndicatorTurnsOffIndicatorWhenRequestSucceeds {
Expand Down
Loading

0 comments on commit bc33c6d

Please sign in to comment.