google/EarlGrey

View on GitHub
EarlGrey/Additions/NSURLConnection+GREYAdditions.m

Summary

Maintainability
Test Coverage
//
// Copyright 2016 Google Inc.
//
// 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 "Additions/NSURLConnection+GREYAdditions.h"

#include <objc/runtime.h>

#import "Additions/NSObject+GREYAdditions.h"
#import "Additions/NSURL+GREYAdditions.h"
#import "Common/GREYConfiguration.h"
#import "Common/GREYFatalAsserts.h"
#import "Common/GREYSwizzler.h"
#import "Delegate/GREYNSURLConnectionDelegate.h"
#import "Synchronization/GREYAppStateTracker.h"
#import "Synchronization/GREYAppStateTrackerObject.h"

typedef void (^NSURLConnectionCompletionBlock)(NSURLResponse *, NSData *, NSError *);

@implementation NSURLConnection (GREYAdditions)

+ (void)load {
  @autoreleasepool {
    GREYSwizzler *swizzler = [[GREYSwizzler alloc] init];
    SEL originalSelector = @selector(sendAsynchronousRequest:queue:completionHandler:);
    SEL swizzledSelector = @selector(greyswizzled_sendAsynchronousRequest:queue:completionHandler:);
    BOOL swizzleSuccess = [swizzler swizzleClass:self
                              replaceClassMethod:originalSelector
                                      withMethod:swizzledSelector];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle NSURLConnection::"
                               @"sendAsynchronousRequest:queue:completionHandler:");

    originalSelector = @selector(sendSynchronousRequest:returningResponse:error:);
    swizzledSelector = @selector(greyswizzled_sendSynchronousRequest:returningResponse:error:);
    swizzleSuccess = [swizzler swizzleClass:self
                         replaceClassMethod:originalSelector
                                 withMethod:swizzledSelector];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle "
                               @"NSURLConnection::sendSynchronousRequest:returningResponse:error:");

    originalSelector = @selector(connectionWithRequest:delegate:);
    swizzledSelector = @selector(greyswizzled_connectionWithRequest:delegate:);
    swizzleSuccess = [swizzler swizzleClass:self
                         replaceClassMethod:originalSelector
                                 withMethod:swizzledSelector];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle NSURLConnection::connectionWithRequest:delegate:");

    originalSelector = @selector(initWithRequest:delegate:startImmediately:);
    swizzledSelector = @selector(greyswizzled_initWithRequest:delegate:startImmediately:);
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:originalSelector
                                 withMethod:swizzledSelector];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle "
                               @"NSURLConnection::initWithRequest:delegate:startImmediately:");

    originalSelector = @selector(initWithRequest:delegate:);
    swizzledSelector = @selector(greyswizzled_initWithRequest:delegate:);
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:originalSelector
                                 withMethod:swizzledSelector];
    GREYFatalAssertWithMessage(swizzleSuccess,
                               @"Cannot swizzle NSURLConnection::initWithRequest:delegate:");

    originalSelector = @selector(start);
    swizzledSelector = @selector(greyswizzled_start);
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:originalSelector
                                 withMethod:swizzledSelector];
    GREYFatalAssertWithMessage(swizzleSuccess, @"Cannot swizzle NSURLConnection::start");

    originalSelector = @selector(cancel);
    swizzledSelector = @selector(greyswizzled_cancel);
    swizzleSuccess = [swizzler swizzleClass:self
                      replaceInstanceMethod:originalSelector
                                 withMethod:swizzledSelector];
    GREYFatalAssertWithMessage(swizzleSuccess, @"Cannot swizzle NSURLConnection::cancel");
  }
}

#pragma mark - Package Internal

- (void)grey_trackPending {
  GREYAppStateTrackerObject *object = TRACK_STATE_FOR_OBJECT(kGREYPendingNetworkRequest, self);
  objc_setAssociatedObject(self,
                           @selector(grey_trackPending),
                           object,
                           OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)grey_untrackPending {
  GREYAppStateTrackerObject *object =
      objc_getAssociatedObject(self, @selector(grey_trackPending));
  UNTRACK_STATE_FOR_OBJECT(kGREYPendingNetworkRequest, object);
}

#pragma mark - Swizzled Implementation

+ (NSData *)greyswizzled_sendSynchronousRequest:(NSURLRequest *)request
                              returningResponse:(NSURLResponse **)response
                                          error:(NSError **)error {
  GREYAppStateTrackerObject *object;
  NSObject *uniqueIdentifier;
  if ([request.URL grey_shouldSynchronize] && ![NSThread isMainThread]) {
    uniqueIdentifier = [[NSObject alloc] init];
    object = TRACK_STATE_FOR_OBJECT(kGREYPendingNetworkRequest, uniqueIdentifier);
  }

  NSData *data =
      INVOKE_ORIGINAL_IMP3(NSData *,
                           @selector(greyswizzled_sendSynchronousRequest:returningResponse:error:),
                           request,
                           response,
                           error);
  if (object) {
    UNTRACK_STATE_FOR_OBJECT(kGREYPendingNetworkRequest, object);
    // Hold a reference to the unique identifier until we no longer track the connection.
    uniqueIdentifier = nil;
  }
  return data;
}


+ (void)greyswizzled_sendAsynchronousRequest:(NSURLRequest *)request
                                       queue:(NSOperationQueue *)queue
                           completionHandler:(NSURLConnectionCompletionBlock)handler {
  void (^completionHandler)(NSURLResponse *, NSData *, NSError *) = handler;
  if ([request.URL grey_shouldSynchronize]) {
    // Use a unique identifier to track connection.
    __block NSObject *uniqueIdentifier = [[NSObject alloc] init];
    GREYAppStateTrackerObject *object =
        TRACK_STATE_FOR_OBJECT(kGREYPendingNetworkRequest, uniqueIdentifier);
    completionHandler = ^(NSURLResponse *response,
                          NSData *data,
                          NSError *error) {
      handler(response, data, error);
      UNTRACK_STATE_FOR_OBJECT(kGREYPendingNetworkRequest, object);
      // Hold a reference to the unique identifier until we no longer track the connection.
      uniqueIdentifier = nil;
    };
  }
  INVOKE_ORIGINAL_IMP3(void,
                       @selector(greyswizzled_sendAsynchronousRequest:queue:completionHandler:),
                       request,
                       queue,
                       completionHandler);
}

+ (NSURLConnection *)greyswizzled_connectionWithRequest:(NSURLRequest *)request
                                               delegate:(id<NSURLConnectionDelegate>)delegate {
  GREYNSURLConnectionDelegate *proxyDelegate =
      [[GREYNSURLConnectionDelegate alloc] initWithOriginalNSURLConnectionDelegate:delegate];
  NSURLConnection *connection =
      INVOKE_ORIGINAL_IMP2(NSURLConnection *,
                           @selector(greyswizzled_connectionWithRequest:delegate:),
                           request,
                           proxyDelegate);
  return connection;
}

- (instancetype)greyswizzled_initWithRequest:(NSURLRequest *)request
                                    delegate:(id<NSURLConnectionDelegate>)delegate {
  GREYNSURLConnectionDelegate *proxyDelegate =
      [[GREYNSURLConnectionDelegate alloc] initWithOriginalNSURLConnectionDelegate:delegate];
  id instance = INVOKE_ORIGINAL_IMP2(id,
                                     @selector(greyswizzled_initWithRequest:delegate:),
                                     request,
                                     proxyDelegate);
  // Track since this call will begin to load data from request.
  if ([request.URL grey_shouldSynchronize]) {
    [instance grey_trackPending];
  }
  return instance;
}

- (instancetype)greyswizzled_initWithRequest:(NSURLRequest *)request
                                    delegate:(id<NSURLConnectionDelegate>)delegate
                            startImmediately:(BOOL)startImmediately {
  if (startImmediately && [request.URL grey_shouldSynchronize]) {
    [self grey_trackPending];
  }
  GREYNSURLConnectionDelegate *proxyDelegate =
      [[GREYNSURLConnectionDelegate alloc] initWithOriginalNSURLConnectionDelegate:delegate];

  return INVOKE_ORIGINAL_IMP3(id,
                              @selector(greyswizzled_initWithRequest:delegate:startImmediately:),
                              request,
                              proxyDelegate,
                              startImmediately);
}

- (void)greyswizzled_start {
  if ([self.originalRequest.URL grey_shouldSynchronize]) {
    [self grey_trackPending];
  }

  return INVOKE_ORIGINAL_IMP(void, @selector(greyswizzled_start));
}

- (void)greyswizzled_cancel {
  [self grey_untrackPending];

  return INVOKE_ORIGINAL_IMP(void, @selector(greyswizzled_cancel));
}

@end