diff --git a/Develop/CompoundOperationsExampleDev/CompoundOperationsExampleDevTests/Source/Base/ChainableOperationBaseTests.m b/Develop/CompoundOperationsExampleDev/CompoundOperationsExampleDevTests/Source/Base/ChainableOperationBaseTests.m index ab1f533..4c90588 100644 --- a/Develop/CompoundOperationsExampleDev/CompoundOperationsExampleDevTests/Source/Base/ChainableOperationBaseTests.m +++ b/Develop/CompoundOperationsExampleDev/CompoundOperationsExampleDevTests/Source/Base/ChainableOperationBaseTests.m @@ -119,7 +119,7 @@ - (void)testThatChainaleOperationBaseCallsInputDataClassMethodWhenStarted { }]; // when - [self.chainableOperationBaseMock start]; + [(ChainableOperationBase *)self.chainableOperationBaseMock start]; // then [self waitForExpectationsWithTimeout:COODefaultTestTimeout handler:^(NSError * _Nullable error) { @@ -135,7 +135,7 @@ - (void)testThatChainaleOperationBaseCallsProcessDataMethodWhenStarted { }]; // when - [self.chainableOperationBaseMock start]; + [(ChainableOperationBase *)self.chainableOperationBaseMock start]; // then [self waitForExpectationsWithTimeout:COODefaultTestTimeout handler:^(NSError * _Nullable error) { @@ -152,7 +152,7 @@ - (void)testThatChainaleOperationBaseCallsProcessDataMethodWithCorrectParameters }]; // when - [self.chainableOperationBaseMock start]; + [(ChainableOperationBase *)self.chainableOperationBaseMock start]; // then __weak __typeof__(self) weakSelf = self; @@ -172,7 +172,7 @@ - (void)testThatChainaleOperationBaseMakeCorrectOutputAfterDataProcessing { }]; // when - [self.chainableOperationBaseMock start]; + [(ChainableOperationBase *)self.chainableOperationBaseMock start]; // then __weak __typeof__(self) weakSelf = self; diff --git a/Develop/CompoundOperationsExampleDev/CompoundOperationsExampleDevTests/Source/Base/CompoundOperationTests.m b/Develop/CompoundOperationsExampleDev/CompoundOperationsExampleDevTests/Source/Base/CompoundOperationTests.m index 5f5508f..c33b305 100644 --- a/Develop/CompoundOperationsExampleDev/CompoundOperationsExampleDevTests/Source/Base/CompoundOperationTests.m +++ b/Develop/CompoundOperationsExampleDev/CompoundOperationsExampleDevTests/Source/Base/CompoundOperationTests.m @@ -115,6 +115,7 @@ - (void)testThatCompoundOperationCancellsCorrectly { // then OCMVerify([self.mockOperationQueue setSuspended:YES]); OCMVerify([self.mockOperationQueue cancelAllOperations]); + OCMVerify([self.mockOperationQueue setSuspended:NO]); } diff --git a/Source/Base/AsyncOperation/AsyncOperation.m b/Source/Base/AsyncOperation/AsyncOperation.m index 916ee29..868d8b3 100755 --- a/Source/Base/AsyncOperation/AsyncOperation.m +++ b/Source/Base/AsyncOperation/AsyncOperation.m @@ -20,19 +20,24 @@ #import "AsyncOperation.h" -static NSString *const kExecutingFlagSelector = @"isExecuting"; -static NSString *const kFinishedFlagSelector = @"isFinished"; +@interface AsyncOperation () + +@property (strong, nonatomic) NSRecursiveLock *recursiveLock; + +@end @implementation AsyncOperation { BOOL executing; BOOL finished; -}; +} - (instancetype)init { self = [super init]; if (self) { executing = NO; finished = NO; + _recursiveLock = [[NSRecursiveLock alloc] init]; + _recursiveLock.name = [NSString stringWithFormat:@"com.strongself.%@-lock", [self class]]; } return self; } @@ -44,14 +49,22 @@ - (BOOL)isAsynchronous { } - (BOOL)isExecuting { - return executing; + [self.recursiveLock lock]; + BOOL result = executing; + [self.recursiveLock unlock]; + + return result; } - (BOOL)isFinished { - return finished; + [self.recursiveLock lock]; + BOOL result = finished; + [self.recursiveLock unlock]; + + return result; } -#pragma mark - Private methods +#pragma mark - NSOperation overrides - (void)start { /** @@ -73,11 +86,8 @@ - (void)start { If it wasn't cancelled and wasn't started manually, we're beginning the task */ - [self willChangeValueForKey:kExecutingFlagSelector]; - + [self lockedMarkStarted]; [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil]; - executing = YES; - [self didChangeValueForKey:kExecutingFlagSelector]; } } @@ -86,20 +96,56 @@ - (void)main { format:@"You should override the method %@ in a subclass", NSStringFromSelector(_cmd)]; } +#pragma mark - Utils + +- (void)changeValueForKey:(NSString *)key inBlock:(void(^)())block { + [self willChangeValueForKey:key]; + block(); + [self didChangeValueForKey:key]; +} + +- (void)lock:(void(^)())block { + [self.recursiveLock lock]; + block(); + [self.recursiveLock unlock]; +} + +#pragma mark - State management + +- (void)markFinished { + [self changeValueForKey:NSStringFromSelector(@selector(isFinished)) inBlock:^{ + finished = YES; + }]; +} + +- (void)markStarted { + [self changeValueForKey:NSStringFromSelector(@selector(isExecuting)) inBlock:^{ + executing = YES; + }]; +} + +- (void)lockedMarkStarted { + [self lock:^{ + [self markStarted]; + }]; +} + +- (void)markComplete { + [self changeValueForKey:NSStringFromSelector(@selector(isExecuting)) inBlock:^{ + executing = NO; + }]; +} + - (void)complete { /** @author Egor Tolstoy We should always manually setup finished and executing flags after the operation is complete or cancelled */ - [self willChangeValueForKey:kFinishedFlagSelector]; - [self willChangeValueForKey:kExecutingFlagSelector]; - - executing = NO; - finished = YES; - - [self didChangeValueForKey:kExecutingFlagSelector]; - [self didChangeValueForKey:kFinishedFlagSelector]; + [self lock:^{ + [self markComplete]; + [self markFinished]; + }]; } @end diff --git a/Source/Base/CompoundOperation/CompoundOperation.m b/Source/Base/CompoundOperation/CompoundOperation.m index 4a9af58..57d085f 100644 --- a/Source/Base/CompoundOperation/CompoundOperation.m +++ b/Source/Base/CompoundOperation/CompoundOperation.m @@ -156,15 +156,11 @@ - (void)main { - (void)cancel { // We should cancel the operation only if it's executing if (![self isFinished] && ![self isCancelled]) { - [super cancel]; - - if ([self isExecuting]) { - [self finishCompoundOperationExecution]; - } + [super cancel]; + [self finishCompoundOperationExecution]; } } - #pragma mark - - (void)didCompleteChainableOperationWithError:(NSError *)error {