diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc
index 2a74a3b34093ff67fecb368a37c06cde559ed794..711d0d5870968835eb6679fa81a50a51e0b2d02c 100644
--- a/src/compiler/objective_c_generator.cc
+++ b/src/compiler/objective_c_generator.cc
@@ -67,7 +67,7 @@ void PrintMethodSignature(Printer *printer, const MethodDescriptor *method,
 
   printer->Print(vars, "- ($return_type$)$method_name$With");
   if (method->client_streaming()) {
-    printer->Print("RequestsWriter:(id<GRXWriter>)requestWriter");
+    printer->Print("RequestsWriter:(GRXWriter *)requestWriter");
   } else {
     printer->Print(vars, "Request:($request_class$ *)request");
   }
diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h
index cba53fa2f67a0713e9769d042d1612f9f26a283b..4a8b7fff4860a9bbba23fbfbf39209c73269df9b 100644
--- a/src/objective-c/GRPCClient/GRPCCall.h
+++ b/src/objective-c/GRPCClient/GRPCCall.h
@@ -52,7 +52,7 @@
 extern id const kGRPCStatusMetadataKey;
 
 // Represents a single gRPC remote call.
-@interface GRPCCall : NSObject<GRXWriter>
+@interface GRPCCall : GRXWriter
 
 // These HTTP headers will be passed to the server as part of this call. Each HTTP header is a
 // name-value pair with string names and either string or binary values.
@@ -89,7 +89,7 @@ extern id const kGRPCStatusMetadataKey;
 // To finish a call right away, invoke cancel.
 - (instancetype)initWithHost:(NSString *)host
                         path:(NSString *)path
-              requestsWriter:(id<GRXWriter>)requestsWriter NS_DESIGNATED_INITIALIZER;
+              requestsWriter:(GRXWriter *)requestsWriter NS_DESIGNATED_INITIALIZER;
 
 // Finishes the request side of this call, notifies the server that the RPC
 // should be cancelled, and finishes the response side of the call with an error
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m
index 4ac4e4d37f5ca3fc11bb8d6fc80cbcc1fd4a47a8..53e5abe177bae77d210547d6dc74b6cc63ddb312 100644
--- a/src/objective-c/GRPCClient/GRPCCall.m
+++ b/src/objective-c/GRPCClient/GRPCCall.m
@@ -79,7 +79,7 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
   // all. This wrapper over our actual writeable ensures thread-safety and
   // correct ordering.
   GRPCDelegateWrapper *_responseWriteable;
-  id<GRXWriter> _requestWriter;
+  GRXWriter *_requestWriter;
 
   NSMutableDictionary *_requestMetadata;
   NSMutableDictionary *_responseMetadata;
@@ -94,7 +94,7 @@ NSString * const kGRPCStatusMetadataKey = @"io.grpc.StatusMetadataKey";
 // Designated initializer
 - (instancetype)initWithHost:(NSString *)host
                         path:(NSString *)path
-              requestsWriter:(id<GRXWriter>)requestWriter {
+              requestsWriter:(GRXWriter *)requestWriter {
   if (!host || !path) {
     [NSException raise:NSInvalidArgumentException format:@"Neither host nor method can be nil."];
   }
diff --git a/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h b/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h
index 1ef245fe37bde42f0d4bfc89fbb9dd60ee8f6b2e..9a30a2f9660be3764573b3bf3d5f060f8d3e360d 100644
--- a/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h
+++ b/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.h
@@ -33,8 +33,9 @@
 
 #import <Foundation/Foundation.h>
 
+#import <RxLibrary/GRXWriter.h>
+
 @protocol GRXWriteable;
-@protocol GRXWriter;
 
 // This is a thread-safe wrapper over a GRXWriteable instance. It lets one
 // enqueue calls to a GRXWriteable instance for the main thread, guaranteeing
@@ -54,7 +55,7 @@
 // writesFinishedWithError: is sent to the writeable, and released after that.
 // This is used to create a retain cycle that keeps both objects alive until the
 // writing is explicitly finished.
-- (instancetype)initWithWriteable:(id<GRXWriteable>)writeable writer:(id<GRXWriter>)writer
+- (instancetype)initWithWriteable:(id<GRXWriteable>)writeable writer:(GRXWriter *)writer
     NS_DESIGNATED_INITIALIZER;
 
 // Enqueues writeValue: to be sent to the writeable in the main thread.
diff --git a/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m b/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m
index 59c0565494b107c1678db7c59f8f2ed42f678a31..294cfb7e2394f2ac04bbb432ea87d01ba6ca0c5c 100644
--- a/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m
+++ b/src/objective-c/GRPCClient/private/GRPCDelegateWrapper.m
@@ -38,7 +38,7 @@
 @interface GRPCDelegateWrapper ()
 // These are atomic so that cancellation can nillify them from any thread.
 @property(atomic, strong) id<GRXWriteable> writeable;
-@property(atomic, strong) id<GRXWriter> writer;
+@property(atomic, strong) GRXWriter *writer;
 @end
 
 @implementation GRPCDelegateWrapper {
@@ -52,7 +52,7 @@
 }
 
 // Designated initializer
-- (instancetype)initWithWriteable:(id<GRXWriteable>)writeable writer:(id<GRXWriter>)writer {
+- (instancetype)initWithWriteable:(id<GRXWriteable>)writeable writer:(GRXWriter *)writer {
   if (self = [super init]) {
     _writeableQueue = dispatch_get_main_queue();
     _writeable = writeable;
diff --git a/src/objective-c/ProtoRPC/ProtoRPC.h b/src/objective-c/ProtoRPC/ProtoRPC.h
index fcc0a507feb0528814534057f25a6db9f33b2117..bd926b732873efb887aced402518612f6ead9db5 100644
--- a/src/objective-c/ProtoRPC/ProtoRPC.h
+++ b/src/objective-c/ProtoRPC/ProtoRPC.h
@@ -40,7 +40,7 @@
 
 - (instancetype)initWithHost:(NSString *)host
                       method:(ProtoMethod *)method
-              requestsWriter:(id<GRXWriter>)requestsWriter
+              requestsWriter:(GRXWriter *)requestsWriter
                responseClass:(Class)responseClass
           responsesWriteable:(id<GRXWriteable>)responsesWriteable NS_DESIGNATED_INITIALIZER;
 
diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m
index fe3ccf054139870f96aa8c552d10b12eddca55c2..889d71a3084008101d480e7bc983b2c41842ab47 100644
--- a/src/objective-c/ProtoRPC/ProtoRPC.m
+++ b/src/objective-c/ProtoRPC/ProtoRPC.m
@@ -35,7 +35,6 @@
 
 #import <GPBProtocolBuffers.h>
 #import <RxLibrary/GRXWriteable.h>
-#import <RxLibrary/GRXWriter.h>
 #import <RxLibrary/GRXWriter+Transformations.h>
 
 @implementation ProtoRPC {
@@ -46,7 +45,7 @@
 #pragma clang diagnostic ignored "-Wobjc-designated-initializers"
 - (instancetype)initWithHost:(NSString *)host
                         path:(NSString *)path
-              requestsWriter:(id<GRXWriter>)requestsWriter {
+              requestsWriter:(GRXWriter *)requestsWriter {
   [NSException raise:NSInvalidArgumentException
               format:@"Please use ProtoRPC's designated initializer instead."];
   return nil;
@@ -56,7 +55,7 @@
 // Designated initializer
 - (instancetype)initWithHost:(NSString *)host
                       method:(ProtoMethod *)method
-              requestsWriter:(id<GRXWriter>)requestsWriter
+              requestsWriter:(GRXWriter *)requestsWriter
                responseClass:(Class)responseClass
           responsesWriteable:(id<GRXWriteable>)responsesWriteable {
   // Because we can't tell the type system to constrain the class, we need to check at runtime:
@@ -65,12 +64,11 @@
                 format:@"A protobuf class to parse the responses must be provided."];
   }
   // A writer that serializes the proto messages to send.
-  id<GRXWriter> bytesWriter =
-      [[[GRXWriter alloc] initWithWriter:requestsWriter] map:^id(GPBMessage *proto) {
-        // TODO(jcanizales): Fail with an understandable error message if the requestsWriter isn't
-        // sending GPBMessages.
-        return [proto data];
-      }];
+  GRXWriter *bytesWriter = [requestsWriter map:^id(GPBMessage *proto) {
+    // TODO(jcanizales): Fail with an understandable error message if the requestsWriter isn't
+    // sending GPBMessages.
+    return [proto data];
+  }];
   if ((self = [super initWithHost:host path:method.HTTPPath requestsWriter:bytesWriter])) {
     // A writeable that parses the proto messages received.
     _responseWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
diff --git a/src/objective-c/ProtoRPC/ProtoService.h b/src/objective-c/ProtoRPC/ProtoService.h
index c5ef820f48a765692082b1908c1c3f2405797278..2e8cb33696b99ce472518938699f300a7eff8da1 100644
--- a/src/objective-c/ProtoRPC/ProtoService.h
+++ b/src/objective-c/ProtoRPC/ProtoService.h
@@ -35,7 +35,7 @@
 
 @class ProtoRPC;
 @protocol GRXWriteable;
-@protocol GRXWriter;
+@class GRXWriter;
 
 @interface ProtoService : NSObject
 - (instancetype)initWithHost:(NSString *)host
@@ -43,7 +43,7 @@
                  serviceName:(NSString *)serviceName NS_DESIGNATED_INITIALIZER;
 
 - (ProtoRPC *)RPCToMethod:(NSString *)method
-           requestsWriter:(id<GRXWriter>)requestsWriter
+           requestsWriter:(GRXWriter *)requestsWriter
   	        responseClass:(Class)responseClass
   	   responsesWriteable:(id<GRXWriteable>)responsesWriteable;
 @end
diff --git a/src/objective-c/ProtoRPC/ProtoService.m b/src/objective-c/ProtoRPC/ProtoService.m
index d7c5b6a8501dc0e83fc113fd842b05695facf8d9..fccc6aadc9cc8488d1f7b424795c7d9e5d1dc581 100644
--- a/src/objective-c/ProtoRPC/ProtoService.m
+++ b/src/objective-c/ProtoRPC/ProtoService.m
@@ -66,7 +66,7 @@
 }
 
 - (ProtoRPC *)RPCToMethod:(NSString *)method
-           requestsWriter:(id<GRXWriter>)requestsWriter
+           requestsWriter:(GRXWriter *)requestsWriter
             responseClass:(Class)responseClass
        responsesWriteable:(id<GRXWriteable>)responsesWriteable {
   ProtoMethod *methodName = [[ProtoMethod alloc] initWithPackage:_packageName
diff --git a/src/objective-c/RxLibrary/GRXBufferedPipe.h b/src/objective-c/RxLibrary/GRXBufferedPipe.h
index 5e876a73bfe3db670198e51f8ae94f37b73f4380..b6296e1ed7236f0bd914a6b74b82d2086a563ab1 100644
--- a/src/objective-c/RxLibrary/GRXBufferedPipe.h
+++ b/src/objective-c/RxLibrary/GRXBufferedPipe.h
@@ -51,7 +51,7 @@
 // pipe will keep buffering all data written to it, your application could run out of memory and
 // crash. If you want to react to flow control signals to prevent that, instead of using this class
 // you can implement an object that conforms to GRXWriter.
-@interface GRXBufferedPipe : NSObject<GRXWriteable, GRXWriter>
+@interface GRXBufferedPipe : GRXWriter<GRXWriteable>
 
 // Convenience constructor.
 + (instancetype)pipe;
diff --git a/src/objective-c/RxLibrary/GRXForwardingWriter.h b/src/objective-c/RxLibrary/GRXForwardingWriter.h
new file mode 100644
index 0000000000000000000000000000000000000000..d004333d2b4958bcca80d45e302e2b3ca0d861c8
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXForwardingWriter.h
@@ -0,0 +1,43 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import "GRXWriter.h"
+
+// A "proxy" class that simply forwards values, completion, and errors from its
+// input writer to its writeable.
+// It is useful as a superclass for pipes that act as a transformation of their
+// input writer, and for classes that represent objects with input and
+// output sequences of values, like an RPC.
+@interface GRXForwardingWriter : GRXWriter
+- (instancetype)initWithWriter:(GRXWriter *)writer NS_DESIGNATED_INITIALIZER;
+@end
diff --git a/src/objective-c/RxLibrary/GRXForwardingWriter.m b/src/objective-c/RxLibrary/GRXForwardingWriter.m
new file mode 100644
index 0000000000000000000000000000000000000000..2342f51ab3616955d2d4d8fe571af1ff80f31be2
--- /dev/null
+++ b/src/objective-c/RxLibrary/GRXForwardingWriter.m
@@ -0,0 +1,112 @@
+/*
+ *
+ * Copyright 2015, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#import "GRXForwardingWriter.h"
+
+@interface GRXForwardingWriter () <GRXWriteable>
+@end
+
+@implementation GRXForwardingWriter {
+  GRXWriter *_writer;
+  id<GRXWriteable> _writeable;
+}
+
+- (instancetype)init {
+  return [self initWithWriter:nil];
+}
+
+// Designated initializer
+- (instancetype)initWithWriter:(GRXWriter *)writer {
+  if (!writer) {
+    [NSException raise:NSInvalidArgumentException format:@"writer can't be nil."];
+  }
+  if ((self = [super init])) {
+    _writer = writer;
+  }
+  return self;
+}
+
+// This is used to send a completion or an error to the writeable. It nillifies
+// our reference to it in order to guarantee no more messages are sent to it,
+// and to release it.
+- (void)finishOutputWithError:(NSError *)errorOrNil {
+  id<GRXWriteable> writeable = _writeable;
+  _writeable = nil;
+  [writeable writesFinishedWithError:errorOrNil];
+}
+
+// This is used to stop the input writer. It nillifies our reference to it
+// to release it.
+- (void)finishInput {
+  GRXWriter *writer = _writer;
+  _writer = nil;
+  writer.state = GRXWriterStateFinished;
+}
+
+#pragma mark GRXWriteable implementation
+
+- (void)writeValue:(id)value {
+  [_writeable writeValue:value];
+}
+
+- (void)writesFinishedWithError:(NSError *)errorOrNil {
+  _writer = nil;
+  [self finishOutputWithError:errorOrNil];
+}
+
+#pragma mark GRXWriter implementation
+
+- (GRXWriterState)state {
+  return _writer ? _writer.state : GRXWriterStateFinished;
+}
+
+- (void)setState:(GRXWriterState)state {
+  if (state == GRXWriterStateFinished) {
+    _writeable = nil;
+    [self finishInput];
+  } else {
+    _writer.state = state;
+  }
+}
+
+- (void)startWithWriteable:(id<GRXWriteable>)writeable {
+  _writeable = writeable;
+  [_writer startWithWriteable:self];
+}
+
+- (void)finishWithError:(NSError *)errorOrNil {
+  [self finishOutputWithError:errorOrNil];
+  [self finishInput];
+}
+
+@end
diff --git a/src/objective-c/RxLibrary/GRXImmediateWriter.h b/src/objective-c/RxLibrary/GRXImmediateWriter.h
index f86d38dcd8157526f897002a5ed7315983dcddca..b171f0c760a7a4436b58e217687acfb51c9d0da0 100644
--- a/src/objective-c/RxLibrary/GRXImmediateWriter.h
+++ b/src/objective-c/RxLibrary/GRXImmediateWriter.h
@@ -40,15 +40,15 @@
 //
 // Unless the writeable callback pauses them or stops them early, these writers will do all their
 // interactions with the writeable before the start method returns.
-@interface GRXImmediateWriter : NSObject<GRXWriter>
+@interface GRXImmediateWriter : GRXWriter
 
 // Returns a writer that pulls values from the passed NSEnumerator instance and pushes them to
 // its writeable. The NSEnumerator is released when it finishes.
-+ (id<GRXWriter>)writerWithEnumerator:(NSEnumerator *)enumerator;
++ (GRXWriter *)writerWithEnumerator:(NSEnumerator *)enumerator;
 
 // Returns a writer that pushes to its writeable the successive values returned by the passed
 // block. When the block first returns nil, it is released.
-+ (id<GRXWriter>)writerWithValueSupplier:(id (^)())block;
++ (GRXWriter *)writerWithValueSupplier:(id (^)())block;
 
 // Returns a writer that iterates over the values of the passed container and pushes them to
 // its writeable. The container is released when the iteration is over.
@@ -56,18 +56,18 @@
 // Note that the usual speed gain of NSFastEnumeration over NSEnumerator results from not having to
 // call one method per element. Because GRXWriteable instances accept values one by one, that speed
 // gain doesn't happen here.
-+ (id<GRXWriter>)writerWithContainer:(id<NSFastEnumeration>)container;
++ (GRXWriter *)writerWithContainer:(id<NSFastEnumeration>)container;
 
 // Returns a writer that sends the passed value to its writeable and then finishes (releasing the
 // value).
-+ (id<GRXWriter>)writerWithValue:(id)value;
++ (GRXWriter *)writerWithValue:(id)value;
 
 // Returns a writer that, as part of its start method, sends the passed error to the writeable
 // (then releasing the error).
-+ (id<GRXWriter>)writerWithError:(NSError *)error;
++ (GRXWriter *)writerWithError:(NSError *)error;
 
 // Returns a writer that, as part of its start method, finishes immediately without sending any
 // values to its writeable.
-+ (id<GRXWriter>)emptyWriter;
++ (GRXWriter *)emptyWriter;
 
 @end
diff --git a/src/objective-c/RxLibrary/GRXImmediateWriter.m b/src/objective-c/RxLibrary/GRXImmediateWriter.m
index 0b4979872e2f553bb0b9cb5e9eaed9a95a82a02b..b6d2b2cac0ddb44e9e5d2bf3d46b6102480521d3 100644
--- a/src/objective-c/RxLibrary/GRXImmediateWriter.m
+++ b/src/objective-c/RxLibrary/GRXImmediateWriter.m
@@ -63,19 +63,19 @@
   return [[self alloc] initWithEnumerator:enumerator error:errorOrNil];
 }
 
-+ (id<GRXWriter>)writerWithEnumerator:(NSEnumerator *)enumerator {
++ (GRXWriter *)writerWithEnumerator:(NSEnumerator *)enumerator {
   return [self writerWithEnumerator:enumerator error:nil];
 }
 
-+ (id<GRXWriter>)writerWithValueSupplier:(id (^)())block {
++ (GRXWriter *)writerWithValueSupplier:(id (^)())block {
   return [self writerWithEnumerator:[NSEnumerator grx_enumeratorWithValueSupplier:block]];
 }
 
-+ (id<GRXWriter>)writerWithContainer:(id<NSFastEnumeration>)container {
++ (GRXWriter *)writerWithContainer:(id<NSFastEnumeration>)container {
   return [self writerWithEnumerator:[NSEnumerator grx_enumeratorWithContainer:container]];;
 }
 
-+ (id<GRXWriter>)writerWithValue:(id)value {
++ (GRXWriter *)writerWithValue:(id)value {
   if (value) {
     return [self writerWithEnumerator:[NSEnumerator grx_enumeratorWithSingleValue:value]];
   } else {
@@ -83,7 +83,7 @@
   }
 }
 
-+ (id<GRXWriter>)writerWithError:(NSError *)error {
++ (GRXWriter *)writerWithError:(NSError *)error {
   if (error) {
     return [self writerWithEnumerator:nil error:error];
   } else {
@@ -91,7 +91,7 @@
   }
 }
 
-+ (id<GRXWriter>)emptyWriter {
++ (GRXWriter *)emptyWriter {
   static GRXImmediateWriter *emptyWriter;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
diff --git a/src/objective-c/RxLibrary/GRXWriter+Immediate.m b/src/objective-c/RxLibrary/GRXWriter+Immediate.m
index 39c54f86ec6eb5cd6030918d68f1b9ace0e2e826..1d55eb35293ecddf4757b792d8b0a764fb533a9d 100644
--- a/src/objective-c/RxLibrary/GRXWriter+Immediate.m
+++ b/src/objective-c/RxLibrary/GRXWriter+Immediate.m
@@ -38,27 +38,27 @@
 @implementation GRXWriter (Immediate)
 
 + (instancetype)writerWithEnumerator:(NSEnumerator *)enumerator {
-  return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithEnumerator:enumerator]];
+  return [GRXImmediateWriter writerWithEnumerator:enumerator];
 }
 
 + (instancetype)writerWithValueSupplier:(id (^)())block {
-  return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithValueSupplier:block]];
+  return [GRXImmediateWriter writerWithValueSupplier:block];
 }
 
 + (instancetype)writerWithContainer:(id<NSFastEnumeration>)container {
-  return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithContainer:container]];
+  return [GRXImmediateWriter writerWithContainer:container];
 }
 
 + (instancetype)writerWithValue:(id)value {
-  return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithValue:value]];
+  return [GRXImmediateWriter writerWithValue:value];
 }
 
 + (instancetype)writerWithError:(NSError *)error {
-  return [[self alloc] initWithWriter:[GRXImmediateWriter writerWithError:error]];
+  return [GRXImmediateWriter writerWithError:error];
 }
 
 + (instancetype)emptyWriter {
-  return [[self alloc] initWithWriter:[GRXImmediateWriter emptyWriter]];
+  return [GRXImmediateWriter emptyWriter];
 }
 
 @end
diff --git a/src/objective-c/RxLibrary/GRXWriter.h b/src/objective-c/RxLibrary/GRXWriter.h
index dcf44e31433945f712119bb9a15843dcc869c265..5d6e1a472af965b4a5d084ff4fcf2031c5ab5383 100644
--- a/src/objective-c/RxLibrary/GRXWriter.h
+++ b/src/objective-c/RxLibrary/GRXWriter.h
@@ -85,7 +85,7 @@ typedef NS_ENUM(NSInteger, GRXWriterState) {
 // Unless otherwise indicated by a conforming class, no messages should be sent
 // concurrently to a GRXWriter. I.e., conforming classes aren't required to
 // be thread-safe.
-@protocol GRXWriter <NSObject>
+@interface GRXWriter : NSObject
 
 // This property can be used to query the current state of the writer, which
 // determines how it might currently use its writeable. Some state transitions can
@@ -116,12 +116,3 @@ typedef NS_ENUM(NSInteger, GRXWriterState) {
 // can't remember the details in the presence of concurrency.
 - (void)finishWithError:(NSError *)errorOrNil;
 @end
-
-// A "proxy" class that simply forwards values, completion, and errors from its
-// input writer to its writeable.
-// It is useful as a superclass for pipes that act as a transformation of their
-// input writer, and for classes that represent objects with input and
-// output sequences of values, like an RPC.
-@interface GRXWriter : NSObject<GRXWriter>
-- (instancetype)initWithWriter:(id<GRXWriter>)writer NS_DESIGNATED_INITIALIZER;
-@end
diff --git a/src/objective-c/RxLibrary/GRXWriter.m b/src/objective-c/RxLibrary/GRXWriter.m
index cc143835605dbddca187931535557805ff199f05..019fcbd7858320819cb9648dde98f62ccb4874e8 100644
--- a/src/objective-c/RxLibrary/GRXWriter.m
+++ b/src/objective-c/RxLibrary/GRXWriter.m
@@ -33,80 +33,6 @@
 
 #import "GRXWriter.h"
 
-@interface GRXWriter () <GRXWriteable>
-@end
-
-@implementation GRXWriter {
-  id<GRXWriter> _writer;
-  id<GRXWriteable> _writeable;
-}
-
-- (instancetype)init {
-  return [self initWithWriter:nil];
-}
-
-// Designated initializer
-- (instancetype)initWithWriter:(id<GRXWriter>)writer {
-  if (!writer) {
-    [NSException raise:NSInvalidArgumentException format:@"writer can't be nil."];
-  }
-  if ((self = [super init])) {
-    _writer = writer;
-  }
-  return self;
-}
-
-// This is used to send a completion or an error to the writeable. It nillifies
-// our reference to it in order to guarantee no more messages are sent to it,
-// and to release it.
-- (void)finishOutputWithError:(NSError *)errorOrNil {
-  id<GRXWriteable> writeable = _writeable;
-  _writeable = nil;
-  [writeable writesFinishedWithError:errorOrNil];
-}
-
-// This is used to stop the input writer. It nillifies our reference to it
-// to release it.
-- (void)finishInput {
-  id<GRXWriter> writer = _writer;
-  _writer = nil;
-  writer.state = GRXWriterStateFinished;
-}
-
-#pragma mark GRXWriteable implementation
-
-- (void)writeValue:(id)value {
-  [_writeable writeValue:value];
-}
-
-- (void)writesFinishedWithError:(NSError *)errorOrNil {
-  _writer = nil;
-  [self finishOutputWithError:errorOrNil];
-}
-
-#pragma mark GRXWriter implementation
-
-- (GRXWriterState)state {
-  return _writer ? _writer.state : GRXWriterStateFinished;
-}
-
-- (void)setState:(GRXWriterState)state {
-  if (state == GRXWriterStateFinished) {
-    _writeable = nil;
-    [self finishInput];
-  } else {
-    _writer.state = state;
-  }
-}
-
-- (void)startWithWriteable:(id<GRXWriteable>)writeable {
-  _writeable = writeable;
-  [_writer startWithWriteable:self];
-}
-
-- (void)finishWithError:(NSError *)errorOrNil {
-  [self finishOutputWithError:errorOrNil];
-  [self finishInput];
-}
+@implementation GRXWriter
 
 @end
diff --git a/src/objective-c/RxLibrary/transformations/GRXMappingWriter.h b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.h
index 55f6f82f20b1b54853cafdfc5e842f61e00e8682..43b87068647d45ef8bc1c4cf5475e37204a337e1 100644
--- a/src/objective-c/RxLibrary/transformations/GRXMappingWriter.h
+++ b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.h
@@ -31,10 +31,10 @@
  *
  */
 
-#import "RxLibrary/GRXWriter.h"
+#import "RxLibrary/GRXForwardingWriter.h"
 
 // A "proxy" writer that transforms all the values of its input writer by using a mapping function.
-@interface GRXMappingWriter : GRXWriter
-- (instancetype)initWithWriter:(id<GRXWriter>)writer map:(id (^)(id value))map
+@interface GRXMappingWriter : GRXForwardingWriter
+- (instancetype)initWithWriter:(GRXWriter *)writer map:(id (^)(id value))map
     NS_DESIGNATED_INITIALIZER;
 @end
diff --git a/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m
index 2cdfea1b6751a54429f6d39a4755eeac9b98bba2..f3242e4fa95b4749f7cbfd386341baf9c19823f3 100644
--- a/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m
+++ b/src/objective-c/RxLibrary/transformations/GRXMappingWriter.m
@@ -37,19 +37,19 @@ static id (^kIdentity)(id value) = ^id(id value) {
   return value;
 };
 
-@interface GRXWriter () <GRXWriteable>
+@interface GRXForwardingWriter () <GRXWriteable>
 @end
 
 @implementation GRXMappingWriter {
   id (^_map)(id value);
 }
 
-- (instancetype)initWithWriter:(id<GRXWriter>)writer {
+- (instancetype)initWithWriter:(GRXWriter *)writer {
   return [self initWithWriter:writer map:nil];
 }
 
 // Designated initializer
-- (instancetype)initWithWriter:(id<GRXWriter>)writer map:(id (^)(id value))map {
+- (instancetype)initWithWriter:(GRXWriter *)writer map:(id (^)(id value))map {
   if ((self = [super initWithWriter:writer])) {
     _map = map ?: kIdentity;
   }
diff --git a/src/objective-c/tests/GRPCClientTests.m b/src/objective-c/tests/GRPCClientTests.m
index 2e03ad88a82d0adb91cda5f6422a63f7b42f8aff..3210ad70502c59d5cf4d19be55913187a3c4724e 100644
--- a/src/objective-c/tests/GRPCClientTests.m
+++ b/src/objective-c/tests/GRPCClientTests.m
@@ -120,7 +120,7 @@ static ProtoMethod *kUnaryCallMethod;
   request.responseSize = 100;
   request.fillUsername = YES;
   request.fillOauthScope = YES;
-  id<GRXWriter> requestsWriter = [GRXWriter writerWithValue:[request data]];
+  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
 
   GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
                                              path:kUnaryCallMethod.HTTPPath
@@ -150,7 +150,7 @@ static ProtoMethod *kUnaryCallMethod;
   RMTSimpleRequest *request = [RMTSimpleRequest message];
   request.fillUsername = YES;
   request.fillOauthScope = YES;
-  id<GRXWriter> requestsWriter = [GRXWriter writerWithValue:[request data]];
+  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[request data]];
 
   GRPCCall *call = [[GRPCCall alloc] initWithHost:kHostAddress
                                              path:kUnaryCallMethod.HTTPPath
diff --git a/src/objective-c/tests/InteropTests.m b/src/objective-c/tests/InteropTests.m
index 38fc65839c55ed901a73fc8f8a7c64ab12e18580..501f33317a1cb1bc71d2e98c4d8dd52e233bc0d5 100644
--- a/src/objective-c/tests/InteropTests.m
+++ b/src/objective-c/tests/InteropTests.m
@@ -143,7 +143,7 @@
   RMTStreamingInputCallRequest *request4 = [RMTStreamingInputCallRequest message];
   request4.payload.body = [NSMutableData dataWithLength:45904];
 
-  id<GRXWriter> writer = [GRXWriter writerWithContainer:@[request1, request2, request3, request4]];
+  GRXWriter *writer = [GRXWriter writerWithContainer:@[request1, request2, request3, request4]];
 
   [_service streamingInputCallWithRequestsWriter:writer
                                          handler:^(RMTStreamingInputCallResponse *response,
diff --git a/src/objective-c/tests/LocalClearTextTests.m b/src/objective-c/tests/LocalClearTextTests.m
index 10c9f13ea3bbdf3d885d0677f94248a772ce3b33..4317614dd9d3ce1208d5ed33e57c41fa6653c555 100644
--- a/src/objective-c/tests/LocalClearTextTests.m
+++ b/src/objective-c/tests/LocalClearTextTests.m
@@ -64,7 +64,7 @@ static NSString * const kService = @"RouteGuide";
 //                                                         interface:kService
 //                                                            method:@"EmptyCall"];
 //
-//  id<GRXWriter> requestsWriter = [GRXWriter writerWithValue:[NSData data]];
+//  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[NSData data]];
 //
 //  GRPCCall *call = [[GRPCCall alloc] initWithHost:kRouteGuideHost
 //                                           method:method
@@ -91,7 +91,7 @@ static NSString * const kService = @"RouteGuide";
                                                      service:kService
                                                       method:@"RecordRoute"];
 
-  id<GRXWriter> requestsWriter = [GRXWriter emptyWriter];
+  GRXWriter *requestsWriter = [GRXWriter emptyWriter];
 
   GRPCCall *call = [[GRPCCall alloc] initWithHost:kRouteGuideHost
                                              path:method.HTTPPath
@@ -122,7 +122,7 @@ static NSString * const kService = @"RouteGuide";
   RGDPoint *point = [RGDPoint message];
   point.latitude = 28E7;
   point.longitude = -15E7;
-  id<GRXWriter> requestsWriter = [GRXWriter writerWithValue:[point data]];
+  GRXWriter *requestsWriter = [GRXWriter writerWithValue:[point data]];
 
   GRPCCall *call = [[GRPCCall alloc] initWithHost:kRouteGuideHost
                                              path:method.HTTPPath