Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Delegates for reporting sent/received bytes #148

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
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
36 changes: 36 additions & 0 deletions SPTDataLoader/SPTDataLoader.m
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,42 @@ - (void)needsNewBodyStream:(void (^)(NSInputStream *))completionHandler
}
}

- (void)updatedCountOfBytesReceived:(int64_t)countOfBytesReceived
countOfBytesExpectedToReceive:(int64_t)countOfBytesExpectedToReceive
forRequest:(SPTDataLoaderRequest *)request
{
if (![self isRequestExpected:request]) {
return;
}

if ([self.delegate respondsToSelector:@selector(dataLoader:updatedCountOfBytesReceived:countOfBytesExpectedToReceive:forRequest:)]) {
[self executeDelegateBlock:^{
[self.delegate dataLoader:self
updatedCountOfBytesReceived:countOfBytesReceived
countOfBytesExpectedToReceive:countOfBytesExpectedToReceive
forRequest:request];
}];
}
}

- (void)updatedCountOfBytesSent:(int64_t)countOfBytesSent
countOfBytesExpectedToSend:(int64_t)countOfBytesExpectedToSend
forRequest:(SPTDataLoaderRequest *)request
{
if (![self isRequestExpected:request]) {
return;
}

if ([self.delegate respondsToSelector:@selector(dataLoader:updatedCountOfBytesSent:countOfBytesExpectedToSend:forRequest:)]) {
[self executeDelegateBlock:^{
[self.delegate dataLoader:self
updatedCountOfBytesSent:countOfBytesSent
countOfBytesExpectedToSend:countOfBytesExpectedToSend
forRequest:request];
}];
}
}

#pragma mark SPTDataLoaderCancellationTokenDelegate

- (void)cancellationTokenDidCancel:(id<SPTDataLoaderCancellationToken>)cancellationToken
Expand Down
26 changes: 26 additions & 0 deletions SPTDataLoader/SPTDataLoaderFactory.m
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,32 @@ - (void)needsNewBodyStream:(void (^)(NSInputStream * _Nonnull))completionHandler
[requestResponseHandler needsNewBodyStream:completionHandler forRequest:request];
}

- (void)updatedCountOfBytesReceived:(int64_t)countOfBytesReceived
countOfBytesExpectedToReceive:(int64_t)countOfBytesExpectedToReceive
forRequest:(SPTDataLoaderRequest *)request
{
id<SPTDataLoaderRequestResponseHandler> requestResponseHandler = nil;
@synchronized(self.requestToRequestResponseHandler) {
requestResponseHandler = [self.requestToRequestResponseHandler objectForKey:request];
}
[requestResponseHandler updatedCountOfBytesReceived:countOfBytesReceived
countOfBytesExpectedToReceive:countOfBytesExpectedToReceive
forRequest:request];
}

- (void)updatedCountOfBytesSent:(int64_t)countOfBytesSent
countOfBytesExpectedToSend:(int64_t)countOfBytesExpectedToSend
forRequest:(SPTDataLoaderRequest *)request
{
id<SPTDataLoaderRequestResponseHandler> requestResponseHandler = nil;
@synchronized(self.requestToRequestResponseHandler) {
requestResponseHandler = [self.requestToRequestResponseHandler objectForKey:request];
}
[requestResponseHandler updatedCountOfBytesSent:countOfBytesSent
countOfBytesExpectedToSend:countOfBytesExpectedToSend
forRequest:request];
}

#pragma mark SPTDataLoaderRequestResponseHandlerDelegate

- (void)requestResponseHandler:(id<SPTDataLoaderRequestResponseHandler>)requestResponseHandler
Expand Down
19 changes: 19 additions & 0 deletions SPTDataLoader/SPTDataLoaderRequestResponseHandler.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,25 @@ NS_ASSUME_NONNULL_BEGIN
- (void)needsNewBodyStream:(void (^)(NSInputStream *))completionHandler
forRequest:(SPTDataLoaderRequest *)request;

/**
* Called when the number of bytes that received from the server has changed
* @param countOfBytesReceived The number of bytes that received from the server in the response body
* @param countOfBytesExpectedToReceive The number of bytes that expects to receive in the response body
* @param request The object describing the request
*/
- (void)updatedCountOfBytesReceived:(int64_t)countOfBytesReceived
countOfBytesExpectedToReceive:(int64_t)countOfBytesExpectedToReceive
forRequest:(SPTDataLoaderRequest *)request;
/**
* Called when the number of bytes that sent to the server has changed
* @param countOfBytesSent The number of bytes that sent to the server in the request body
* @param countOfBytesExpectedToSend The number of bytes that the task expects to send in the request body
* @param request The object describing the request
*/
- (void)updatedCountOfBytesSent:(int64_t)countOfBytesSent
countOfBytesExpectedToSend:(int64_t)countOfBytesExpectedToSend
forRequest:(SPTDataLoaderRequest *)request;

@optional

/**
Expand Down
107 changes: 107 additions & 0 deletions SPTDataLoader/SPTDataLoaderRequestTaskHandler.m
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@

static NSUInteger const SPTDataLoaderRequestTaskHandlerMaxRedirects = 10;

static void *SPTDataLoaderRequestTaskHandlerContext = &SPTDataLoaderRequestTaskHandlerContext;
static NSString *const kCountOfBytesReceived = @"countOfBytesReceived";
static NSString *const kCountOfBytesSent = @"countOfBytesSent";
static NSString *const kCountOfBytesExpectedToReceive = @"countOfBytesExpectedToReceive";
static NSString *const kCountOfBytesExpectedToSend = @"countOfBytesExpectedToSend";

@interface SPTDataLoaderRequestTaskHandler ()

@property (nonatomic, assign, readwrite, getter = isCancelled) BOOL cancelled;
Expand All @@ -53,6 +59,7 @@ @interface SPTDataLoaderRequestTaskHandler ()
@property (nonatomic, assign) BOOL calledFailedResponse;
@property (nonatomic, assign) BOOL calledCancelledRequest;
@property (nonatomic, assign) BOOL started;
@property (nonatomic, assign, getter = isObserving) BOOL observing;
@property (nonatomic, strong, readwrite) dispatch_queue_t retryQueue;

@end
Expand Down Expand Up @@ -83,6 +90,7 @@ - (instancetype)initWithTask:(NSURLSessionTask *)task
self = [super init];
if (self) {
_task = task;
[self addObserverForTask:_task];
_request = request;
_requestResponseHandler = requestResponseHandler;
_rateLimiter = rateLimiter;
Expand All @@ -99,6 +107,30 @@ - (instancetype)initWithTask:(NSURLSessionTask *)task
return self;
}

@synthesize task = _task;

- (NSURLSessionTask *)task {
@synchronized(self) {
return _task;
}
}

- (void)setTask:(NSURLSessionTask * _Nonnull)task {
@synchronized(self) {
if (_task != task) {
if (_task) {
[self removeObserverForTask:_task];
}

_task = task;

if (_task) {
[self addObserverForTask:_task];
}
}
}
}

- (void)receiveData:(NSData *)data
{
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
Expand Down Expand Up @@ -239,11 +271,86 @@ - (void)completeIfInFlight
}
}

#pragma mark KVO

- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSString *, id> *)change
context:(nullable void *)context
{
if (context == SPTDataLoaderRequestTaskHandlerContext) {
NSURLSessionTask *task = (NSURLSessionTask *)object;
if ([task isKindOfClass:[NSURLSessionTask class]]) {
id<SPTDataLoaderRequestResponseHandler> requestResponseHandler = self.requestResponseHandler;
if ([keyPath isEqualToString:kCountOfBytesReceived] || [keyPath isEqualToString:kCountOfBytesExpectedToReceive]) {
[requestResponseHandler updatedCountOfBytesReceived:self.task.countOfBytesReceived
countOfBytesExpectedToReceive:self.task.countOfBytesExpectedToReceive
forRequest:self.request];
} else if ([keyPath isEqualToString:kCountOfBytesSent] || [keyPath isEqualToString:kCountOfBytesExpectedToSend]) {
[requestResponseHandler updatedCountOfBytesSent:self.task.countOfBytesSent
countOfBytesExpectedToSend:self.task.countOfBytesExpectedToSend
forRequest:self.request];
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we call [super observeValueForKeyPath:…] in the else case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling super implementation is by the book but SPTDataLoaderRequestTaskHandler is the first class in the inheritance chain. So if anyone inherits from SPTDataLoaderRequestTaskHandler and makes its own default KVO implementation it will lead to the crash because calling observeValueForKeyPath:ofObject:change:context: on NSObject throws an NSInternalInconsistency exception.

}

- (void)addObserverForTask:(NSURLSessionTask *)task
{
NSAssert(!self.isObserving, @"Observer is already registered for task");
if (self.isObserving) {
return;
}

self.observing = YES;

[task addObserver:self
forKeyPath:kCountOfBytesReceived
options:NSKeyValueObservingOptionNew
context:SPTDataLoaderRequestTaskHandlerContext];
[task addObserver:self
forKeyPath:kCountOfBytesSent
options:NSKeyValueObservingOptionNew
context:SPTDataLoaderRequestTaskHandlerContext];
[task addObserver:self
forKeyPath:kCountOfBytesExpectedToReceive
options:NSKeyValueObservingOptionNew
context:SPTDataLoaderRequestTaskHandlerContext];
[task addObserver:self
forKeyPath:kCountOfBytesExpectedToSend
options:NSKeyValueObservingOptionNew
context:SPTDataLoaderRequestTaskHandlerContext];
}

- (void)removeObserverForTask:(NSURLSessionTask *)task
{
NSAssert(self.isObserving, @"Observer is not registered");
if (!self.isObserving) {
return;
}

self.observing = NO;

[task removeObserver:self
forKeyPath:kCountOfBytesReceived
context:SPTDataLoaderRequestTaskHandlerContext];
[task removeObserver:self
forKeyPath:kCountOfBytesSent
context:SPTDataLoaderRequestTaskHandlerContext];
[task removeObserver:self
forKeyPath:kCountOfBytesExpectedToReceive
context:SPTDataLoaderRequestTaskHandlerContext];
[task removeObserver:self
forKeyPath:kCountOfBytesExpectedToSend
context:SPTDataLoaderRequestTaskHandlerContext];
}

#pragma mark NSObject

- (void)dealloc
{
[self completeIfInFlight];
[self removeObserverForTask:_task];
}

@end
Expand Down
6 changes: 4 additions & 2 deletions SPTDataLoaderTests/NSURLSessionTaskMock.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@

#pragma mark NSURLSessionTask

@property (atomic, readonly) int64_t countOfBytesSent;
@property (atomic, readonly) int64_t countOfBytesReceived;
@property (atomic, assign) int64_t countOfBytesSent;
@property (atomic, assign) int64_t countOfBytesReceived;
@property (atomic, assign) int64_t countOfBytesExpectedToSend;
@property (atomic, assign) int64_t countOfBytesExpectedToReceive;
@property (atomic, nullable, readonly, copy) NSURLRequest *currentRequest;

@end
2 changes: 2 additions & 0 deletions SPTDataLoaderTests/NSURLSessionTaskMock.m
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ - (NSURLResponse *)response

@synthesize countOfBytesSent;
@synthesize countOfBytesReceived;
@synthesize countOfBytesExpectedToSend;
@synthesize countOfBytesExpectedToReceive;
@synthesize currentRequest;

@end
20 changes: 20 additions & 0 deletions SPTDataLoaderTests/SPTDataLoaderFactoryTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,24 @@ - (void)testRequestingNewBodyStream
XCTAssertEqual(requestResponseHandler.numberOfNewBodyStreamCalls, 1u, @"The factory did not relay a prompt for delivering a new body stream to the correct handler");
}

- (void)testUpdatedCountOfBytesReceived
{
SPTDataLoaderRequestResponseHandlerMock *requestResponseHandler = [SPTDataLoaderRequestResponseHandlerMock new];
SPTDataLoaderRequest *request = [SPTDataLoaderRequest new];
[self.factory requestResponseHandler:requestResponseHandler performRequest:request];
[self.factory updatedCountOfBytesReceived:1u countOfBytesExpectedToReceive:2u forRequest:request];

XCTAssertEqual(requestResponseHandler.numberOfUpdatedCountOfBytesReceived, 1u, @"The factory did not relay an update of the number of bytes that received from the server");
}

- (void)testUpdatedCountOfBytesSent
{
SPTDataLoaderRequestResponseHandlerMock *requestResponseHandler = [SPTDataLoaderRequestResponseHandlerMock new];
SPTDataLoaderRequest *request = [SPTDataLoaderRequest new];
[self.factory requestResponseHandler:requestResponseHandler performRequest:request];
[self.factory updatedCountOfBytesSent:1u countOfBytesExpectedToSend:2u forRequest:request];

XCTAssertEqual(requestResponseHandler.numberOfUpdatedCountOfBytesSent, 1u, @"The factory did not relay an update of the number of bytes that sent to the server");
}

@end
2 changes: 2 additions & 0 deletions SPTDataLoaderTests/SPTDataLoaderRequestResponseHandlerMock.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
@property (nonatomic, assign, readonly) NSUInteger numberOfSuccessfulDataResponseCalls;
@property (nonatomic, assign, readonly) NSUInteger numberOfReceivedInitialResponseCalls;
@property (nonatomic, assign, readonly) NSUInteger numberOfNewBodyStreamCalls;
@property (nonatomic, assign, readonly) NSUInteger numberOfUpdatedCountOfBytesReceived;
@property (nonatomic, assign, readonly) NSUInteger numberOfUpdatedCountOfBytesSent;
@property (nonatomic, strong, readonly) SPTDataLoaderResponse *lastReceivedResponse;
@property (nonatomic, assign, readwrite, getter = isAuthorising) BOOL authorising;
@property (nonatomic, strong, readwrite) dispatch_block_t failedResponseBlock;
Expand Down
16 changes: 16 additions & 0 deletions SPTDataLoaderTests/SPTDataLoaderRequestResponseHandlerMock.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ @interface SPTDataLoaderRequestResponseHandlerMock ()
@property (nonatomic, assign, readwrite) NSUInteger numberOfSuccessfulDataResponseCalls;
@property (nonatomic, assign, readwrite) NSUInteger numberOfReceivedInitialResponseCalls;
@property (nonatomic, assign, readwrite) NSUInteger numberOfNewBodyStreamCalls;
@property (nonatomic, assign, readwrite) NSUInteger numberOfUpdatedCountOfBytesReceived;
@property (nonatomic, assign, readwrite) NSUInteger numberOfUpdatedCountOfBytesSent;
@property (nonatomic, strong, readwrite) SPTDataLoaderResponse *lastReceivedResponse;

@end
Expand Down Expand Up @@ -83,4 +85,18 @@ - (void)needsNewBodyStream:(void (^)(NSInputStream * _Nonnull))completionHandler
self.numberOfNewBodyStreamCalls++;
}

- (void)updatedCountOfBytesReceived:(int64_t)countOfBytesReceived
countOfBytesExpectedToReceive:(int64_t)countOfBytesExpectedToReceive
forRequest:(SPTDataLoaderRequest *)request
{
self.numberOfUpdatedCountOfBytesReceived++;
}

- (void)updatedCountOfBytesSent:(int64_t)countOfBytesSent
countOfBytesExpectedToSend:(int64_t)countOfBytesExpectedToSend
forRequest:(SPTDataLoaderRequest *)request
{
self.numberOfUpdatedCountOfBytesSent++;
}

@end
16 changes: 16 additions & 0 deletions SPTDataLoaderTests/SPTDataLoaderRequestTaskHandlerTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -204,4 +204,20 @@ - (void)testCancelledWhenReceivingCancelError
XCTAssertTrue(self.handler.cancelled);
}

- (void)testRelayUpdatedCountOfBytesSent
{
self.task.countOfBytesSent = 1u;
self.task.countOfBytesExpectedToSend = 2u;

XCTAssertEqual(self.requestResponseHandler.numberOfUpdatedCountOfBytesSent, 2u, @"The handler did not relay the updated count of bytes sent its request response handler");
}

- (void)testRelayUpdatedCountOfBytesReceived
{
self.task.countOfBytesReceived = 1u;
self.task.countOfBytesExpectedToReceive = 2u;

XCTAssertEqual(self.requestResponseHandler.numberOfUpdatedCountOfBytesReceived, 2u, @"The handler did not relay the updated count of bytes received its request response handler");
}

@end
23 changes: 23 additions & 0 deletions include/SPTDataLoader/SPTDataLoaderDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,29 @@ didReceiveDataChunk:(NSData *)data
needsNewBodyStream:(void (^)(NSInputStream *))completionHandler
forRequest:(SPTDataLoaderRequest *)request;

/**
* Called when the data loader receives an update of the number of bytes that received from the server for the request
* @param dataLoader The data loader that received an update
* @param countOfBytesReceived The number of bytes that received from the server in the response body
* @param countOfBytesExpectedToReceive The number of bytes that expects to receive in the response body
* @param request The object describing the request that was updated
*/
- (void)dataLoader:(SPTDataLoader *)dataLoader
updatedCountOfBytesReceived:(int64_t)countOfBytesReceived
countOfBytesExpectedToReceive:(int64_t)countOfBytesExpectedToReceive
forRequest:(SPTDataLoaderRequest *)request;
/**
* Called when the data loader receives an update of the number of bytes that sent to the server for request
* @param dataLoader The data loader that received an update
* @param countOfBytesSent The number of bytes that sent to the server in the request body
* @param countOfBytesExpectedToSend The number of bytes that the task expects to send in the request body
* @param request The object describing the request
*/
- (void)dataLoader:(SPTDataLoader *)dataLoader
updatedCountOfBytesSent:(int64_t)countOfBytesSent
countOfBytesExpectedToSend:(int64_t)countOfBytesExpectedToSend
forRequest:(SPTDataLoaderRequest *)request;

@end

NS_ASSUME_NONNULL_END