| /* | |
| * | |
| * Copyright 2015 gRPC authors. | |
| * | |
| * 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 "GRXConcurrentWriteable.h" | |
| #import "GRXWriteable.h" | |
| @interface GRXConcurrentWriteable () | |
| // This is atomic so that cancellation can nillify it from any thread. | |
| @property(atomic, strong) id<GRXWriteable> writeable; | |
| @end | |
| @implementation GRXConcurrentWriteable { | |
| dispatch_queue_t _writeableQueue; | |
| // This ivar ensures that writesFinishedWithError: is only sent once to the writeable. Protected | |
| // by _writeableQueue. | |
| BOOL _alreadyFinished; | |
| // This ivar ensures that a cancelWithError: call prevents further values to be sent to | |
| // self.writeable. It must support manipulation outside of _writeableQueue and thus needs to be | |
| // protected by self lock. | |
| BOOL _cancelled; | |
| } | |
| - (instancetype)init { | |
| return [self initWithWriteable:nil]; | |
| } | |
| // Designated initializer | |
| - (instancetype)initWithWriteable:(id<GRXWriteable>)writeable | |
| dispatchQueue:(dispatch_queue_t)queue { | |
| if (self = [super init]) { | |
| _writeableQueue = queue; | |
| _writeable = writeable; | |
| _alreadyFinished = NO; | |
| _cancelled = NO; | |
| } | |
| return self; | |
| } | |
| - (instancetype)initWithWriteable:(id<GRXWriteable>)writeable { | |
| return [self initWithWriteable:writeable dispatchQueue:dispatch_get_main_queue()]; | |
| } | |
| - (void)enqueueValue:(id)value completionHandler:(void (^)(void))handler { | |
| dispatch_async(_writeableQueue, ^{ | |
| if (self->_alreadyFinished) { | |
| return; | |
| } | |
| @synchronized(self) { | |
| if (self->_cancelled) { | |
| return; | |
| } | |
| } | |
| [self.writeable writeValue:value]; | |
| handler(); | |
| }); | |
| } | |
| - (void)enqueueSuccessfulCompletion { | |
| dispatch_async(_writeableQueue, ^{ | |
| if (self->_alreadyFinished) { | |
| return; | |
| } | |
| @synchronized(self) { | |
| if (self->_cancelled) { | |
| return; | |
| } | |
| } | |
| [self.writeable writesFinishedWithError:nil]; | |
| // Skip any possible message to the wrapped writeable enqueued after this one. | |
| self->_alreadyFinished = YES; | |
| self.writeable = nil; | |
| }); | |
| } | |
| - (void)cancelWithError:(NSError *)error { | |
| NSAssert(error != nil, @"For a successful completion, use enqueueSuccessfulCompletion."); | |
| @synchronized(self) { | |
| self->_cancelled = YES; | |
| } | |
| dispatch_async(_writeableQueue, ^{ | |
| if (self->_alreadyFinished) { | |
| // a cancel or a successful completion is already issued | |
| return; | |
| } | |
| [self.writeable writesFinishedWithError:error]; | |
| // Skip any possible message to the wrapped writeable enqueued after this one. | |
| self->_alreadyFinished = YES; | |
| self.writeable = nil; | |
| }); | |
| } | |
| - (void)cancelSilently { | |
| dispatch_async(_writeableQueue, ^{ | |
| if (self->_alreadyFinished) { | |
| return; | |
| } | |
| self.writeable = nil; | |
| }); | |
| } | |
| @end |