diff --git a/bazel/grpc_build_system.bzl b/bazel/grpc_build_system.bzl
index f386b87b583c13879b84c167feff947efbcaa755..23f90d0dc806a05ea11005fae9619ad7237232b9 100644
--- a/bazel/grpc_build_system.bzl
+++ b/bazel/grpc_build_system.bzl
@@ -24,7 +24,6 @@
 #
 
 load("//bazel:cc_grpc_library.bzl", "cc_grpc_library")
-load("@build_bazel_rules_apple//apple:resources.bzl", "apple_resource_bundle")
 load("@upb//bazel:upb_proto_library.bzl", "upb_proto_library")
 load("@build_bazel_rules_apple//apple:ios.bzl", "ios_unit_test")
 
@@ -239,19 +238,13 @@ def grpc_cc_binary(name, srcs = [], deps = [], external_deps = [], args = [], da
     )
 
 def grpc_generate_one_off_targets():
-    apple_resource_bundle(
-        # The choice of name is signicant here, since it determines the bundle name.
-        name = "gRPCCertificates",
-        resources = ["etc/roots.pem"],
-    )
-
     # In open-source, grpc_objc* libraries depend directly on //:grpc
     native.alias(
         name = "grpc_objc",
         actual = "//:grpc",
     )
 
-def grpc_objc_use_cronet_config():
+def grpc_generate_objc_one_off_targets():
     pass
 
 def grpc_sh_test(name, srcs, args = [], data = []):
@@ -296,7 +289,7 @@ def grpc_package(name, visibility = "private", features = []):
 
 def grpc_objc_library(
         name,
-        srcs,
+        srcs = [],
         hdrs = [],
         textual_hdrs = [],
         data = [],
diff --git a/gRPC-ProtoRPC.podspec b/gRPC-ProtoRPC.podspec
index 57dac5a10e52d240a81563f87bdff2dd6818a5e1..4c5ebb36562c64cdc04f99192fa5d965b098078d 100644
--- a/gRPC-ProtoRPC.podspec
+++ b/gRPC-ProtoRPC.podspec
@@ -42,22 +42,40 @@ Pod::Spec.new do |s|
   s.module_name = name
   s.header_dir = name
 
-  src_dir = 'src/objective-c/ProtoRPC'
+  s.default_subspec = 'Main', 'Legacy', 'Legacy-Header'
 
-  s.default_subspec = 'Main'
+  s.subspec 'Legacy-Header' do |ss|
+    ss.header_mappings_dir = "src/objective-c/ProtoRPC"
+    ss.public_header_files = "src/objective-c/ProtoRPC/ProtoRPCLegacy.h"
+    ss.source_files = "src/objective-c/ProtoRPC/ProtoRPCLegacy.h"
+  end
 
   s.subspec 'Main' do |ss|
-    ss.header_mappings_dir = "#{src_dir}"
-    ss.dependency 'gRPC', version
+    ss.header_mappings_dir = "src/objective-c/ProtoRPC"
+    ss.dependency "#{s.name}/Legacy-Header", version
+    ss.dependency 'gRPC/Interface', version
+    ss.dependency 'Protobuf', '~> 3.0'
+
+    ss.source_files = "src/objective-c/ProtoRPC/ProtoMethod.{h,m}",
+                      "src/objective-c/ProtoRPC/ProtoRPC.{h,m}",
+                      "src/objective-c/ProtoRPC/ProtoService.{h,m}"
+  end
+
+  s.subspec 'Legacy' do |ss|
+    ss.header_mappings_dir = "src/objective-c/ProtoRPC"
+    ss.dependency "#{s.name}/Main", version
+    ss.dependency "#{s.name}/Legacy-Header", version
+    ss.dependency 'gRPC/GRPCCore', version
     ss.dependency 'gRPC-RxLibrary', version
     ss.dependency 'Protobuf', '~> 3.0'
 
-    ss.source_files = "#{src_dir}/*.{h,m}"
+    ss.source_files = "src/objective-c/ProtoRPC/ProtoRPCLegacy.m",
+                      "src/objective-c/ProtoRPC/ProtoServiceLegacy.m"
   end
 
   # CFStream is now default. Leaving this subspec only for compatibility purpose.
   s.subspec 'CFStream' do |ss|
-    ss.dependency "#{s.name}/Main", version
+    ss.dependency "#{s.name}/Legacy", version
   end
 
   s.pod_target_xcconfig = {
diff --git a/gRPC-RxLibrary.podspec b/gRPC-RxLibrary.podspec
index 9666c3c1b98a139263d9a8e2795c2a9caaf1f99d..2e412cf67d69112f12f9a945f71facbda4381b11 100644
--- a/gRPC-RxLibrary.podspec
+++ b/gRPC-RxLibrary.podspec
@@ -42,6 +42,23 @@ Pod::Spec.new do |s|
   s.module_name = name
   s.header_dir = name
 
+  s.default_subspec = 'Interface', 'Implementation'
+
+  src_dir = 'src/objective-c/RxLibrary'
+  s.subspec 'Interface' do |ss|
+    ss.header_mappings_dir = "#{src_dir}"
+    ss.source_files = "#{src_dir}/*.h"
+    ss.public_header_files = "#{src_dir}/*.h"
+  end
+
+  s.subspec 'Implementation' do |ss|
+    ss.header_mappings_dir = "#{src_dir}"
+    ss.source_files = "#{src_dir}/*.m", "#{src_dir}/**/*.{h,m}"
+    ss.private_header_files = "#{src_dir}/**/*.h"
+
+    ss.dependency "#{s.name}/Interface"
+  end
+
   src_dir = 'src/objective-c/RxLibrary'
   s.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}"
   s.private_header_files = "#{src_dir}/private/*.h"
diff --git a/gRPC.podspec b/gRPC.podspec
index 09baf1e3b4b21b5f3428afca4b4f35f96446112a..e18adcd276e8d3fca1c9b8a4a9ddeff045a1673e 100644
--- a/gRPC.podspec
+++ b/gRPC.podspec
@@ -41,13 +41,7 @@ Pod::Spec.new do |s|
   s.module_name = name
   s.header_dir = name
 
-  src_dir = 'src/objective-c/GRPCClient'
-
-  s.dependency 'gRPC-RxLibrary', version
-  s.default_subspec = 'Main'
-
-  # Certificates, to be able to establish TLS connections:
-  s.resource_bundles = { 'gRPCCertificates' => ['etc/roots.pem'] }
+  s.default_subspec = 'Interface', 'GRPCCore', 'Interface-Legacy'
 
   s.pod_target_xcconfig = {
     # This is needed by all pods that depend on gRPC-RxLibrary:
@@ -55,29 +49,103 @@ Pod::Spec.new do |s|
     'CLANG_WARN_STRICT_PROTOTYPES' => 'NO',
   }
 
-  s.subspec 'Main' do |ss|
-    ss.header_mappings_dir = "#{src_dir}"
-
-    ss.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}"
-    ss.exclude_files = "#{src_dir}/GRPCCall+GID.{h,m}"
-    ss.private_header_files = "#{src_dir}/private/*.h", "#{src_dir}/internal/*.h"
-
-    ss.dependency 'gRPC-Core', version
+  s.subspec 'Interface-Legacy' do |ss|
+    ss.header_mappings_dir = 'src/objective-c/GRPCClient'
+
+    ss.public_header_files = "GRPCClient/GRPCCall+ChannelArg.h",
+                             "GRPCClient/GRPCCall+ChannelCredentials.h",
+                             "GRPCClient/GRPCCall+Cronet.h",
+                             "GRPCClient/GRPCCall+OAuth2.h",
+                             "GRPCClient/GRPCCall+Tests.h",
+                             "src/objective-c/GRPCClient/GRPCCallLegacy.h",
+                             "src/objective-c/GRPCClient/GRPCTypes.h"
+
+    ss.source_files = "GRPCClient/GRPCCall+ChannelArg.h",
+                      "GRPCClient/GRPCCall+ChannelCredentials.h",
+                      "GRPCClient/GRPCCall+Cronet.h",
+                      "GRPCClient/GRPCCall+OAuth2.h",
+                      "GRPCClient/GRPCCall+Tests.h",
+                      "src/objective-c/GRPCClient/GRPCCallLegacy.h",
+                      "src/objective-c/GRPCClient/GRPCTypes.h"
+    ss.dependency "gRPC-RxLibrary/Interface", version
   end
 
-  # CFStream is now default. Leaving this subspec only for compatibility purpose.
-  s.subspec 'CFStream' do |ss|
-    ss.dependency "#{s.name}/Main", version
+  s.subspec 'Interface' do |ss|
+    ss.header_mappings_dir = 'src/objective-c/GRPCClient'
+
+    ss.public_header_files = 'src/objective-c/GRPCClient/GRPCCall.h',
+                             'src/objective-c/GRPCClient/GRPCCall+Interceptor.h',
+                             'src/objective-c/GRPCClient/GRPCCallOptions.h',
+                             'src/objective-c/GRPCClient/GRPCInterceptor.h',
+                             'src/objective-c/GRPCClient/GRPCTransport.h',
+                             'src/objective-c/GRPCClient/GRPCDispatchable.h',
+                             'src/objective-c/GRPCClient/version.h'
+
+    ss.source_files = 'src/objective-c/GRPCClient/GRPCCall.h',
+                      'src/objective-c/GRPCClient/GRPCCall.m',
+                      'src/objective-c/GRPCClient/GRPCCall+Interceptor.h',
+                      'src/objective-c/GRPCClient/GRPCCall+Interceptor.m',
+                      'src/objective-c/GRPCClient/GRPCCallOptions.h',
+                      'src/objective-c/GRPCClient/GRPCCallOptions.m',
+                      'src/objective-c/GRPCClient/GRPCDispatchable.h',
+                      'src/objective-c/GRPCClient/GRPCInterceptor.h',
+                      'src/objective-c/GRPCClient/GRPCInterceptor.m',
+                      'src/objective-c/GRPCClient/GRPCTransport.h',
+                      'src/objective-c/GRPCClient/GRPCTransport.m',
+                      'src/objective-c/GRPCClient/internal/*.h',
+                      'src/objective-c/GRPCClient/private/GRPCTransport+Private.h',
+                      'src/objective-c/GRPCClient/private/GRPCTransport+Private.m',
+                      'src/objective-c/GRPCClient/version.h'
+
+    ss.dependency "#{s.name}/Interface-Legacy", version
   end
 
-  s.subspec 'GID' do |ss|
-    ss.ios.deployment_target = '7.0'
+  s.subspec 'GRPCCore' do |ss|
+    ss.header_mappings_dir = 'src/objective-c/GRPCClient'
+
+    ss.public_header_files = 'src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h',
+                             'src/objective-c/GRPCClient/GRPCCall+Cronet.h',
+                             'src/objective-c/GRPCClient/GRPCCall+OAuth2.h',
+                             'src/objective-c/GRPCClient/GRPCCall+Tests.h',
+                             'src/objective-c/GRPCClient/GRPCCall+ChannelArg.h',
+                             'src/objective-c/GRPCClient/internal_testing/*.h'
+    ss.private_header_files = 'src/objective-c/GRPCClient/private/GRPCCore/*.h'
+    ss.source_files = 'src/objective-c/GRPCClient/internal_testing/*.{h,m}',
+                      'src/objective-c/GRPCClient/private/GRPCCore/*.{h,m}',
+                      'src/objective-c/GRPCClient/GRPCCall+ChannelArg.h',
+                      'src/objective-c/GRPCClient/GRPCCall+ChannelArg.m',
+                      'src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h',
+                      'src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.m',
+                      'src/objective-c/GRPCClient/GRPCCall+Cronet.h',
+                      'src/objective-c/GRPCClient/GRPCCall+Cronet.m',
+                      'src/objective-c/GRPCClient/GRPCCall+OAuth2.h',
+                      'src/objective-c/GRPCClient/GRPCCall+OAuth2.m',
+                      'src/objective-c/GRPCClient/GRPCCall+Tests.h',
+                      'src/objective-c/GRPCClient/GRPCCall+Tests.m',
+                      'src/objective-c/GRPCClient/GRPCCallLegacy.m'
+
+    # Certificates, to be able to establish TLS connections:
+    ss.resource_bundles = { 'gRPCCertificates' => ['etc/roots.pem'] }
+
+    ss.dependency "#{s.name}/Interface-Legacy", version
+    ss.dependency "#{s.name}/Interface", version
+    ss.dependency 'gRPC-Core', version
+    ss.dependency 'gRPC-RxLibrary', version
+  end
 
-    ss.header_mappings_dir = "#{src_dir}"
+  s.subspec 'GRPCCoreCronet' do |ss|
+    ss.header_mappings_dir = 'src/objective-c/GRPCClient'
 
-    ss.source_files = "#{src_dir}/GRPCCall+GID.{h,m}"
+    ss.source_files = 'src/objective-c/GRPCClient/GRPCCall+Cronet.h',
+                      'src/objective-c/GRPCClient/GRPCCall+Cronet.m',
+                      'src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/*.{h,m}'
+    ss.dependency "#{s.name}/GRPCCore", version
+    ss.dependency 'gRPC-Core/Cronet-Implementation', version
+    ss.dependency 'CronetFramework'
+  end
 
-    ss.dependency "#{s.name}/Main", version
-    ss.dependency 'Google/SignIn'
+  # CFStream is now default. Leaving this subspec only for compatibility purpose.
+  s.subspec 'CFStream' do |ss|
+    ss.dependency "#{s.name}/GRPCCore", version
   end
 end
diff --git a/src/compiler/objective_c_generator.cc b/src/compiler/objective_c_generator.cc
index 24845ecdb06622773a41cc92be2d9b60cd250f9b..ed262308aba9123974c1b20df0408136ecb65e3a 100644
--- a/src/compiler/objective_c_generator.cc
+++ b/src/compiler/objective_c_generator.cc
@@ -238,19 +238,21 @@ void PrintV2Implementation(Printer* printer, const MethodDescriptor* method,
 }
 
 void PrintMethodImplementations(Printer* printer,
-                                const MethodDescriptor* method) {
+                                const MethodDescriptor* method,
+                                const Parameters& generator_params) {
   map< ::grpc::string, ::grpc::string> vars = GetMethodVars(method);
 
   PrintProtoRpcDeclarationAsPragma(printer, method, vars);
 
-  // TODO(jcanizales): Print documentation from the method.
-  printer->Print("// Deprecated methods.\n");
-  PrintSimpleSignature(printer, method, vars);
-  PrintSimpleImplementation(printer, method, vars);
+  if (!generator_params.no_v1_compatibility) {
+    // TODO(jcanizales): Print documentation from the method.
+    PrintSimpleSignature(printer, method, vars);
+    PrintSimpleImplementation(printer, method, vars);
 
-  printer->Print("// Returns a not-yet-started RPC object.\n");
-  PrintAdvancedSignature(printer, method, vars);
-  PrintAdvancedImplementation(printer, method, vars);
+    printer->Print("// Returns a not-yet-started RPC object.\n");
+    PrintAdvancedSignature(printer, method, vars);
+    PrintAdvancedImplementation(printer, method, vars);
+  }
 
   PrintV2Signature(printer, method, vars);
   PrintV2Implementation(printer, method, vars);
@@ -276,9 +278,12 @@ void PrintMethodImplementations(Printer* printer,
   return output;
 }
 
-::grpc::string GetProtocol(const ServiceDescriptor* service) {
+::grpc::string GetProtocol(const ServiceDescriptor* service,
+                           const Parameters& generator_params) {
   ::grpc::string output;
 
+  if (generator_params.no_v1_compatibility) return output;
+
   // Scope the output stream so it closes and finalizes output to the string.
   grpc::protobuf::io::StringOutputStream output_stream(&output);
   Printer printer(&output_stream, '$');
@@ -321,7 +326,8 @@ void PrintMethodImplementations(Printer* printer,
   return output;
 }
 
-::grpc::string GetInterface(const ServiceDescriptor* service) {
+::grpc::string GetInterface(const ServiceDescriptor* service,
+                            const Parameters& generator_params) {
   ::grpc::string output;
 
   // Scope the output stream so it closes and finalizes output to the string.
@@ -338,7 +344,11 @@ void PrintMethodImplementations(Printer* printer,
                 " */\n");
   printer.Print(vars,
                 "@interface $service_class$ :"
-                " GRPCProtoService<$service_class$, $service_class$2>\n");
+                " GRPCProtoService<$service_class$2");
+  if (!generator_params.no_v1_compatibility) {
+    printer.Print(vars, ", $service_class$");
+  }
+  printer.Print(">\n");
   printer.Print(
       "- (instancetype)initWithHost:(NSString *)host "
       "callOptions:(GRPCCallOptions "
@@ -347,17 +357,20 @@ void PrintMethodImplementations(Printer* printer,
   printer.Print(
       "+ (instancetype)serviceWithHost:(NSString *)host "
       "callOptions:(GRPCCallOptions *_Nullable)callOptions;\n");
-  printer.Print(
-      "// The following methods belong to a set of old APIs that have been "
-      "deprecated.\n");
-  printer.Print("- (instancetype)initWithHost:(NSString *)host;\n");
-  printer.Print("+ (instancetype)serviceWithHost:(NSString *)host;\n");
+  if (!generator_params.no_v1_compatibility) {
+    printer.Print(
+        "// The following methods belong to a set of old APIs that have been "
+        "deprecated.\n");
+    printer.Print("- (instancetype)initWithHost:(NSString *)host;\n");
+    printer.Print("+ (instancetype)serviceWithHost:(NSString *)host;\n");
+  }
   printer.Print("@end\n");
 
   return output;
 }
 
-::grpc::string GetSource(const ServiceDescriptor* service) {
+::grpc::string GetSource(const ServiceDescriptor* service,
+                         const Parameters& generator_params) {
   ::grpc::string output;
   {
     // Scope the output stream so it closes and finalizes output to the string.
@@ -381,22 +394,28 @@ void PrintMethodImplementations(Printer* printer,
                   "                 packageName:@\"$package$\"\n"
                   "                 serviceName:@\"$service_name$\"\n"
                   "                 callOptions:callOptions];\n"
-                  "}\n\n"
-                  "- (instancetype)initWithHost:(NSString *)host {\n"
-                  "  return [super initWithHost:host\n"
-                  "                 packageName:@\"$package$\"\n"
-                  "                 serviceName:@\"$service_name$\"];\n"
-                  "}\n\n"
-                  "#pragma clang diagnostic pop\n\n");
-
+                  "}\n\n");
+    if (!generator_params.no_v1_compatibility) {
+      printer.Print(vars,
+                    "- (instancetype)initWithHost:(NSString *)host {\n"
+                    "  return [super initWithHost:host\n"
+                    "                 packageName:@\"$package$\"\n"
+                    "                 serviceName:@\"$service_name$\"];\n"
+                    "}\n\n");
+    }
+    printer.Print("#pragma clang diagnostic pop\n\n");
+
+    if (!generator_params.no_v1_compatibility) {
+      printer.Print(
+          "// Override superclass initializer to disallow different"
+          " package and service names.\n"
+          "- (instancetype)initWithHost:(NSString *)host\n"
+          "                 packageName:(NSString *)packageName\n"
+          "                 serviceName:(NSString *)serviceName {\n"
+          "  return [self initWithHost:host];\n"
+          "}\n\n");
+    }
     printer.Print(
-        "// Override superclass initializer to disallow different"
-        " package and service names.\n"
-        "- (instancetype)initWithHost:(NSString *)host\n"
-        "                 packageName:(NSString *)packageName\n"
-        "                 serviceName:(NSString *)serviceName {\n"
-        "  return [self initWithHost:host];\n"
-        "}\n\n"
         "- (instancetype)initWithHost:(NSString *)host\n"
         "                 packageName:(NSString *)packageName\n"
         "                 serviceName:(NSString *)serviceName\n"
@@ -404,11 +423,14 @@ void PrintMethodImplementations(Printer* printer,
         "  return [self initWithHost:host callOptions:callOptions];\n"
         "}\n\n");
 
+    printer.Print("#pragma mark - Class Methods\n\n");
+    if (!generator_params.no_v1_compatibility) {
+      printer.Print(
+          "+ (instancetype)serviceWithHost:(NSString *)host {\n"
+          "  return [[self alloc] initWithHost:host];\n"
+          "}\n\n");
+    }
     printer.Print(
-        "#pragma mark - Class Methods\n\n"
-        "+ (instancetype)serviceWithHost:(NSString *)host {\n"
-        "  return [[self alloc] initWithHost:host];\n"
-        "}\n\n"
         "+ (instancetype)serviceWithHost:(NSString *)host "
         "callOptions:(GRPCCallOptions *_Nullable)callOptions {\n"
         "  return [[self alloc] initWithHost:host callOptions:callOptions];\n"
@@ -417,7 +439,8 @@ void PrintMethodImplementations(Printer* printer,
     printer.Print("#pragma mark - Method Implementations\n\n");
 
     for (int i = 0; i < service->method_count(); i++) {
-      PrintMethodImplementations(&printer, service->method(i));
+      PrintMethodImplementations(&printer, service->method(i),
+                                 generator_params);
     }
 
     printer.Print("@end\n");
diff --git a/src/compiler/objective_c_generator.h b/src/compiler/objective_c_generator.h
index c171e5bf772daa67752f093e2b890adc1b2b0952..518962fceee26ab928ed1dc6385ab8b2435662af 100644
--- a/src/compiler/objective_c_generator.h
+++ b/src/compiler/objective_c_generator.h
@@ -23,6 +23,11 @@
 
 namespace grpc_objective_c_generator {
 
+struct Parameters {
+  // Do not generate V1 interface and implementation
+  bool no_v1_compatibility;
+};
+
 using ::grpc::protobuf::FileDescriptor;
 using ::grpc::protobuf::FileDescriptor;
 using ::grpc::protobuf::ServiceDescriptor;
@@ -34,7 +39,8 @@ string GetAllMessageClasses(const FileDescriptor* file);
 // Returns the content to be included defining the @protocol segment at the
 // insertion point of the generated implementation file. This interface is
 // legacy and for backwards compatibility.
-string GetProtocol(const ServiceDescriptor* service);
+string GetProtocol(const ServiceDescriptor* service,
+                   const Parameters& generator_params);
 
 // Returns the content to be included defining the @protocol segment at the
 // insertion point of the generated implementation file.
@@ -42,11 +48,13 @@ string GetV2Protocol(const ServiceDescriptor* service);
 
 // Returns the content to be included defining the @interface segment at the
 // insertion point of the generated implementation file.
-string GetInterface(const ServiceDescriptor* service);
+string GetInterface(const ServiceDescriptor* service,
+                    const Parameters& generator_params);
 
 // Returns the content to be included in the "global_scope" insertion point of
 // the generated implementation file.
-string GetSource(const ServiceDescriptor* service);
+string GetSource(const ServiceDescriptor* service,
+                 const Parameters& generator_params);
 
 }  // namespace grpc_objective_c_generator
 
diff --git a/src/compiler/objective_c_plugin.cc b/src/compiler/objective_c_plugin.cc
index f398033f6db4c8ea44d1d04214f84f91980faecc..a08064a08bd60b44be64978e23434b0a965531b9 100644
--- a/src/compiler/objective_c_plugin.cc
+++ b/src/compiler/objective_c_plugin.cc
@@ -111,6 +111,22 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
     ::grpc::string file_name =
         google::protobuf::compiler::objectivec::FilePath(file);
 
+    grpc_objective_c_generator::Parameters generator_params;
+    generator_params.no_v1_compatibility = false;
+
+    if (!parameter.empty()) {
+      std::vector<grpc::string> parameters_list =
+          grpc_generator::tokenize(parameter, ",");
+      for (auto parameter_string = parameters_list.begin();
+           parameter_string != parameters_list.end(); parameter_string++) {
+        std::vector<grpc::string> param =
+            grpc_generator::tokenize(*parameter_string, "=");
+        if (param[0] == "no_v1_compatibility") {
+          generator_params.no_v1_compatibility = true;
+        }
+      }
+    }
+
     {
       // Generate .pbrpc.h
 
@@ -121,18 +137,25 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
         imports = FrameworkImport(file_name + ".pbobjc.h", framework);
       }
 
-      ::grpc::string system_imports = SystemImport("ProtoRPC/ProtoService.h") +
-                                      SystemImport("ProtoRPC/ProtoRPC.h") +
-                                      SystemImport("RxLibrary/GRXWriteable.h") +
-                                      SystemImport("RxLibrary/GRXWriter.h");
+      ::grpc::string system_imports =
+          SystemImport("ProtoRPC/ProtoService.h") +
+          (generator_params.no_v1_compatibility
+               ? SystemImport("ProtoRPC/ProtoRPC.h")
+               : SystemImport("ProtoRPC/ProtoRPCLegacy.h"));
+      if (!generator_params.no_v1_compatibility) {
+        system_imports += SystemImport("RxLibrary/GRXWriteable.h") +
+                          SystemImport("RxLibrary/GRXWriter.h");
+      }
 
       ::grpc::string forward_declarations =
-          "@class GRPCProtoCall;\n"
           "@class GRPCUnaryProtoCall;\n"
           "@class GRPCStreamingProtoCall;\n"
           "@class GRPCCallOptions;\n"
-          "@protocol GRPCProtoResponseHandler;\n"
-          "\n";
+          "@protocol GRPCProtoResponseHandler;\n";
+      if (!generator_params.no_v1_compatibility) {
+        forward_declarations += "@class GRPCProtoCall;\n";
+      }
+      forward_declarations += "\n";
 
       ::grpc::string class_declarations =
           grpc_objective_c_generator::GetAllMessageClasses(file);
@@ -152,13 +175,15 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
       ::grpc::string protocols;
       for (int i = 0; i < file->service_count(); i++) {
         const grpc::protobuf::ServiceDescriptor* service = file->service(i);
-        protocols += grpc_objective_c_generator::GetProtocol(service);
+        protocols +=
+            grpc_objective_c_generator::GetProtocol(service, generator_params);
       }
 
       ::grpc::string interfaces;
       for (int i = 0; i < file->service_count(); i++) {
         const grpc::protobuf::ServiceDescriptor* service = file->service(i);
-        interfaces += grpc_objective_c_generator::GetInterface(service);
+        interfaces +=
+            grpc_objective_c_generator::GetInterface(service, generator_params);
       }
 
       Write(context, file_name + ".pbrpc.h",
@@ -178,14 +203,16 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
       ::grpc::string imports;
       if (framework.empty()) {
         imports = LocalImport(file_name + ".pbrpc.h") +
-                  LocalImport(file_name + ".pbobjc.h") +
-                  SystemImport("ProtoRPC/ProtoRPC.h") +
-                  SystemImport("RxLibrary/GRXWriter+Immediate.h");
+                  LocalImport(file_name + ".pbobjc.h");
       } else {
         imports = FrameworkImport(file_name + ".pbrpc.h", framework) +
-                  FrameworkImport(file_name + ".pbobjc.h", framework) +
-                  SystemImport("ProtoRPC/ProtoRPC.h") +
-                  SystemImport("RxLibrary/GRXWriter+Immediate.h");
+                  FrameworkImport(file_name + ".pbobjc.h", framework);
+      }
+      imports += (generator_params.no_v1_compatibility
+                      ? SystemImport("ProtoRPC/ProtoRPC.h")
+                      : SystemImport("ProtoRPC/ProtoRPCLegacy.h"));
+      if (!generator_params.no_v1_compatibility) {
+        imports += SystemImport("RxLibrary/GRXWriter+Immediate.h");
       }
 
       ::grpc::string class_imports;
@@ -196,7 +223,8 @@ class ObjectiveCGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
       ::grpc::string definitions;
       for (int i = 0; i < file->service_count(); i++) {
         const grpc::protobuf::ServiceDescriptor* service = file->service(i);
-        definitions += grpc_objective_c_generator::GetSource(service);
+        definitions +=
+            grpc_objective_c_generator::GetSource(service, generator_params);
       }
 
       Write(context, file_name + ".pbrpc.m",
diff --git a/src/objective-c/BUILD b/src/objective-c/BUILD
index 3a71086f0f60f9efa2189733f77aeb7d386a1fe6..5f53486d17eeb1040ceaec6a0844ed7975d6ebb8 100644
--- a/src/objective-c/BUILD
+++ b/src/objective-c/BUILD
@@ -18,24 +18,30 @@ licenses(["notice"])  # Apache v2
 
 package(default_visibility = ["//visibility:public"])
 
-load("//bazel:grpc_build_system.bzl", "grpc_objc_library", "grpc_objc_use_cronet_config")
+load("//bazel:grpc_build_system.bzl", "grpc_objc_library", "grpc_generate_objc_one_off_targets")
 
 exports_files(["LICENSE"])
 
-grpc_objc_use_cronet_config()
+grpc_generate_objc_one_off_targets()
+
+grpc_objc_library(
+    name = "rx_library_headers",
+    hdrs = glob([
+        "RxLibrary/*.h",
+    ]),
+    includes = ["."],
+)
 
 grpc_objc_library(
     name = "rx_library",
     srcs = glob([
         "RxLibrary/*.m",
-        "RxLibrary/transformations/*.m",
-    ]),
-    hdrs = glob([
-        "RxLibrary/*.h",
-        "RxLibrary/transformations/*.h",
     ]),
     includes = ["."],
-    deps = [":rx_library_private"],
+    deps = [
+        ":rx_library_headers",
+        ":rx_library_private",
+    ],
 )
 
 grpc_objc_library(
@@ -50,84 +56,196 @@ grpc_objc_library(
 )
 
 grpc_objc_library(
-    name = "grpc_objc_client",
-    srcs = glob(
-        [
-            "GRPCClient/*.m",
-            "GRPCClient/private/*.m",
-        ],
-        exclude = ["GRPCClient/GRPCCall+GID.m"],
-    ),
-    hdrs = glob(
-        [
-            "GRPCClient/*.h",
-            "GRPCClient/internal/*.h",
-        ],
-        exclude = ["GRPCClient/GRPCCall+GID.h"],
-    ),
-    data = ["//:gRPCCertificates"],
+    name = "grpc_objc_interface_legacy",
+    hdrs = [
+        "GRPCClient/GRPCCall+ChannelArg.h",
+        "GRPCClient/GRPCCall+ChannelCredentials.h",
+        "GRPCClient/GRPCCall+Cronet.h",
+        "GRPCClient/GRPCCall+OAuth2.h",
+        "GRPCClient/GRPCCall+Tests.h",
+        "GRPCClient/GRPCCallLegacy.h",
+        "GRPCClient/GRPCTypes.h",
+    ],
+    deps = [
+        "rx_library_headers",
+    ],
+)
+
+grpc_objc_library(
+    name = "grpc_objc_interface",
+    hdrs = [
+        "GRPCClient/GRPCCall.h",
+        "GRPCClient/GRPCCall+Interceptor.h",
+        "GRPCClient/GRPCCallOptions.h",
+        "GRPCClient/GRPCInterceptor.h",
+        "GRPCClient/GRPCTransport.h",
+        "GRPCClient/GRPCDispatchable.h",
+        "GRPCClient/internal/GRPCCallOptions+Internal.h",
+        "GRPCClient/version.h",
+    ],
+    srcs = [
+        "GRPCClient/GRPCCall.m",
+        "GRPCClient/GRPCCall+Interceptor.m",
+        "GRPCClient/GRPCCallOptions.m",
+        "GRPCClient/GRPCInterceptor.m",
+        "GRPCClient/GRPCTransport.m",
+        "GRPCClient/private/GRPCTransport+Private.m",
+    ],
     includes = ["."],
-    textual_hdrs = glob([
-        "GRPCClient/private/*.h",
-    ]),
+    textual_hdrs = [
+        "GRPCClient/private/GRPCTransport+Private.h",
+    ],
     deps = [
+        ":grpc_objc_interface_legacy",
+    ],
+)
+
+grpc_objc_library(
+    name = "grpc_objc_client_core",
+    hdrs = [
+        "GRPCClient/GRPCCall+ChannelCredentials.h",
+        "GRPCClient/GRPCCall+Cronet.h",
+        "GRPCClient/GRPCCall+OAuth2.h",
+        "GRPCClient/GRPCCall+Tests.h",
+        "GRPCClient/GRPCCall+ChannelArg.h",
+    ],
+    textual_hdrs = glob(["GRPCClient/private/GRPCCore/*.h"]),
+    srcs = [
+        "GRPCClient/GRPCCall+ChannelArg.m",
+        "GRPCClient/GRPCCall+ChannelCredentials.m",
+        "GRPCClient/GRPCCall+Cronet.m",
+        "GRPCClient/GRPCCall+OAuth2.m",
+        "GRPCClient/GRPCCall+Tests.m",
+        "GRPCClient/GRPCCallLegacy.m",
+    ] + glob(["GRPCClient/private/GRPCCore/*.m"]),
+    data = [":gRPCCertificates"],
+    includes = ["."],
+    deps = [
+        ":grpc_objc_interface",
+        ":grpc_objc_interface_legacy",
         ":rx_library",
         "//:grpc_objc",
     ],
 )
 
+alias(
+    name = "grpc_objc_client",
+    actual = "grpc_objc_client_core",
+)
+
+grpc_objc_library(
+    name = "proto_objc_rpc_legacy_header",
+    hdrs = [
+        "ProtoRPC/ProtoRPCLegacy.h",
+    ],
+    includes = ["."],
+)
+
 grpc_objc_library(
-    name = "proto_objc_rpc",
-    srcs = glob(
-        ["ProtoRPC/*.m"],
-    ),
-    hdrs = glob(
-        ["ProtoRPC/*.h"],
-    ),
-    # Different from Cocoapods, do not import as if @com_google_protobuf//:protobuf_objc is a framework,
-    # use the real paths of @com_google_protobuf//:protobuf_objc instead
+    name = "proto_objc_rpc_v2",
+    srcs = [
+        "ProtoRPC/ProtoMethod.m",
+        "ProtoRPC/ProtoRPC.m",
+        "ProtoRPC/ProtoService.m",
+    ],
+    hdrs = [
+        "ProtoRPC/ProtoMethod.h",
+        "ProtoRPC/ProtoRPC.h",
+        "ProtoRPC/ProtoService.h",
+    ],
+    includes = ["."],
     defines = ["GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=0"],
     deps = [
-        ":grpc_objc_client",
+        ":grpc_objc_interface",
+        ":proto_objc_rpc_legacy_header",
+        "@com_google_protobuf//:protobuf_objc",
+    ],
+)
+
+grpc_objc_library(
+    name = "proto_objc_rpc",
+    srcs = [
+        "ProtoRPC/ProtoRPCLegacy.m",
+        "ProtoRPC/ProtoServiceLegacy.m",
+    ],
+    hdrs = [
+        "ProtoRPC/ProtoMethod.h",
+        "ProtoRPC/ProtoRPCLegacy.h",
+        "ProtoRPC/ProtoService.h",
+    ],
+    deps = [
         ":rx_library",
+        ":proto_objc_rpc_v2",
+        ":proto_objc_rpc_legacy_header",
+        ":grpc_objc_client_core",
         "@com_google_protobuf//:protobuf_objc",
     ],
 )
 
+load("@build_bazel_rules_apple//apple:resources.bzl", "apple_resource_bundle")
+
+apple_resource_bundle(
+    # The choice of name is signicant here, since it determines the bundle name.
+    name = "gRPCCertificates",
+    resources = ["//:etc/roots.pem"],
+)
+
+# Internal target combining grpc_objc_client_core and proto_objc_rpc for testing
 grpc_objc_library(
-    name = "grpc_objc_client_internal_testing",
-    srcs = glob(
-        [
-            "GRPCClient/*.m",
-            "GRPCClient/private/*.m",
-            "GRPCClient/internal_testing/*.m",
-            "ProtoRPC/*.m",
-        ],
-        exclude = ["GRPCClient/GRPCCall+GID.m"],
-    ),
-    hdrs = glob(
-        [
-            "GRPCClient/*.h",
-            "GRPCClient/internal/*.h",
-            "GRPCClient/internal_testing/*.h",
-            "ProtoRPC/*.h",
-        ],
-        exclude = ["GRPCClient/GRPCCall+GID.h"],
-    ),
+    name = "grpc_objc_client_core_internal_testing",
+    hdrs = [
+        "GRPCClient/GRPCCall+ChannelCredentials.h",
+        "GRPCClient/GRPCCall+Cronet.h",
+        "GRPCClient/GRPCCall+OAuth2.h",
+        "GRPCClient/GRPCCall+Tests.h",
+        "GRPCClient/GRPCCall+ChannelArg.h",
+        "GRPCClient/internal_testing/GRPCCall+InternalTests.h",
+    ],
+    textual_hdrs = glob(["GRPCClient/private/GRPCCore/*.h"]),
+    srcs = [
+        "GRPCClient/GRPCCall+ChannelArg.m",
+        "GRPCClient/GRPCCall+ChannelCredentials.m",
+        "GRPCClient/GRPCCall+Cronet.m",
+        "GRPCClient/GRPCCall+OAuth2.m",
+        "GRPCClient/GRPCCall+Tests.m",
+        "GRPCClient/GRPCCallLegacy.m",
+        "GRPCClient/internal_testing/GRPCCall+InternalTests.m",
+    ] + glob(["GRPCClient/private/GRPCCore/*.m"]),
+    data = [":gRPCCertificates"],
     includes = ["."],
-    data = ["//:gRPCCertificates"],
     defines = [
         "GRPC_TEST_OBJC=1",
-        "GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS=0",
     ],
-    textual_hdrs = glob(
-        [
-            "GRPCClient/private/*.h",
-        ],
-    ),
     deps = [
+        ":grpc_objc_interface",
+        ":grpc_objc_interface_legacy",
         ":rx_library",
         "//:grpc_objc",
+    ],
+)
+
+grpc_objc_library(
+    name = "proto_objc_rpc_internal_testing",
+    srcs = [
+        "ProtoRPC/ProtoRPCLegacy.m",
+        "ProtoRPC/ProtoServiceLegacy.m",
+    ],
+    hdrs = [
+        "ProtoRPC/ProtoMethod.h",
+        "ProtoRPC/ProtoRPC.h",
+        "ProtoRPC/ProtoRPCLegacy.h",
+        "ProtoRPC/ProtoService.h",
+    ],
+    deps = [
+        ":rx_library",
+        ":proto_objc_rpc_v2",
+        ":proto_objc_rpc_legacy_header",
+        ":grpc_objc_client_core_internal_testing",
         "@com_google_protobuf//:protobuf_objc",
     ],
 )
+
+alias(
+    name = "grpc_objc_client_internal_testing",
+    actual = "proto_objc_rpc_internal_testing",
+)
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
index 2ddd53a5c6695257d9504be01fd880ebedf51482..ff5a153a0f65ecd3dd4eb20f4a0be2bfce620da8 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.h
@@ -15,7 +15,7 @@
  * limitations under the License.
  *
  */
-#import "GRPCCall.h"
+#import "GRPCCallLegacy.h"
 
 #include <AvailabilityMacros.h>
 
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
index ae60d6208e11c42492b00c599c6c6b323fa7059a..aae1b740c71b6c3d6fcb510e36f587e88b9a3863 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelArg.m
@@ -18,8 +18,8 @@
 
 #import "GRPCCall+ChannelArg.h"
 
-#import "private/GRPCChannelPool.h"
-#import "private/GRPCHost.h"
+#import "private/GRPCCore/GRPCChannelPool.h"
+#import "private/GRPCCore/GRPCHost.h"
 
 #import <grpc/impl/codegen/compression_types.h>
 
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h b/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h
index 7d6f79b7655ed4bf1296fd6cf37cc883eacf7254..c3d194bff9451f870e2f1ee94e353c9a88ccdc00 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h
@@ -16,7 +16,7 @@
  *
  */
 
-#import "GRPCCall.h"
+#import "GRPCCallLegacy.h"
 
 // Deprecated interface. Please use GRPCCallOptions instead.
 @interface GRPCCall (ChannelCredentials)
diff --git a/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.m b/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.m
index 2689ec2effb22e5184a2bb221dcc2034ba0ca7d1..aa97ca82bf8de2e1cc497084071cb0c26dc49746 100644
--- a/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.m
+++ b/src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.m
@@ -18,7 +18,7 @@
 
 #import "GRPCCall+ChannelCredentials.h"
 
-#import "private/GRPCHost.h"
+#import "private/GRPCCore/GRPCHost.h"
 
 @implementation GRPCCall (ChannelCredentials)
 
diff --git a/src/objective-c/GRPCClient/GRPCCall+Cronet.h b/src/objective-c/GRPCClient/GRPCCall+Cronet.h
index 3059c6f18600e5bae74752283de1008e4d01d921..d107ada367258759c0c5f05823dd667aca75a3a4 100644
--- a/src/objective-c/GRPCClient/GRPCCall+Cronet.h
+++ b/src/objective-c/GRPCClient/GRPCCall+Cronet.h
@@ -15,12 +15,16 @@
  * limitations under the License.
  *
  */
-#ifdef GRPC_COMPILE_WITH_CRONET
-#import <Cronet/Cronet.h>
 
-#import "GRPCCall.h"
+#import "GRPCCallLegacy.h"
+#import "GRPCTypes.h"
 
-// Deprecated interface. Please use GRPCCallOptions instead.
+typedef struct stream_engine stream_engine;
+
+// Transport id for Cronet transport
+extern const GRPCTransportId gGRPCCoreCronetId;
+
+// Deprecated class. Please use the gGRPCCoreCronetId with GRPCCallOptions.transport instead.
 @interface GRPCCall (Cronet)
 
 + (void)useCronetWithEngine:(stream_engine*)engine;
@@ -28,4 +32,3 @@
 + (BOOL)isUsingCronet;
 
 @end
-#endif
diff --git a/src/objective-c/GRPCClient/GRPCCall+Cronet.m b/src/objective-c/GRPCClient/GRPCCall+Cronet.m
index ba8d2c23db8d86a9c3700996a6d00d97603edcd0..a732208e1f645f1738e4576030345374f00511cd 100644
--- a/src/objective-c/GRPCClient/GRPCCall+Cronet.m
+++ b/src/objective-c/GRPCClient/GRPCCall+Cronet.m
@@ -18,7 +18,8 @@
 
 #import "GRPCCall+Cronet.h"
 
-#ifdef GRPC_COMPILE_WITH_CRONET
+const GRPCTransportId gGRPCCoreCronetId = "io.grpc.transport.core.cronet";
+
 static BOOL useCronet = NO;
 static stream_engine *globalCronetEngine;
 
@@ -38,4 +39,3 @@ static stream_engine *globalCronetEngine;
 }
 
 @end
-#endif
diff --git a/src/objective-c/GRPCClient/GRPCCall+GID.h b/src/objective-c/GRPCClient/GRPCCall+GID.h
index 8293e92ebe9ec848a69c4b1b4f2e5af24f10e20f..80e34ea98da86cf7df0853a4b31844a509f3ceb7 100644
--- a/src/objective-c/GRPCClient/GRPCCall+GID.h
+++ b/src/objective-c/GRPCClient/GRPCCall+GID.h
@@ -17,7 +17,7 @@
  */
 
 #import "GRPCCall+OAuth2.h"
-#import "GRPCCall.h"
+#import "GRPCCallLegacy.h"
 
 #import <Google/SignIn.h>
 
diff --git a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
index 60cdc50bfda10ba28ed1b18066e83db41b729991..cf60c794f270fe8e1fae0cbcd27853bcd72c6126 100644
--- a/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
+++ b/src/objective-c/GRPCClient/GRPCCall+OAuth2.h
@@ -16,9 +16,9 @@
  *
  */
 
-#import "GRPCCall.h"
+#import "GRPCCallLegacy.h"
 
-#import "GRPCCallOptions.h"
+@protocol GRPCAuthorizationProtocol;
 
 // Deprecated interface. Please use GRPCCallOptions instead.
 @interface GRPCCall (OAuth2)
diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.h b/src/objective-c/GRPCClient/GRPCCall+Tests.h
index edaa5ed582c6a897fbcfe20a91c0ab847ef95fc0..af81eec6b8279e7612d392bd691d4f5f0423e48c 100644
--- a/src/objective-c/GRPCClient/GRPCCall+Tests.h
+++ b/src/objective-c/GRPCClient/GRPCCall+Tests.h
@@ -16,7 +16,7 @@
  *
  */
 
-#import "GRPCCall.h"
+#import "GRPCCallLegacy.h"
 
 // Deprecated interface. Please use GRPCCallOptions instead.
 @interface GRPCCall (Tests)
diff --git a/src/objective-c/GRPCClient/GRPCCall+Tests.m b/src/objective-c/GRPCClient/GRPCCall+Tests.m
index 20f5cba4178073e770397653748dd5bcc2151a36..3a1dff388681f6ff6cae4820694f26a49d11d24d 100644
--- a/src/objective-c/GRPCClient/GRPCCall+Tests.m
+++ b/src/objective-c/GRPCClient/GRPCCall+Tests.m
@@ -18,7 +18,7 @@
 
 #import "GRPCCall+Tests.h"
 
-#import "private/GRPCHost.h"
+#import "private/GRPCCore/GRPCHost.h"
 
 #import "GRPCCallOptions.h"
 
diff --git a/src/objective-c/GRPCClient/GRPCCall.h b/src/objective-c/GRPCClient/GRPCCall.h
index d02ec60172768f4e5cea7a8ce17c7df3aac47289..1c7a10048cf0d863d8629f5083e8354a701cb281 100644
--- a/src/objective-c/GRPCClient/GRPCCall.h
+++ b/src/objective-c/GRPCClient/GRPCCall.h
@@ -33,134 +33,19 @@
  */
 
 #import <Foundation/Foundation.h>
-#import <RxLibrary/GRXWriter.h>
 
-#include <AvailabilityMacros.h>
+#import "GRPCCallOptions.h"
+#import "GRPCDispatchable.h"
+#import "GRPCTypes.h"
 
-#include "GRPCCallOptions.h"
+// The legacy header is included for backwards compatibility. Some V1 API users are still using
+// GRPCCall by importing GRPCCall.h header so we need this import.
+#import "GRPCCallLegacy.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
-#pragma mark gRPC errors
-
-/** Domain of NSError objects produced by gRPC. */
-extern NSString *const kGRPCErrorDomain;
-
-/**
- * gRPC error codes.
- * Note that a few of these are never produced by the gRPC libraries, but are of general utility for
- * server applications to produce.
- */
-typedef NS_ENUM(NSUInteger, GRPCErrorCode) {
-  /** The operation was cancelled (typically by the caller). */
-  GRPCErrorCodeCancelled = 1,
-
-  /**
-   * Unknown error. Errors raised by APIs that do not return enough error information may be
-   * converted to this error.
-   */
-  GRPCErrorCodeUnknown = 2,
-
-  /**
-   * The client specified an invalid argument. Note that this differs from FAILED_PRECONDITION.
-   * INVALID_ARGUMENT indicates arguments that are problematic regardless of the state of the
-   * server (e.g., a malformed file name).
-   */
-  GRPCErrorCodeInvalidArgument = 3,
-
-  /**
-   * Deadline expired before operation could complete. For operations that change the state of the
-   * server, this error may be returned even if the operation has completed successfully. For
-   * example, a successful response from the server could have been delayed long enough for the
-   * deadline to expire.
-   */
-  GRPCErrorCodeDeadlineExceeded = 4,
-
-  /** Some requested entity (e.g., file or directory) was not found. */
-  GRPCErrorCodeNotFound = 5,
-
-  /** Some entity that we attempted to create (e.g., file or directory) already exists. */
-  GRPCErrorCodeAlreadyExists = 6,
-
-  /**
-   * The caller does not have permission to execute the specified operation. PERMISSION_DENIED isn't
-   * used for rejections caused by exhausting some resource (RESOURCE_EXHAUSTED is used instead for
-   * those errors). PERMISSION_DENIED doesn't indicate a failure to identify the caller
-   * (UNAUTHENTICATED is used instead for those errors).
-   */
-  GRPCErrorCodePermissionDenied = 7,
-
-  /**
-   * The request does not have valid authentication credentials for the operation (e.g. the caller's
-   * identity can't be verified).
-   */
-  GRPCErrorCodeUnauthenticated = 16,
-
-  /** Some resource has been exhausted, perhaps a per-user quota. */
-  GRPCErrorCodeResourceExhausted = 8,
-
-  /**
-   * The RPC was rejected because the server is not in a state required for the procedure's
-   * execution. For example, a directory to be deleted may be non-empty, etc.
-   * The client should not retry until the server state has been explicitly fixed (e.g. by
-   * performing another RPC). The details depend on the service being called, and should be found in
-   * the NSError's userInfo.
-   */
-  GRPCErrorCodeFailedPrecondition = 9,
-
-  /**
-   * The RPC was aborted, typically due to a concurrency issue like sequencer check failures,
-   * transaction aborts, etc. The client should retry at a higher-level (e.g., restarting a read-
-   * modify-write sequence).
-   */
-  GRPCErrorCodeAborted = 10,
-
-  /**
-   * The RPC was attempted past the valid range. E.g., enumerating past the end of a list.
-   * Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed if the system state
-   * changes. For example, an RPC to get elements of a list will generate INVALID_ARGUMENT if asked
-   * to return the element at a negative index, but it will generate OUT_OF_RANGE if asked to return
-   * the element at an index past the current size of the list.
-   */
-  GRPCErrorCodeOutOfRange = 11,
-
-  /** The procedure is not implemented or not supported/enabled in this server. */
-  GRPCErrorCodeUnimplemented = 12,
-
-  /**
-   * Internal error. Means some invariant expected by the server application or the gRPC library has
-   * been broken.
-   */
-  GRPCErrorCodeInternal = 13,
-
-  /**
-   * The server is currently unavailable. This is most likely a transient condition and may be
-   * corrected by retrying with a backoff. Note that it is not always safe to retry
-   * non-idempotent operations.
-   */
-  GRPCErrorCodeUnavailable = 14,
-
-  /** Unrecoverable data loss or corruption. */
-  GRPCErrorCodeDataLoss = 15,
-};
-
-/**
- * Keys used in |NSError|'s |userInfo| dictionary to store the response headers and trailers sent by
- * the server.
- */
-extern NSString *const kGRPCHeadersKey;
-extern NSString *const kGRPCTrailersKey;
-
 /** An object can implement this protocol to receive responses from server from a call. */
-@protocol GRPCResponseHandler<NSObject>
-
-@required
-
-/**
- * All the responses must be issued to a user-provided dispatch queue. This property specifies the
- * dispatch queue to be used for issuing the notifications.
- */
-@property(atomic, readonly) dispatch_queue_t dispatchQueue;
+@protocol GRPCResponseHandler<NSObject, GRPCDispatchable>
 
 @optional
 
@@ -302,114 +187,3 @@ extern NSString *const kGRPCTrailersKey;
 @end
 
 NS_ASSUME_NONNULL_END
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wnullability-completeness"
-
-/**
- * This interface is deprecated. Please use \a GRPCcall2.
- *
- * Represents a single gRPC remote call.
- */
-@interface GRPCCall : GRXWriter
-
-- (instancetype)init NS_UNAVAILABLE;
-
-/**
- * The container of the request headers of an RPC conforms to this protocol, which is a subset of
- * NSMutableDictionary's interface. It will become a NSMutableDictionary later on.
- * The keys of this container are the header names, which per the HTTP standard are case-
- * insensitive. They are stored in lowercase (which is how HTTP/2 mandates them on the wire), and
- * can only consist of ASCII characters.
- * A header value is a NSString object (with only ASCII characters), unless the header name has the
- * suffix "-bin", in which case the value has to be a NSData object.
- */
-/**
- * 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.
- *
- * The passed dictionary has to use NSString keys, corresponding to the header names. The value
- * associated to each can be a NSString object or a NSData object. E.g.:
- *
- * call.requestHeaders = @{@"authorization": @"Bearer ..."};
- *
- * call.requestHeaders[@"my-header-bin"] = someData;
- *
- * After the call is started, trying to modify this property is an error.
- *
- * The property is initialized to an empty NSMutableDictionary.
- */
-@property(atomic, readonly) NSMutableDictionary *requestHeaders;
-
-/**
- * This dictionary is populated with the HTTP headers received from the server. This happens before
- * any response message is received from the server. It has the same structure as the request
- * headers dictionary: Keys are NSString header names; names ending with the suffix "-bin" have a
- * NSData value; the others have a NSString value.
- *
- * The value of this property is nil until all response headers are received, and will change before
- * any of -writeValue: or -writesFinishedWithError: are sent to the writeable.
- */
-@property(atomic, readonly) NSDictionary *responseHeaders;
-
-/**
- * Same as responseHeaders, but populated with the HTTP trailers received from the server before the
- * call finishes.
- *
- * The value of this property is nil until all response trailers are received, and will change
- * before -writesFinishedWithError: is sent to the writeable.
- */
-@property(atomic, readonly) NSDictionary *responseTrailers;
-
-/**
- * The request writer has to write NSData objects into the provided Writeable. The server will
- * receive each of those separately and in order as distinct messages.
- * A gRPC call might not complete until the request writer finishes. On the other hand, the request
- * finishing doesn't necessarily make the call to finish, as the server might continue sending
- * messages to the response side of the call indefinitely (depending on the semantics of the
- * specific remote method called).
- * To finish a call right away, invoke cancel.
- * host parameter should not contain the scheme (http:// or https://), only the name or IP addr
- * and the port number, for example @"localhost:5050".
- */
-- (instancetype)initWithHost:(NSString *)host
-                        path:(NSString *)path
-              requestsWriter:(GRXWriter *)requestWriter;
-
-/**
- * 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 of code CANCELED.
- */
-- (void)cancel;
-
-/**
- * The following methods are deprecated.
- */
-+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path;
-@property(atomic, copy, readwrite) NSString *serverName;
-@property NSTimeInterval timeout;
-- (void)setResponseDispatchQueue:(dispatch_queue_t)queue;
-
-@end
-
-#pragma mark Backwards compatibiity
-
-/** This protocol is kept for backwards compatibility with existing code. */
-DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.")
-@protocol GRPCRequestHeaders<NSObject>
-@property(nonatomic, readonly) NSUInteger count;
-
-- (id)objectForKeyedSubscript:(id)key;
-- (void)setObject:(id)obj forKeyedSubscript:(id)key;
-
-- (void)removeAllObjects;
-- (void)removeObjectForKey:(id)key;
-@end
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated"
-/** This is only needed for backwards-compatibility. */
-@interface NSMutableDictionary (GRPCRequestHeaders)<GRPCRequestHeaders>
-@end
-#pragma clang diagnostic pop
-#pragma clang diagnostic pop
diff --git a/src/objective-c/GRPCClient/GRPCCall.m b/src/objective-c/GRPCClient/GRPCCall.m
index cd293cc8e59c0bacb6b66336ed132d56a15d4f0f..73ee530ef2c4373a535c3d6adc894119424ea834 100644
--- a/src/objective-c/GRPCClient/GRPCCall.m
+++ b/src/objective-c/GRPCClient/GRPCCall.m
@@ -17,56 +17,86 @@
  */
 
 #import "GRPCCall.h"
+
 #import "GRPCCall+Interceptor.h"
-#import "GRPCCall+OAuth2.h"
 #import "GRPCCallOptions.h"
 #import "GRPCInterceptor.h"
 
-#import <RxLibrary/GRXBufferedPipe.h>
-#import <RxLibrary/GRXConcurrentWriteable.h>
-#import <RxLibrary/GRXImmediateSingleWriter.h>
-#import <RxLibrary/GRXWriter+Immediate.h>
-#include <grpc/grpc.h>
-#include <grpc/support/time.h>
-
-#import "private/GRPCCall+V2API.h"
-#import "private/GRPCCallInternal.h"
-#import "private/GRPCChannelPool.h"
-#import "private/GRPCCompletionQueue.h"
-#import "private/GRPCHost.h"
-#import "private/GRPCRequestHeaders.h"
-#import "private/GRPCWrappedCall.h"
-#import "private/NSData+GRPC.h"
-#import "private/NSDictionary+GRPC.h"
-#import "private/NSError+GRPC.h"
-
-// At most 6 ops can be in an op batch for a client: SEND_INITIAL_METADATA,
-// SEND_MESSAGE, SEND_CLOSE_FROM_CLIENT, RECV_INITIAL_METADATA, RECV_MESSAGE,
-// and RECV_STATUS_ON_CLIENT.
-NSInteger kMaxClientBatch = 6;
+#import "private/GRPCTransport+Private.h"
 
 NSString *const kGRPCHeadersKey = @"io.grpc.HeadersKey";
 NSString *const kGRPCTrailersKey = @"io.grpc.TrailersKey";
-static NSMutableDictionary *callFlags;
 
-static NSString *const kAuthorizationHeader = @"authorization";
-static NSString *const kBearerPrefix = @"Bearer ";
+NSString *const kGRPCErrorDomain = @"io.grpc";
+
+/**
+ * The response dispatcher creates its own serial dispatch queue and target the queue to the
+ * dispatch queue of a user provided response handler. It removes the requirement of having to use
+ * serial dispatch queue in the user provided response handler.
+ */
+@interface GRPCResponseDispatcher : NSObject<GRPCResponseHandler>
+
+- (nullable instancetype)initWithResponseHandler:(id<GRPCResponseHandler>)responseHandler;
+
+@end
 
-const char *kCFStreamVarName = "grpc_cfstream";
+@implementation GRPCResponseDispatcher {
+  id<GRPCResponseHandler> _responseHandler;
+  dispatch_queue_t _dispatchQueue;
+}
 
-@interface GRPCCall ()<GRXWriteable>
-// Make them read-write.
-@property(atomic, strong) NSDictionary *responseHeaders;
-@property(atomic, strong) NSDictionary *responseTrailers;
+- (instancetype)initWithResponseHandler:(id<GRPCResponseHandler>)responseHandler {
+  if ((self = [super init])) {
+    _responseHandler = responseHandler;
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
+    if (@available(iOS 8.0, macOS 10.10, *)) {
+      _dispatchQueue = dispatch_queue_create(
+          NULL,
+          dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
+    } else {
+#else
+    {
+#endif
+      _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+    }
+    dispatch_set_target_queue(_dispatchQueue, _responseHandler.dispatchQueue);
+  }
 
-- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+  return self;
+}
+
+- (dispatch_queue_t)dispatchQueue {
+  return _dispatchQueue;
+}
+
+- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata {
+  if ([_responseHandler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
+    [_responseHandler didReceiveInitialMetadata:initialMetadata];
+  }
+}
+
+- (void)didReceiveData:(id)data {
+  // For backwards compatibility with didReceiveRawMessage, if the user provided a response handler
+  // that handles didReceiveRawMesssage, we issue to that method instead
+  if ([_responseHandler respondsToSelector:@selector(didReceiveRawMessage:)]) {
+    [_responseHandler didReceiveRawMessage:data];
+  } else if ([_responseHandler respondsToSelector:@selector(didReceiveData:)]) {
+    [_responseHandler didReceiveData:data];
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
+                               error:(nullable NSError *)error {
+  if ([_responseHandler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+    [_responseHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
+  }
+}
 
-- (instancetype)initWithHost:(NSString *)host
-                        path:(NSString *)path
-                  callSafety:(GRPCCallSafety)safety
-              requestsWriter:(GRXWriter *)requestsWriter
-                 callOptions:(GRPCCallOptions *)callOptions
-                   writeDone:(void (^)(void))writeDone;
+- (void)didWriteData {
+  if ([_responseHandler respondsToSelector:@selector(didWriteData)]) {
+    [_responseHandler didWriteData];
+  }
+}
 
 @end
 
@@ -140,54 +170,39 @@ const char *kCFStreamVarName = "grpc_cfstream";
     }
     _responseHandler = responseHandler;
 
-    // Initialize the interceptor chain
-
-    // First initialize the internal call
-    GRPCCall2Internal *internalCall = [[GRPCCall2Internal alloc] init];
-    id<GRPCInterceptorInterface> nextInterceptor = internalCall;
-    GRPCInterceptorManager *nextManager = nil;
-
-    // Then initialize the global interceptor, if applicable
+    GRPCResponseDispatcher *dispatcher =
+        [[GRPCResponseDispatcher alloc] initWithResponseHandler:_responseHandler];
+    NSMutableArray<id<GRPCInterceptorFactory>> *interceptorFactories;
+    if (_actualCallOptions.interceptorFactories != nil) {
+      interceptorFactories =
+          [NSMutableArray arrayWithArray:_actualCallOptions.interceptorFactories];
+    } else {
+      interceptorFactories = [NSMutableArray array];
+    }
     id<GRPCInterceptorFactory> globalInterceptorFactory = [GRPCCall2 globalInterceptorFactory];
-    if (globalInterceptorFactory) {
-      GRPCInterceptorManager *manager =
-          [[GRPCInterceptorManager alloc] initWithNextInterceptor:nextInterceptor];
-      GRPCInterceptor *interceptor =
-          [globalInterceptorFactory createInterceptorWithManager:manager];
-      if (interceptor != nil) {
-        [internalCall setResponseHandler:interceptor];
-        nextInterceptor = interceptor;
-        nextManager = manager;
-      }
+    if (globalInterceptorFactory != nil) {
+      [interceptorFactories addObject:globalInterceptorFactory];
     }
-
-    // Finally initialize the interceptors in the chain
-    NSArray *interceptorFactories = _actualCallOptions.interceptorFactories;
-    for (int i = (int)interceptorFactories.count - 1; i >= 0; i--) {
-      GRPCInterceptorManager *manager =
-          [[GRPCInterceptorManager alloc] initWithNextInterceptor:nextInterceptor];
-      GRPCInterceptor *interceptor = [interceptorFactories[i] createInterceptorWithManager:manager];
-      NSAssert(interceptor != nil, @"Failed to create interceptor from factory: %@",
-               interceptorFactories[i]);
-      if (interceptor == nil) {
-        NSLog(@"Failed to create interceptor from factory: %@", interceptorFactories[i]);
-        continue;
-      }
-      if (nextManager == nil) {
-        [internalCall setResponseHandler:interceptor];
+    // continuously create interceptor until one is successfully created
+    while (_firstInterceptor == nil) {
+      if (interceptorFactories.count == 0) {
+        _firstInterceptor = [[GRPCTransportManager alloc] initWithTransportId:_callOptions.transport
+                                                          previousInterceptor:dispatcher];
+        break;
       } else {
-        [nextManager setPreviousInterceptor:interceptor];
+        _firstInterceptor =
+            [[GRPCInterceptorManager alloc] initWithFactories:interceptorFactories
+                                          previousInterceptor:dispatcher
+                                                  transportId:_callOptions.transport];
+        if (_firstInterceptor == nil) {
+          [interceptorFactories removeObjectAtIndex:0];
+        }
       }
-      nextInterceptor = interceptor;
-      nextManager = manager;
     }
-    if (nextManager == nil) {
-      [internalCall setResponseHandler:_responseHandler];
-    } else {
-      [nextManager setPreviousInterceptor:_responseHandler];
+    NSAssert(_firstInterceptor != nil, @"Failed to create interceptor or transport.");
+    if (_firstInterceptor == nil) {
+      NSLog(@"Failed to create interceptor or transport.");
     }
-
-    _firstInterceptor = nextInterceptor;
   }
 
   return self;
@@ -200,696 +215,42 @@ const char *kCFStreamVarName = "grpc_cfstream";
 }
 
 - (void)start {
-  id<GRPCInterceptorInterface> copiedFirstInterceptor;
-  @synchronized(self) {
-    copiedFirstInterceptor = _firstInterceptor;
-  }
-  GRPCRequestOptions *requestOptions = [_requestOptions copy];
-  GRPCCallOptions *callOptions = [_actualCallOptions copy];
-  if ([copiedFirstInterceptor respondsToSelector:@selector(startWithRequestOptions:callOptions:)]) {
-    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
-      [copiedFirstInterceptor startWithRequestOptions:requestOptions callOptions:callOptions];
-    });
-  }
+  id<GRPCInterceptorInterface> copiedFirstInterceptor = _firstInterceptor;
+  GRPCRequestOptions *requestOptions = _requestOptions;
+  GRPCCallOptions *callOptions = _actualCallOptions;
+  dispatch_async(copiedFirstInterceptor.dispatchQueue, ^{
+    [copiedFirstInterceptor startWithRequestOptions:requestOptions callOptions:callOptions];
+  });
 }
 
 - (void)cancel {
-  id<GRPCInterceptorInterface> copiedFirstInterceptor;
-  @synchronized(self) {
-    copiedFirstInterceptor = _firstInterceptor;
-  }
-  if ([copiedFirstInterceptor respondsToSelector:@selector(cancel)]) {
-    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
+  id<GRPCInterceptorInterface> copiedFirstInterceptor = _firstInterceptor;
+  if (copiedFirstInterceptor != nil) {
+    dispatch_async(copiedFirstInterceptor.dispatchQueue, ^{
       [copiedFirstInterceptor cancel];
     });
   }
 }
 
 - (void)writeData:(id)data {
-  id<GRPCInterceptorInterface> copiedFirstInterceptor;
-  @synchronized(self) {
-    copiedFirstInterceptor = _firstInterceptor;
-  }
-  if ([copiedFirstInterceptor respondsToSelector:@selector(writeData:)]) {
-    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
-      [copiedFirstInterceptor writeData:data];
-    });
-  }
-}
-
-- (void)finish {
-  id<GRPCInterceptorInterface> copiedFirstInterceptor;
-  @synchronized(self) {
-    copiedFirstInterceptor = _firstInterceptor;
-  }
-  if ([copiedFirstInterceptor respondsToSelector:@selector(finish)]) {
-    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
-      [copiedFirstInterceptor finish];
-    });
-  }
-}
-
-- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
-  id<GRPCInterceptorInterface> copiedFirstInterceptor;
-  @synchronized(self) {
-    copiedFirstInterceptor = _firstInterceptor;
-  }
-  if ([copiedFirstInterceptor respondsToSelector:@selector(receiveNextMessages:)]) {
-    dispatch_async(copiedFirstInterceptor.requestDispatchQueue, ^{
-      [copiedFirstInterceptor receiveNextMessages:numberOfMessages];
-    });
-  }
-}
-
-@end
-
-// The following methods of a C gRPC call object aren't reentrant, and thus
-// calls to them must be serialized:
-// - start_batch
-// - destroy
-//
-// start_batch with a SEND_MESSAGE argument can only be called after the
-// OP_COMPLETE event for any previous write is received. This is achieved by
-// pausing the requests writer immediately every time it writes a value, and
-// resuming it again when OP_COMPLETE is received.
-//
-// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
-// the OP_COMPLETE event for any previous read is received.This is easier to
-// enforce, as we're writing the received messages into the writeable:
-// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
-// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
-// each RECV_MESSAGE batch.
-@implementation GRPCCall {
-  dispatch_queue_t _callQueue;
-
-  NSString *_host;
-  NSString *_path;
-  GRPCCallSafety _callSafety;
-  GRPCCallOptions *_callOptions;
-  GRPCWrappedCall *_wrappedCall;
-
-  // The C gRPC library has less guarantees on the ordering of events than we
-  // do. Particularly, in the face of errors, there's no ordering guarantee at
-  // all. This wrapper over our actual writeable ensures thread-safety and
-  // correct ordering.
-  GRXConcurrentWriteable *_responseWriteable;
-
-  // The network thread wants the requestWriter to resume (when the server is ready for more input),
-  // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop
-  // it. Because a writer isn't thread-safe, we'll synchronize those operations on it.
-  // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or
-  // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to
-  // pause the writer immediately on writeValue:, so we need our locking to be recursive.
-  GRXWriter *_requestWriter;
-
-  // To create a retain cycle when a call is started, up until it finishes. See
-  // |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a
-  // reference to the call object if all they're interested in is the handler being executed when
-  // the response arrives.
-  GRPCCall *_retainSelf;
-
-  GRPCRequestHeaders *_requestHeaders;
-
-  // In the case that the call is a unary call (i.e. the writer to GRPCCall is of type
-  // GRXImmediateSingleWriter), GRPCCall will delay sending ops (not send them to C core
-  // immediately) and buffer them into a batch _unaryOpBatch. The batch is sent to C core when
-  // the SendClose op is added.
-  BOOL _unaryCall;
-  NSMutableArray *_unaryOpBatch;
-
-  // The dispatch queue to be used for enqueuing responses to user. Defaulted to the main dispatch
-  // queue
-  dispatch_queue_t _responseQueue;
-
-  // The OAuth2 token fetched from a token provider.
-  NSString *_fetchedOauth2AccessToken;
-
-  // The callback to be called when a write message op is done.
-  void (^_writeDone)(void);
-
-  // Indicate a read request to core is pending.
-  BOOL _pendingCoreRead;
-
-  // Indicate pending read message request from user.
-  NSUInteger _pendingReceiveNextMessages;
-}
-
-@synthesize state = _state;
-
-+ (void)initialize {
-  // Guarantees the code in {} block is invoked only once. See ref at:
-  // https://developer.apple.com/documentation/objectivec/nsobject/1418639-initialize?language=objc
-  if (self == [GRPCCall self]) {
-    grpc_init();
-    callFlags = [NSMutableDictionary dictionary];
-  }
-}
-
-+ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path {
-  if (host.length == 0 || path.length == 0) {
-    return;
-  }
-  NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
-  @synchronized(callFlags) {
-    switch (callSafety) {
-      case GRPCCallSafetyDefault:
-        callFlags[hostAndPath] = @0;
-        break;
-      case GRPCCallSafetyIdempotentRequest:
-        callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
-        break;
-      case GRPCCallSafetyCacheableRequest:
-        callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
-        break;
-      default:
-        break;
-    }
-  }
-}
-
-+ (uint32_t)callFlagsForHost:(NSString *)host path:(NSString *)path {
-  NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
-  @synchronized(callFlags) {
-    return [callFlags[hostAndPath] intValue];
-  }
-}
-
-// Designated initializer
-- (instancetype)initWithHost:(NSString *)host
-                        path:(NSString *)path
-              requestsWriter:(GRXWriter *)requestWriter {
-  return [self initWithHost:host
-                       path:path
-                 callSafety:GRPCCallSafetyDefault
-             requestsWriter:requestWriter
-                callOptions:nil];
-}
-
-- (instancetype)initWithHost:(NSString *)host
-                        path:(NSString *)path
-                  callSafety:(GRPCCallSafety)safety
-              requestsWriter:(GRXWriter *)requestsWriter
-                 callOptions:(GRPCCallOptions *)callOptions {
-  return [self initWithHost:host
-                       path:path
-                 callSafety:safety
-             requestsWriter:requestsWriter
-                callOptions:callOptions
-                  writeDone:nil];
-}
-
-- (instancetype)initWithHost:(NSString *)host
-                        path:(NSString *)path
-                  callSafety:(GRPCCallSafety)safety
-              requestsWriter:(GRXWriter *)requestsWriter
-                 callOptions:(GRPCCallOptions *)callOptions
-                   writeDone:(void (^)(void))writeDone {
-  // Purposely using pointer rather than length (host.length == 0) for backwards compatibility.
-  NSAssert(host != nil && path != nil, @"Neither host nor path can be nil.");
-  NSAssert(safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value.");
-  NSAssert(requestsWriter.state == GRXWriterStateNotStarted,
-           @"The requests writer can't be already started.");
-  if (!host || !path) {
-    return nil;
-  }
-  if (safety > GRPCCallSafetyCacheableRequest) {
-    return nil;
-  }
-  if (requestsWriter.state != GRXWriterStateNotStarted) {
-    return nil;
-  }
-
-  if ((self = [super init])) {
-    _host = [host copy];
-    _path = [path copy];
-    _callSafety = safety;
-    _callOptions = [callOptions copy];
-
-    // Serial queue to invoke the non-reentrant methods of the grpc_call object.
-    _callQueue = dispatch_queue_create("io.grpc.call", DISPATCH_QUEUE_SERIAL);
-
-    _requestWriter = requestsWriter;
-    _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
-    _writeDone = writeDone;
-
-    if ([requestsWriter isKindOfClass:[GRXImmediateSingleWriter class]]) {
-      _unaryCall = YES;
-      _unaryOpBatch = [NSMutableArray arrayWithCapacity:kMaxClientBatch];
-    }
-
-    _responseQueue = dispatch_get_main_queue();
-
-    // do not start a read until initial metadata is received
-    _pendingReceiveNextMessages = 0;
-    _pendingCoreRead = YES;
-  }
-  return self;
-}
-
-- (void)setResponseDispatchQueue:(dispatch_queue_t)queue {
-  @synchronized(self) {
-    if (_state != GRXWriterStateNotStarted) {
-      return;
-    }
-    _responseQueue = queue;
-  }
-}
-
-#pragma mark Finish
-
-// This function should support being called within a @synchronized(self) block in another function
-// Should not manipulate _requestWriter for deadlock prevention.
-- (void)finishWithError:(NSError *)errorOrNil {
-  @synchronized(self) {
-    if (_state == GRXWriterStateFinished) {
-      return;
-    }
-    _state = GRXWriterStateFinished;
-
-    if (errorOrNil) {
-      [_responseWriteable cancelWithError:errorOrNil];
-    } else {
-      [_responseWriteable enqueueSuccessfulCompletion];
-    }
-
-    // If the call isn't retained anywhere else, it can be deallocated now.
-    _retainSelf = nil;
-  }
-}
-
-- (void)cancel {
-  @synchronized(self) {
-    if (_state == GRXWriterStateFinished) {
-      return;
-    }
-    [self finishWithError:[NSError
-                              errorWithDomain:kGRPCErrorDomain
-                                         code:GRPCErrorCodeCancelled
-                                     userInfo:@{NSLocalizedDescriptionKey : @"Canceled by app"}]];
-    [_wrappedCall cancel];
-  }
-  _requestWriter.state = GRXWriterStateFinished;
-}
-
-- (void)dealloc {
-  __block GRPCWrappedCall *wrappedCall = _wrappedCall;
-  dispatch_async(_callQueue, ^{
-    wrappedCall = nil;
-  });
-}
-
-#pragma mark Read messages
-
-// Only called from the call queue.
-// The handler will be called from the network queue.
-- (void)startReadWithHandler:(void (^)(grpc_byte_buffer *))handler {
-  // TODO(jcanizales): Add error handlers for async failures
-  [_wrappedCall startBatchWithOperations:@[ [[GRPCOpRecvMessage alloc] initWithHandler:handler] ]];
-}
-
-// Called initially from the network queue once response headers are received,
-// then "recursively" from the responseWriteable queue after each response from the
-// server has been written.
-// If the call is currently paused, this is a noop. Restarting the call will invoke this
-// method.
-// TODO(jcanizales): Rename to readResponseIfNotPaused.
-- (void)maybeStartNextRead {
-  @synchronized(self) {
-    if (_state != GRXWriterStateStarted) {
-      return;
-    }
-    if (_callOptions.flowControlEnabled && (_pendingCoreRead || _pendingReceiveNextMessages == 0)) {
-      return;
-    }
-    _pendingCoreRead = YES;
-    _pendingReceiveNextMessages--;
-  }
-
-  dispatch_async(_callQueue, ^{
-    __weak GRPCCall *weakSelf = self;
-    [self startReadWithHandler:^(grpc_byte_buffer *message) {
-      if (message == NULL) {
-        // No more messages from the server
-        return;
-      }
-      __strong GRPCCall *strongSelf = weakSelf;
-      if (strongSelf == nil) {
-        grpc_byte_buffer_destroy(message);
-        return;
-      }
-      NSData *data = [NSData grpc_dataWithByteBuffer:message];
-      grpc_byte_buffer_destroy(message);
-      if (!data) {
-        // The app doesn't have enough memory to hold the server response. We
-        // don't want to throw, because the app shouldn't crash for a behavior
-        // that's on the hands of any server to have. Instead we finish and ask
-        // the server to cancel.
-        @synchronized(strongSelf) {
-          strongSelf->_pendingCoreRead = NO;
-          [strongSelf
-              finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
-                                                  code:GRPCErrorCodeResourceExhausted
-                                              userInfo:@{
-                                                NSLocalizedDescriptionKey :
-                                                    @"Client does not have enough memory to "
-                                                    @"hold the server response."
-                                              }]];
-          [strongSelf->_wrappedCall cancel];
-        }
-        strongSelf->_requestWriter.state = GRXWriterStateFinished;
-      } else {
-        @synchronized(strongSelf) {
-          [strongSelf->_responseWriteable enqueueValue:data
-                                     completionHandler:^{
-                                       __strong GRPCCall *strongSelf = weakSelf;
-                                       if (strongSelf) {
-                                         @synchronized(strongSelf) {
-                                           strongSelf->_pendingCoreRead = NO;
-                                           [strongSelf maybeStartNextRead];
-                                         }
-                                       }
-                                     }];
-        }
-      }
-    }];
+  id<GRPCInterceptorInterface> copiedFirstInterceptor = _firstInterceptor;
+  dispatch_async(copiedFirstInterceptor.dispatchQueue, ^{
+    [copiedFirstInterceptor writeData:data];
   });
 }
 
-#pragma mark Send headers
-
-- (void)sendHeaders {
-  // TODO (mxyan): Remove after deprecated methods are removed
-  uint32_t callSafetyFlags = 0;
-  switch (_callSafety) {
-    case GRPCCallSafetyDefault:
-      callSafetyFlags = 0;
-      break;
-    case GRPCCallSafetyIdempotentRequest:
-      callSafetyFlags = GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
-      break;
-    case GRPCCallSafetyCacheableRequest:
-      callSafetyFlags = GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
-      break;
-  }
-
-  NSMutableDictionary *headers = [_requestHeaders mutableCopy];
-  NSString *fetchedOauth2AccessToken;
-  @synchronized(self) {
-    fetchedOauth2AccessToken = _fetchedOauth2AccessToken;
-  }
-  if (fetchedOauth2AccessToken != nil) {
-    headers[@"authorization"] = [kBearerPrefix stringByAppendingString:fetchedOauth2AccessToken];
-  } else if (_callOptions.oauth2AccessToken != nil) {
-    headers[@"authorization"] =
-        [kBearerPrefix stringByAppendingString:_callOptions.oauth2AccessToken];
-  }
-
-  // TODO(jcanizales): Add error handlers for async failures
-  GRPCOpSendMetadata *op = [[GRPCOpSendMetadata alloc]
-      initWithMetadata:headers
-                 flags:callSafetyFlags
-               handler:nil];  // No clean-up needed after SEND_INITIAL_METADATA
-  dispatch_async(_callQueue, ^{
-    if (!self->_unaryCall) {
-      [self->_wrappedCall startBatchWithOperations:@[ op ]];
-    } else {
-      [self->_unaryOpBatch addObject:op];
-    }
+- (void)finish {
+  id<GRPCInterceptorInterface> copiedFirstInterceptor = _firstInterceptor;
+  dispatch_async(copiedFirstInterceptor.dispatchQueue, ^{
+    [copiedFirstInterceptor finish];
   });
 }
 
 - (void)receiveNextMessages:(NSUInteger)numberOfMessages {
-  if (numberOfMessages == 0) {
-    return;
-  }
-  @synchronized(self) {
-    _pendingReceiveNextMessages += numberOfMessages;
-
-    if (_state != GRXWriterStateStarted || !_callOptions.flowControlEnabled) {
-      return;
-    }
-    [self maybeStartNextRead];
-  }
-}
-
-#pragma mark GRXWriteable implementation
-
-// Only called from the call queue. The error handler will be called from the
-// network queue if the write didn't succeed.
-// If the call is a unary call, parameter \a errorHandler will be ignored and
-// the error handler of GRPCOpSendClose will be executed in case of error.
-- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)(void))errorHandler {
-  __weak GRPCCall *weakSelf = self;
-  void (^resumingHandler)(void) = ^{
-    // Resume the request writer.
-    GRPCCall *strongSelf = weakSelf;
-    if (strongSelf) {
-      strongSelf->_requestWriter.state = GRXWriterStateStarted;
-      if (strongSelf->_writeDone) {
-        strongSelf->_writeDone();
-      }
-    }
-  };
-  GRPCOpSendMessage *op =
-      [[GRPCOpSendMessage alloc] initWithMessage:message handler:resumingHandler];
-  if (!_unaryCall) {
-    [_wrappedCall startBatchWithOperations:@[ op ] errorHandler:errorHandler];
-  } else {
-    // Ignored errorHandler since it is the same as the one for GRPCOpSendClose.
-    // TODO (mxyan): unify the error handlers of all Ops into a single closure.
-    [_unaryOpBatch addObject:op];
-  }
-}
-
-- (void)writeValue:(id)value {
-  NSAssert([value isKindOfClass:[NSData class]], @"value must be of type NSData");
-
-  @synchronized(self) {
-    if (_state == GRXWriterStateFinished) {
-      return;
-    }
-  }
-
-  // Pause the input and only resume it when the C layer notifies us that writes
-  // can proceed.
-  _requestWriter.state = GRXWriterStatePaused;
-
-  dispatch_async(_callQueue, ^{
-    // Write error is not processed here. It is handled by op batch of GRPC_OP_RECV_STATUS_ON_CLIENT
-    [self writeMessage:value withErrorHandler:nil];
-  });
-}
-
-// Only called from the call queue. The error handler will be called from the
-// network queue if the requests stream couldn't be closed successfully.
-- (void)finishRequestWithErrorHandler:(void (^)(void))errorHandler {
-  if (!_unaryCall) {
-    [_wrappedCall startBatchWithOperations:@[ [[GRPCOpSendClose alloc] init] ]
-                              errorHandler:errorHandler];
-  } else {
-    [_unaryOpBatch addObject:[[GRPCOpSendClose alloc] init]];
-    [_wrappedCall startBatchWithOperations:_unaryOpBatch errorHandler:errorHandler];
-  }
-}
-
-- (void)writesFinishedWithError:(NSError *)errorOrNil {
-  if (errorOrNil) {
-    [self cancel];
-  } else {
-    dispatch_async(_callQueue, ^{
-      // EOS error is not processed here. It is handled by op batch of GRPC_OP_RECV_STATUS_ON_CLIENT
-      [self finishRequestWithErrorHandler:nil];
-    });
-  }
-}
-
-#pragma mark Invoke
-
-// Both handlers will eventually be called, from the network queue. Writes can start immediately
-// after this.
-// The first one (headersHandler), when the response headers are received.
-// The second one (completionHandler), whenever the RPC finishes for any reason.
-- (void)invokeCallWithHeadersHandler:(void (^)(NSDictionary *))headersHandler
-                   completionHandler:(void (^)(NSError *, NSDictionary *))completionHandler {
-  dispatch_async(_callQueue, ^{
-    // TODO(jcanizales): Add error handlers for async failures
-    [self->_wrappedCall
-        startBatchWithOperations:@[ [[GRPCOpRecvMetadata alloc] initWithHandler:headersHandler] ]];
-    [self->_wrappedCall
-        startBatchWithOperations:@[ [[GRPCOpRecvStatus alloc] initWithHandler:completionHandler] ]];
+  id<GRPCInterceptorInterface> copiedFirstInterceptor = _firstInterceptor;
+  dispatch_async(copiedFirstInterceptor.dispatchQueue, ^{
+    [copiedFirstInterceptor receiveNextMessages:numberOfMessages];
   });
 }
 
-- (void)invokeCall {
-  __weak GRPCCall *weakSelf = self;
-  [self invokeCallWithHeadersHandler:^(NSDictionary *headers) {
-    // Response headers received.
-    __strong GRPCCall *strongSelf = weakSelf;
-    if (strongSelf) {
-      @synchronized(strongSelf) {
-        // it is ok to set nil because headers are only received once
-        strongSelf.responseHeaders = nil;
-        // copy the header so that the GRPCOpRecvMetadata object may be dealloc'ed
-        NSDictionary *copiedHeaders =
-            [[NSDictionary alloc] initWithDictionary:headers copyItems:YES];
-        strongSelf.responseHeaders = copiedHeaders;
-        strongSelf->_pendingCoreRead = NO;
-        [strongSelf maybeStartNextRead];
-      }
-    }
-  }
-      completionHandler:^(NSError *error, NSDictionary *trailers) {
-        __strong GRPCCall *strongSelf = weakSelf;
-        if (strongSelf) {
-          strongSelf.responseTrailers = trailers;
-
-          if (error) {
-            NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
-            if (error.userInfo) {
-              [userInfo addEntriesFromDictionary:error.userInfo];
-            }
-            userInfo[kGRPCTrailersKey] = strongSelf.responseTrailers;
-            // Since gRPC core does not guarantee the headers block being called before this block,
-            // responseHeaders might be nil.
-            userInfo[kGRPCHeadersKey] = strongSelf.responseHeaders;
-            error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
-          }
-          [strongSelf finishWithError:error];
-          strongSelf->_requestWriter.state = GRXWriterStateFinished;
-        }
-      }];
-}
-
-#pragma mark GRXWriter implementation
-
-// Lock acquired inside startWithWriteable:
-- (void)startCallWithWriteable:(id<GRXWriteable>)writeable {
-  @synchronized(self) {
-    if (_state == GRXWriterStateFinished) {
-      return;
-    }
-
-    _responseWriteable =
-        [[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue];
-
-    GRPCPooledChannel *channel =
-        [[GRPCChannelPool sharedInstance] channelWithHost:_host callOptions:_callOptions];
-    _wrappedCall = [channel wrappedCallWithPath:_path
-                                completionQueue:[GRPCCompletionQueue completionQueue]
-                                    callOptions:_callOptions];
-
-    if (_wrappedCall == nil) {
-      [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
-                                                code:GRPCErrorCodeUnavailable
-                                            userInfo:@{
-                                              NSLocalizedDescriptionKey :
-                                                  @"Failed to create call or channel."
-                                            }]];
-      return;
-    }
-
-    [self sendHeaders];
-    [self invokeCall];
-  }
-
-  // Now that the RPC has been initiated, request writes can start.
-  [_requestWriter startWithWriteable:self];
-}
-
-- (void)startWithWriteable:(id<GRXWriteable>)writeable {
-  id<GRPCAuthorizationProtocol> tokenProvider = nil;
-  @synchronized(self) {
-    _state = GRXWriterStateStarted;
-
-    // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
-    // This makes RPCs in which the call isn't externally retained possible (as long as it is
-    // started before being autoreleased). Care is taken not to retain self strongly in any of the
-    // blocks used in this implementation, so that the life of the instance is determined by this
-    // retain cycle.
-    _retainSelf = self;
-
-    if (_callOptions == nil) {
-      GRPCMutableCallOptions *callOptions = [[GRPCHost callOptionsForHost:_host] mutableCopy];
-      if (_serverName.length != 0) {
-        callOptions.serverAuthority = _serverName;
-      }
-      if (_timeout > 0) {
-        callOptions.timeout = _timeout;
-      }
-      uint32_t callFlags = [GRPCCall callFlagsForHost:_host path:_path];
-      if (callFlags != 0) {
-        if (callFlags == GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) {
-          _callSafety = GRPCCallSafetyIdempotentRequest;
-        } else if (callFlags == GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) {
-          _callSafety = GRPCCallSafetyCacheableRequest;
-        }
-      }
-
-      id<GRPCAuthorizationProtocol> tokenProvider = self.tokenProvider;
-      if (tokenProvider != nil) {
-        callOptions.authTokenProvider = tokenProvider;
-      }
-      _callOptions = callOptions;
-    }
-
-    NSAssert(_callOptions.authTokenProvider == nil || _callOptions.oauth2AccessToken == nil,
-             @"authTokenProvider and oauth2AccessToken cannot be set at the same time");
-
-    tokenProvider = _callOptions.authTokenProvider;
-  }
-
-  if (tokenProvider != nil) {
-    __weak typeof(self) weakSelf = self;
-    [tokenProvider getTokenWithHandler:^(NSString *token) {
-      __strong typeof(self) strongSelf = weakSelf;
-      if (strongSelf) {
-        BOOL startCall = NO;
-        @synchronized(strongSelf) {
-          if (strongSelf->_state != GRXWriterStateFinished) {
-            startCall = YES;
-            if (token) {
-              strongSelf->_fetchedOauth2AccessToken = [token copy];
-            }
-          }
-        }
-        if (startCall) {
-          [strongSelf startCallWithWriteable:writeable];
-        }
-      }
-    }];
-  } else {
-    [self startCallWithWriteable:writeable];
-  }
-}
-
-- (void)setState:(GRXWriterState)newState {
-  @synchronized(self) {
-    // Manual transitions are only allowed from the started or paused states.
-    if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
-      return;
-    }
-
-    switch (newState) {
-      case GRXWriterStateFinished:
-        _state = newState;
-        // Per GRXWriter's contract, setting the state to Finished manually
-        // means one doesn't wish the writeable to be messaged anymore.
-        [_responseWriteable cancelSilently];
-        _responseWriteable = nil;
-        return;
-      case GRXWriterStatePaused:
-        _state = newState;
-        return;
-      case GRXWriterStateStarted:
-        if (_state == GRXWriterStatePaused) {
-          _state = newState;
-          [self maybeStartNextRead];
-        }
-        return;
-      case GRXWriterStateNotStarted:
-        return;
-    }
-  }
-}
-
 @end
diff --git a/src/objective-c/GRPCClient/GRPCCallLegacy.h b/src/objective-c/GRPCClient/GRPCCallLegacy.h
new file mode 100644
index 0000000000000000000000000000000000000000..51dd7da3440169de57664bd616e07a5c430a8706
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCCallLegacy.h
@@ -0,0 +1,136 @@
+/*
+ *
+ * Copyright 2019 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.
+ *
+ */
+
+/**
+ * This is the legacy interface of this gRPC library. This API is deprecated and users should use
+ * the API in GRPCCall.h. This API exists solely for the purpose of backwards compatibility.
+ */
+
+#import <RxLibrary/GRXWriter.h>
+#import "GRPCTypes.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnullability-completeness"
+
+/**
+ * This interface is deprecated. Please use \a GRPCCall2.
+ *
+ * Represents a single gRPC remote call.
+ */
+@interface GRPCCall : GRXWriter
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/**
+ * The container of the request headers of an RPC conforms to this protocol, which is a subset of
+ * NSMutableDictionary's interface. It will become a NSMutableDictionary later on.
+ * The keys of this container are the header names, which per the HTTP standard are case-
+ * insensitive. They are stored in lowercase (which is how HTTP/2 mandates them on the wire), and
+ * can only consist of ASCII characters.
+ * A header value is a NSString object (with only ASCII characters), unless the header name has the
+ * suffix "-bin", in which case the value has to be a NSData object.
+ */
+/**
+ * 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.
+ *
+ * The passed dictionary has to use NSString keys, corresponding to the header names. The value
+ * associated to each can be a NSString object or a NSData object. E.g.:
+ *
+ * call.requestHeaders = @{@"authorization": @"Bearer ..."};
+ *
+ * call.requestHeaders[@"my-header-bin"] = someData;
+ *
+ * After the call is started, trying to modify this property is an error.
+ *
+ * The property is initialized to an empty NSMutableDictionary.
+ */
+@property(atomic, readonly) NSMutableDictionary *requestHeaders;
+
+/**
+ * This dictionary is populated with the HTTP headers received from the server. This happens before
+ * any response message is received from the server. It has the same structure as the request
+ * headers dictionary: Keys are NSString header names; names ending with the suffix "-bin" have a
+ * NSData value; the others have a NSString value.
+ *
+ * The value of this property is nil until all response headers are received, and will change before
+ * any of -writeValue: or -writesFinishedWithError: are sent to the writeable.
+ */
+@property(atomic, readonly) NSDictionary *responseHeaders;
+
+/**
+ * Same as responseHeaders, but populated with the HTTP trailers received from the server before the
+ * call finishes.
+ *
+ * The value of this property is nil until all response trailers are received, and will change
+ * before -writesFinishedWithError: is sent to the writeable.
+ */
+@property(atomic, readonly) NSDictionary *responseTrailers;
+
+/**
+ * The request writer has to write NSData objects into the provided Writeable. The server will
+ * receive each of those separately and in order as distinct messages.
+ * A gRPC call might not complete until the request writer finishes. On the other hand, the request
+ * finishing doesn't necessarily make the call to finish, as the server might continue sending
+ * messages to the response side of the call indefinitely (depending on the semantics of the
+ * specific remote method called).
+ * To finish a call right away, invoke cancel.
+ * host parameter should not contain the scheme (http:// or https://), only the name or IP addr
+ * and the port number, for example @"localhost:5050".
+ */
+- (instancetype)initWithHost:(NSString *)host
+                        path:(NSString *)path
+              requestsWriter:(GRXWriter *)requestWriter;
+
+/**
+ * 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 of code CANCELED.
+ */
+- (void)cancel;
+
+/**
+ * The following methods are deprecated.
+ */
++ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path;
+@property(atomic, copy, readwrite) NSString *serverName;
+@property NSTimeInterval timeout;
+- (void)setResponseDispatchQueue:(dispatch_queue_t)queue;
+
+@end
+
+#pragma mark Backwards compatibiity
+
+/** This protocol is kept for backwards compatibility with existing code. */
+DEPRECATED_MSG_ATTRIBUTE("Use NSDictionary or NSMutableDictionary instead.")
+@protocol GRPCRequestHeaders<NSObject>
+@property(nonatomic, readonly) NSUInteger count;
+
+- (id)objectForKeyedSubscript:(id)key;
+- (void)setObject:(id)obj forKeyedSubscript:(id)key;
+
+- (void)removeAllObjects;
+- (void)removeObjectForKey:(id)key;
+@end
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated"
+/** This is only needed for backwards-compatibility. */
+@interface NSMutableDictionary (GRPCRequestHeaders)<GRPCRequestHeaders>
+@end
+#pragma clang diagnostic pop
+#pragma clang diagnostic pop
diff --git a/src/objective-c/GRPCClient/GRPCCallLegacy.m b/src/objective-c/GRPCClient/GRPCCallLegacy.m
new file mode 100644
index 0000000000000000000000000000000000000000..d06048c3a89b3c08bd40fe33965a3eb62ede1e06
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCCallLegacy.m
@@ -0,0 +1,677 @@
+/*
+ *
+ * Copyright 2019 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 "GRPCCallLegacy.h"
+
+#import "GRPCCall+OAuth2.h"
+#import "GRPCCallOptions.h"
+#import "GRPCTypes.h"
+
+#import "private/GRPCCore/GRPCChannelPool.h"
+#import "private/GRPCCore/GRPCCompletionQueue.h"
+#import "private/GRPCCore/GRPCHost.h"
+#import "private/GRPCCore/GRPCWrappedCall.h"
+#import "private/GRPCCore/NSData+GRPC.h"
+
+#import <RxLibrary/GRXBufferedPipe.h>
+#import <RxLibrary/GRXConcurrentWriteable.h>
+#import <RxLibrary/GRXImmediateSingleWriter.h>
+#import <RxLibrary/GRXWriter+Immediate.h>
+
+#include <grpc/grpc.h>
+
+const char *kCFStreamVarName = "grpc_cfstream";
+static NSMutableDictionary *callFlags;
+
+// At most 6 ops can be in an op batch for a client: SEND_INITIAL_METADATA,
+// SEND_MESSAGE, SEND_CLOSE_FROM_CLIENT, RECV_INITIAL_METADATA, RECV_MESSAGE,
+// and RECV_STATUS_ON_CLIENT.
+NSInteger kMaxClientBatch = 6;
+
+static NSString *const kAuthorizationHeader = @"authorization";
+static NSString *const kBearerPrefix = @"Bearer ";
+
+@interface GRPCCall ()<GRXWriteable>
+// Make them read-write.
+@property(atomic, strong) NSDictionary *responseHeaders;
+@property(atomic, strong) NSDictionary *responseTrailers;
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages;
+
+@end
+
+// The following methods of a C gRPC call object aren't reentrant, and thus
+// calls to them must be serialized:
+// - start_batch
+// - destroy
+//
+// start_batch with a SEND_MESSAGE argument can only be called after the
+// OP_COMPLETE event for any previous write is received. This is achieved by
+// pausing the requests writer immediately every time it writes a value, and
+// resuming it again when OP_COMPLETE is received.
+//
+// Similarly, start_batch with a RECV_MESSAGE argument can only be called after
+// the OP_COMPLETE event for any previous read is received.This is easier to
+// enforce, as we're writing the received messages into the writeable:
+// start_batch is enqueued once upon receiving the OP_COMPLETE event for the
+// RECV_METADATA batch, and then once after receiving each OP_COMPLETE event for
+// each RECV_MESSAGE batch.
+@implementation GRPCCall {
+  dispatch_queue_t _callQueue;
+
+  NSString *_host;
+  NSString *_path;
+  GRPCCallSafety _callSafety;
+  GRPCCallOptions *_callOptions;
+  GRPCWrappedCall *_wrappedCall;
+
+  // The C gRPC library has less guarantees on the ordering of events than we
+  // do. Particularly, in the face of errors, there's no ordering guarantee at
+  // all. This wrapper over our actual writeable ensures thread-safety and
+  // correct ordering.
+  GRXConcurrentWriteable *_responseWriteable;
+
+  // The network thread wants the requestWriter to resume (when the server is ready for more input),
+  // or to stop (on errors), concurrently with user threads that want to start it, pause it or stop
+  // it. Because a writer isn't thread-safe, we'll synchronize those operations on it.
+  // We don't use a dispatch queue for that purpose, because the writer can call writeValue: or
+  // writesFinishedWithError: on this GRPCCall as part of those operations. We want to be able to
+  // pause the writer immediately on writeValue:, so we need our locking to be recursive.
+  GRXWriter *_requestWriter;
+
+  // To create a retain cycle when a call is started, up until it finishes. See
+  // |startWithWriteable:| and |finishWithError:|. This saves users from having to retain a
+  // reference to the call object if all they're interested in is the handler being executed when
+  // the response arrives.
+  GRPCCall *_retainSelf;
+
+  GRPCRequestHeaders *_requestHeaders;
+
+  // In the case that the call is a unary call (i.e. the writer to GRPCCall is of type
+  // GRXImmediateSingleWriter), GRPCCall will delay sending ops (not send them to C core
+  // immediately) and buffer them into a batch _unaryOpBatch. The batch is sent to C core when
+  // the SendClose op is added.
+  BOOL _unaryCall;
+  NSMutableArray *_unaryOpBatch;
+
+  // The dispatch queue to be used for enqueuing responses to user. Defaulted to the main dispatch
+  // queue
+  dispatch_queue_t _responseQueue;
+
+  // The OAuth2 token fetched from a token provider.
+  NSString *_fetchedOauth2AccessToken;
+
+  // The callback to be called when a write message op is done.
+  void (^_writeDone)(void);
+
+  // Indicate a read request to core is pending.
+  BOOL _pendingCoreRead;
+
+  // Indicate pending read message request from user.
+  NSUInteger _pendingReceiveNextMessages;
+}
+
+@synthesize state = _state;
+
++ (void)initialize {
+  // Guarantees the code in {} block is invoked only once. See ref at:
+  // https://developer.apple.com/documentation/objectivec/nsobject/1418639-initialize?language=objc
+  if (self == [GRPCCall self]) {
+    grpc_init();
+    callFlags = [NSMutableDictionary dictionary];
+  }
+}
+
++ (void)setCallSafety:(GRPCCallSafety)callSafety host:(NSString *)host path:(NSString *)path {
+  if (host.length == 0 || path.length == 0) {
+    return;
+  }
+  NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
+  @synchronized(callFlags) {
+    switch (callSafety) {
+      case GRPCCallSafetyDefault:
+        callFlags[hostAndPath] = @0;
+        break;
+      case GRPCCallSafetyIdempotentRequest:
+        callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
+        break;
+      case GRPCCallSafetyCacheableRequest:
+        callFlags[hostAndPath] = @GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
+        break;
+      default:
+        break;
+    }
+  }
+}
+
++ (uint32_t)callFlagsForHost:(NSString *)host path:(NSString *)path {
+  NSString *hostAndPath = [NSString stringWithFormat:@"%@/%@", host, path];
+  @synchronized(callFlags) {
+    return [callFlags[hostAndPath] intValue];
+  }
+}
+
+- (instancetype)initWithHost:(NSString *)host
+                        path:(NSString *)path
+              requestsWriter:(GRXWriter *)requestWriter {
+  return [self initWithHost:host
+                       path:path
+                 callSafety:GRPCCallSafetyDefault
+             requestsWriter:requestWriter
+                callOptions:nil
+                  writeDone:nil];
+}
+
+- (instancetype)initWithHost:(NSString *)host
+                        path:(NSString *)path
+                  callSafety:(GRPCCallSafety)safety
+              requestsWriter:(GRXWriter *)requestsWriter
+                 callOptions:(GRPCCallOptions *)callOptions
+                   writeDone:(void (^)(void))writeDone {
+  // Purposely using pointer rather than length (host.length == 0) for backwards compatibility.
+  NSAssert(host != nil && path != nil, @"Neither host nor path can be nil.");
+  NSAssert(safety <= GRPCCallSafetyCacheableRequest, @"Invalid call safety value.");
+  NSAssert(requestsWriter.state == GRXWriterStateNotStarted,
+           @"The requests writer can't be already started.");
+  if (!host || !path) {
+    return nil;
+  }
+  if (safety > GRPCCallSafetyCacheableRequest) {
+    return nil;
+  }
+  if (requestsWriter.state != GRXWriterStateNotStarted) {
+    return nil;
+  }
+
+  if ((self = [super init])) {
+    _host = [host copy];
+    _path = [path copy];
+    _callSafety = safety;
+    _callOptions = [callOptions copy];
+
+    // Serial queue to invoke the non-reentrant methods of the grpc_call object.
+    _callQueue = dispatch_queue_create("io.grpc.call", DISPATCH_QUEUE_SERIAL);
+
+    _requestWriter = requestsWriter;
+    _requestHeaders = [[GRPCRequestHeaders alloc] initWithCall:self];
+    _writeDone = writeDone;
+
+    if ([requestsWriter isKindOfClass:[GRXImmediateSingleWriter class]]) {
+      _unaryCall = YES;
+      _unaryOpBatch = [NSMutableArray arrayWithCapacity:kMaxClientBatch];
+    }
+
+    _responseQueue = dispatch_get_main_queue();
+
+    // do not start a read until initial metadata is received
+    _pendingReceiveNextMessages = 0;
+    _pendingCoreRead = YES;
+  }
+  return self;
+}
+
+- (void)setResponseDispatchQueue:(dispatch_queue_t)queue {
+  @synchronized(self) {
+    if (_state != GRXWriterStateNotStarted) {
+      return;
+    }
+    _responseQueue = queue;
+  }
+}
+
+#pragma mark Finish
+
+// This function should support being called within a @synchronized(self) block in another function
+// Should not manipulate _requestWriter for deadlock prevention.
+- (void)finishWithError:(NSError *)errorOrNil {
+  @synchronized(self) {
+    if (_state == GRXWriterStateFinished) {
+      return;
+    }
+    _state = GRXWriterStateFinished;
+
+    if (errorOrNil) {
+      [_responseWriteable cancelWithError:errorOrNil];
+    } else {
+      [_responseWriteable enqueueSuccessfulCompletion];
+    }
+
+    // If the call isn't retained anywhere else, it can be deallocated now.
+    _retainSelf = nil;
+  }
+}
+
+- (void)cancel {
+  @synchronized(self) {
+    if (_state == GRXWriterStateFinished) {
+      return;
+    }
+    [self finishWithError:[NSError
+                              errorWithDomain:kGRPCErrorDomain
+                                         code:GRPCErrorCodeCancelled
+                                     userInfo:@{NSLocalizedDescriptionKey : @"Canceled by app"}]];
+    [_wrappedCall cancel];
+  }
+  _requestWriter.state = GRXWriterStateFinished;
+}
+
+- (void)dealloc {
+  __block GRPCWrappedCall *wrappedCall = _wrappedCall;
+  dispatch_async(_callQueue, ^{
+    wrappedCall = nil;
+  });
+}
+
+#pragma mark Read messages
+
+// Only called from the call queue.
+// The handler will be called from the network queue.
+- (void)startReadWithHandler:(void (^)(grpc_byte_buffer *))handler {
+  // TODO(jcanizales): Add error handlers for async failures
+  [_wrappedCall startBatchWithOperations:@[ [[GRPCOpRecvMessage alloc] initWithHandler:handler] ]];
+}
+
+// Called initially from the network queue once response headers are received,
+// then "recursively" from the responseWriteable queue after each response from the
+// server has been written.
+// If the call is currently paused, this is a noop. Restarting the call will invoke this
+// method.
+// TODO(jcanizales): Rename to readResponseIfNotPaused.
+- (void)maybeStartNextRead {
+  @synchronized(self) {
+    if (_state != GRXWriterStateStarted) {
+      return;
+    }
+    if (_callOptions.flowControlEnabled && (_pendingCoreRead || _pendingReceiveNextMessages == 0)) {
+      return;
+    }
+    _pendingCoreRead = YES;
+    _pendingReceiveNextMessages--;
+  }
+
+  dispatch_async(_callQueue, ^{
+    __weak GRPCCall *weakSelf = self;
+    [self startReadWithHandler:^(grpc_byte_buffer *message) {
+      if (message == NULL) {
+        // No more messages from the server
+        return;
+      }
+      __strong GRPCCall *strongSelf = weakSelf;
+      if (strongSelf == nil) {
+        grpc_byte_buffer_destroy(message);
+        return;
+      }
+      NSData *data = [NSData grpc_dataWithByteBuffer:message];
+      grpc_byte_buffer_destroy(message);
+      if (!data) {
+        // The app doesn't have enough memory to hold the server response. We
+        // don't want to throw, because the app shouldn't crash for a behavior
+        // that's on the hands of any server to have. Instead we finish and ask
+        // the server to cancel.
+        @synchronized(strongSelf) {
+          strongSelf->_pendingCoreRead = NO;
+          [strongSelf
+              finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
+                                                  code:GRPCErrorCodeResourceExhausted
+                                              userInfo:@{
+                                                NSLocalizedDescriptionKey :
+                                                    @"Client does not have enough memory to "
+                                                    @"hold the server response."
+                                              }]];
+          [strongSelf->_wrappedCall cancel];
+        }
+        strongSelf->_requestWriter.state = GRXWriterStateFinished;
+      } else {
+        @synchronized(strongSelf) {
+          [strongSelf->_responseWriteable enqueueValue:data
+                                     completionHandler:^{
+                                       __strong GRPCCall *strongSelf = weakSelf;
+                                       if (strongSelf) {
+                                         @synchronized(strongSelf) {
+                                           strongSelf->_pendingCoreRead = NO;
+                                           [strongSelf maybeStartNextRead];
+                                         }
+                                       }
+                                     }];
+        }
+      }
+    }];
+  });
+}
+
+#pragma mark Send headers
+
+- (void)sendHeaders {
+  // TODO (mxyan): Remove after deprecated methods are removed
+  uint32_t callSafetyFlags = 0;
+  switch (_callSafety) {
+    case GRPCCallSafetyDefault:
+      callSafetyFlags = 0;
+      break;
+    case GRPCCallSafetyIdempotentRequest:
+      callSafetyFlags = GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST;
+      break;
+    case GRPCCallSafetyCacheableRequest:
+      callSafetyFlags = GRPC_INITIAL_METADATA_CACHEABLE_REQUEST;
+      break;
+  }
+
+  NSMutableDictionary *headers = [_requestHeaders mutableCopy];
+  NSString *fetchedOauth2AccessToken;
+  @synchronized(self) {
+    fetchedOauth2AccessToken = _fetchedOauth2AccessToken;
+  }
+  if (fetchedOauth2AccessToken != nil) {
+    headers[@"authorization"] = [kBearerPrefix stringByAppendingString:fetchedOauth2AccessToken];
+  } else if (_callOptions.oauth2AccessToken != nil) {
+    headers[@"authorization"] =
+        [kBearerPrefix stringByAppendingString:_callOptions.oauth2AccessToken];
+  }
+
+  // TODO(jcanizales): Add error handlers for async failures
+  GRPCOpSendMetadata *op = [[GRPCOpSendMetadata alloc]
+      initWithMetadata:headers
+                 flags:callSafetyFlags
+               handler:nil];  // No clean-up needed after SEND_INITIAL_METADATA
+  dispatch_async(_callQueue, ^{
+    if (!self->_unaryCall) {
+      [self->_wrappedCall startBatchWithOperations:@[ op ]];
+    } else {
+      [self->_unaryOpBatch addObject:op];
+    }
+  });
+}
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  if (numberOfMessages == 0) {
+    return;
+  }
+  @synchronized(self) {
+    _pendingReceiveNextMessages += numberOfMessages;
+
+    if (_state != GRXWriterStateStarted || !_callOptions.flowControlEnabled) {
+      return;
+    }
+    [self maybeStartNextRead];
+  }
+}
+
+#pragma mark GRXWriteable implementation
+
+// Only called from the call queue. The error handler will be called from the
+// network queue if the write didn't succeed.
+// If the call is a unary call, parameter \a errorHandler will be ignored and
+// the error handler of GRPCOpSendClose will be executed in case of error.
+- (void)writeMessage:(NSData *)message withErrorHandler:(void (^)(void))errorHandler {
+  __weak GRPCCall *weakSelf = self;
+  void (^resumingHandler)(void) = ^{
+    // Resume the request writer.
+    GRPCCall *strongSelf = weakSelf;
+    if (strongSelf) {
+      strongSelf->_requestWriter.state = GRXWriterStateStarted;
+      if (strongSelf->_writeDone) {
+        strongSelf->_writeDone();
+      }
+    }
+  };
+  GRPCOpSendMessage *op =
+      [[GRPCOpSendMessage alloc] initWithMessage:message handler:resumingHandler];
+  if (!_unaryCall) {
+    [_wrappedCall startBatchWithOperations:@[ op ] errorHandler:errorHandler];
+  } else {
+    // Ignored errorHandler since it is the same as the one for GRPCOpSendClose.
+    // TODO (mxyan): unify the error handlers of all Ops into a single closure.
+    [_unaryOpBatch addObject:op];
+  }
+}
+
+- (void)writeValue:(id)value {
+  NSAssert([value isKindOfClass:[NSData class]], @"value must be of type NSData");
+
+  @synchronized(self) {
+    if (_state == GRXWriterStateFinished) {
+      return;
+    }
+  }
+
+  // Pause the input and only resume it when the C layer notifies us that writes
+  // can proceed.
+  _requestWriter.state = GRXWriterStatePaused;
+
+  dispatch_async(_callQueue, ^{
+    // Write error is not processed here. It is handled by op batch of GRPC_OP_RECV_STATUS_ON_CLIENT
+    [self writeMessage:value withErrorHandler:nil];
+  });
+}
+
+// Only called from the call queue. The error handler will be called from the
+// network queue if the requests stream couldn't be closed successfully.
+- (void)finishRequestWithErrorHandler:(void (^)(void))errorHandler {
+  if (!_unaryCall) {
+    [_wrappedCall startBatchWithOperations:@[ [[GRPCOpSendClose alloc] init] ]
+                              errorHandler:errorHandler];
+  } else {
+    [_unaryOpBatch addObject:[[GRPCOpSendClose alloc] init]];
+    [_wrappedCall startBatchWithOperations:_unaryOpBatch errorHandler:errorHandler];
+  }
+}
+
+- (void)writesFinishedWithError:(NSError *)errorOrNil {
+  if (errorOrNil) {
+    [self cancel];
+  } else {
+    dispatch_async(_callQueue, ^{
+      // EOS error is not processed here. It is handled by op batch of GRPC_OP_RECV_STATUS_ON_CLIENT
+      [self finishRequestWithErrorHandler:nil];
+    });
+  }
+}
+
+#pragma mark Invoke
+
+// Both handlers will eventually be called, from the network queue. Writes can start immediately
+// after this.
+// The first one (headersHandler), when the response headers are received.
+// The second one (completionHandler), whenever the RPC finishes for any reason.
+- (void)invokeCallWithHeadersHandler:(void (^)(NSDictionary *))headersHandler
+                   completionHandler:(void (^)(NSError *, NSDictionary *))completionHandler {
+  dispatch_async(_callQueue, ^{
+    // TODO(jcanizales): Add error handlers for async failures
+    [self->_wrappedCall
+        startBatchWithOperations:@[ [[GRPCOpRecvMetadata alloc] initWithHandler:headersHandler] ]];
+    [self->_wrappedCall
+        startBatchWithOperations:@[ [[GRPCOpRecvStatus alloc] initWithHandler:completionHandler] ]];
+  });
+}
+
+- (void)invokeCall {
+  __weak GRPCCall *weakSelf = self;
+  [self invokeCallWithHeadersHandler:^(NSDictionary *headers) {
+    // Response headers received.
+    __strong GRPCCall *strongSelf = weakSelf;
+    if (strongSelf) {
+      @synchronized(strongSelf) {
+        // it is ok to set nil because headers are only received once
+        strongSelf.responseHeaders = nil;
+        // copy the header so that the GRPCOpRecvMetadata object may be dealloc'ed
+        NSDictionary *copiedHeaders =
+            [[NSDictionary alloc] initWithDictionary:headers copyItems:YES];
+        strongSelf.responseHeaders = copiedHeaders;
+        strongSelf->_pendingCoreRead = NO;
+        [strongSelf maybeStartNextRead];
+      }
+    }
+  }
+      completionHandler:^(NSError *error, NSDictionary *trailers) {
+        __strong GRPCCall *strongSelf = weakSelf;
+        if (strongSelf) {
+          strongSelf.responseTrailers = trailers;
+
+          if (error) {
+            NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
+            if (error.userInfo) {
+              [userInfo addEntriesFromDictionary:error.userInfo];
+            }
+            userInfo[kGRPCTrailersKey] = strongSelf.responseTrailers;
+            // Since gRPC core does not guarantee the headers block being called before this block,
+            // responseHeaders might be nil.
+            userInfo[kGRPCHeadersKey] = strongSelf.responseHeaders;
+            error = [NSError errorWithDomain:error.domain code:error.code userInfo:userInfo];
+          }
+          [strongSelf finishWithError:error];
+          strongSelf->_requestWriter.state = GRXWriterStateFinished;
+        }
+      }];
+}
+
+#pragma mark GRXWriter implementation
+
+// Lock acquired inside startWithWriteable:
+- (void)startCallWithWriteable:(id<GRXWriteable>)writeable {
+  @synchronized(self) {
+    if (_state == GRXWriterStateFinished) {
+      return;
+    }
+
+    _responseWriteable =
+        [[GRXConcurrentWriteable alloc] initWithWriteable:writeable dispatchQueue:_responseQueue];
+
+    GRPCPooledChannel *channel =
+        [[GRPCChannelPool sharedInstance] channelWithHost:_host callOptions:_callOptions];
+    _wrappedCall = [channel wrappedCallWithPath:_path
+                                completionQueue:[GRPCCompletionQueue completionQueue]
+                                    callOptions:_callOptions];
+
+    if (_wrappedCall == nil) {
+      [self finishWithError:[NSError errorWithDomain:kGRPCErrorDomain
+                                                code:GRPCErrorCodeUnavailable
+                                            userInfo:@{
+                                              NSLocalizedDescriptionKey :
+                                                  @"Failed to create call or channel."
+                                            }]];
+      return;
+    }
+
+    [self sendHeaders];
+    [self invokeCall];
+  }
+
+  // Now that the RPC has been initiated, request writes can start.
+  [_requestWriter startWithWriteable:self];
+}
+
+- (void)startWithWriteable:(id<GRXWriteable>)writeable {
+  id<GRPCAuthorizationProtocol> tokenProvider = nil;
+  @synchronized(self) {
+    _state = GRXWriterStateStarted;
+
+    // Create a retain cycle so that this instance lives until the RPC finishes (or is cancelled).
+    // This makes RPCs in which the call isn't externally retained possible (as long as it is
+    // started before being autoreleased). Care is taken not to retain self strongly in any of the
+    // blocks used in this implementation, so that the life of the instance is determined by this
+    // retain cycle.
+    _retainSelf = self;
+
+    // If _callOptions is nil, people must be using the deprecated v1 interface. In this case,
+    // generate the call options from the corresponding GRPCHost configs and apply other options
+    // that are not covered by GRPCHost.
+    if (_callOptions == nil) {
+      GRPCMutableCallOptions *callOptions = [[GRPCHost callOptionsForHost:_host] mutableCopy];
+      if (_serverName.length != 0) {
+        callOptions.serverAuthority = _serverName;
+      }
+      if (_timeout > 0) {
+        callOptions.timeout = _timeout;
+      }
+      uint32_t callFlags = [GRPCCall callFlagsForHost:_host path:_path];
+      if (callFlags != 0) {
+        if (callFlags == GRPC_INITIAL_METADATA_IDEMPOTENT_REQUEST) {
+          _callSafety = GRPCCallSafetyIdempotentRequest;
+        } else if (callFlags == GRPC_INITIAL_METADATA_CACHEABLE_REQUEST) {
+          _callSafety = GRPCCallSafetyCacheableRequest;
+        }
+      }
+
+      id<GRPCAuthorizationProtocol> tokenProvider = self.tokenProvider;
+      if (tokenProvider != nil) {
+        callOptions.authTokenProvider = tokenProvider;
+      }
+      _callOptions = callOptions;
+    }
+
+    NSAssert(_callOptions.authTokenProvider == nil || _callOptions.oauth2AccessToken == nil,
+             @"authTokenProvider and oauth2AccessToken cannot be set at the same time");
+
+    tokenProvider = _callOptions.authTokenProvider;
+  }
+
+  if (tokenProvider != nil) {
+    __weak typeof(self) weakSelf = self;
+    [tokenProvider getTokenWithHandler:^(NSString *token) {
+      __strong typeof(self) strongSelf = weakSelf;
+      if (strongSelf) {
+        BOOL startCall = NO;
+        @synchronized(strongSelf) {
+          if (strongSelf->_state != GRXWriterStateFinished) {
+            startCall = YES;
+            if (token) {
+              strongSelf->_fetchedOauth2AccessToken = [token copy];
+            }
+          }
+        }
+        if (startCall) {
+          [strongSelf startCallWithWriteable:writeable];
+        }
+      }
+    }];
+  } else {
+    [self startCallWithWriteable:writeable];
+  }
+}
+
+- (void)setState:(GRXWriterState)newState {
+  @synchronized(self) {
+    // Manual transitions are only allowed from the started or paused states.
+    if (_state == GRXWriterStateNotStarted || _state == GRXWriterStateFinished) {
+      return;
+    }
+
+    switch (newState) {
+      case GRXWriterStateFinished:
+        _state = newState;
+        // Per GRXWriter's contract, setting the state to Finished manually
+        // means one doesn't wish the writeable to be messaged anymore.
+        [_responseWriteable cancelSilently];
+        _responseWriteable = nil;
+        return;
+      case GRXWriterStatePaused:
+        _state = newState;
+        return;
+      case GRXWriterStateStarted:
+        if (_state == GRXWriterStatePaused) {
+          _state = newState;
+          [self maybeStartNextRead];
+        }
+        return;
+      case GRXWriterStateNotStarted:
+        return;
+    }
+  }
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.h b/src/objective-c/GRPCClient/GRPCCallOptions.h
index 98511e3f5cb3d00277a494e8bb67906e92d76ff9..e4261b5b5f9dec7adbc9b80a3352bc5cddb37129 100644
--- a/src/objective-c/GRPCClient/GRPCCallOptions.h
+++ b/src/objective-c/GRPCClient/GRPCCallOptions.h
@@ -18,57 +18,11 @@
 
 #import <Foundation/Foundation.h>
 
-NS_ASSUME_NONNULL_BEGIN
-
-/**
- * Safety remark of a gRPC method as defined in RFC 2616 Section 9.1
- */
-typedef NS_ENUM(NSUInteger, GRPCCallSafety) {
-  /** Signal that there is no guarantees on how the call affects the server state. */
-  GRPCCallSafetyDefault = 0,
-  /** Signal that the call is idempotent. gRPC is free to use PUT verb. */
-  GRPCCallSafetyIdempotentRequest = 1,
-  /**
-   * Signal that the call is cacheable and will not affect server state. gRPC is free to use GET
-   * verb.
-   */
-  GRPCCallSafetyCacheableRequest = 2,
-};
-
-// Compression algorithm to be used by a gRPC call
-typedef NS_ENUM(NSUInteger, GRPCCompressionAlgorithm) {
-  GRPCCompressNone = 0,
-  GRPCCompressDeflate,
-  GRPCCompressGzip,
-  GRPCStreamCompressGzip,
-};
-
-// GRPCCompressAlgorithm is deprecated; use GRPCCompressionAlgorithm
-typedef GRPCCompressionAlgorithm GRPCCompressAlgorithm;
+#import "GRPCTypes.h"
 
-/** The transport to be used by a gRPC call */
-typedef NS_ENUM(NSUInteger, GRPCTransportType) {
-  GRPCTransportTypeDefault = 0,
-  /** gRPC internal HTTP/2 stack with BoringSSL */
-  GRPCTransportTypeChttp2BoringSSL = 0,
-  /** Cronet stack */
-  GRPCTransportTypeCronet,
-  /** Insecure channel. FOR TEST ONLY! */
-  GRPCTransportTypeInsecure,
-};
-
-/**
- * Implement this protocol to provide a token to gRPC when a call is initiated.
- */
-@protocol GRPCAuthorizationProtocol
-
-/**
- * This method is called when gRPC is about to start the call. When OAuth token is acquired,
- * \a handler is expected to be called with \a token being the new token to be used for this call.
- */
-- (void)getTokenWithHandler:(void (^)(NSString *_Nullable token))handler;
+NS_ASSUME_NONNULL_BEGIN
 
-@end
+@protocol GRPCInterceptorFactory;
 
 @interface GRPCCallOptions : NSObject<NSCopying, NSMutableCopying>
 
@@ -104,7 +58,7 @@ typedef NS_ENUM(NSUInteger, GRPCTransportType) {
  * this array. This parameter should not be modified by any interceptor and will
  * not take effect if done so.
  */
-@property(copy, readonly) NSArray *interceptorFactories;
+@property(copy, readonly) NSArray<id<GRPCInterceptorFactory>> *interceptorFactories;
 
 // OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
 
@@ -192,10 +146,23 @@ typedef NS_ENUM(NSUInteger, GRPCTransportType) {
 @property(copy, readonly, nullable) NSString *PEMCertificateChain;
 
 /**
+ * Deprecated: this option is deprecated. Please use the property \a transport
+ * instead.
+ *
  * Select the transport type to be used for this call.
  */
 @property(readonly) GRPCTransportType transportType;
 
+/**
+ * The transport to be used for this call. Users may choose a native transport
+ * identifier defined in \a GRPCTransport or provided by a non-native transport
+ * implementation. If the option is left to be NULL, gRPC will use its default
+ * transport.
+ *
+ * This is currently an experimental option.
+ */
+@property(readonly) GRPCTransportId transport;
+
 /**
  * Override the hostname during the TLS hostname validation process.
  */
@@ -267,7 +234,7 @@ typedef NS_ENUM(NSUInteger, GRPCTransportType) {
  * this array. This parameter should not be modified by any interceptor and will
  * not take effect if done so.
  */
-@property(copy, readwrite) NSArray *interceptorFactories;
+@property(copy, readwrite) NSArray<id<GRPCInterceptorFactory>> *interceptorFactories;
 
 // OAuth2 parameters. Users of gRPC may specify one of the following two parameters.
 
@@ -357,10 +324,23 @@ typedef NS_ENUM(NSUInteger, GRPCTransportType) {
 @property(copy, readwrite, nullable) NSString *PEMCertificateChain;
 
 /**
+ * Deprecated: this option is deprecated. Please use the property \a transport
+ * instead.
+ *
  * Select the transport type to be used for this call.
  */
 @property(readwrite) GRPCTransportType transportType;
 
+/**
+ * The transport to be used for this call. Users may choose a native transport
+ * identifier defined in \a GRPCTransport or provided by a non-native ttransport
+ * implementation. If the option is left to be NULL, gRPC will use its default
+ * transport.
+ *
+ * An interceptor must not change the value of this option.
+ */
+@property(readwrite) GRPCTransportId transport;
+
 /**
  * Override the hostname during the TLS hostname validation process.
  */
diff --git a/src/objective-c/GRPCClient/GRPCCallOptions.m b/src/objective-c/GRPCClient/GRPCCallOptions.m
index 392e42a9d47d2a2884a2f6db7b8e8409a80ccd7f..7f88098eb6fe1856edfdd4ec5c50493f492b5baa 100644
--- a/src/objective-c/GRPCClient/GRPCCallOptions.m
+++ b/src/objective-c/GRPCClient/GRPCCallOptions.m
@@ -17,13 +17,14 @@
  */
 
 #import "GRPCCallOptions.h"
+#import "GRPCTransport.h"
 #import "internal/GRPCCallOptions+Internal.h"
 
 // The default values for the call options.
 static NSString *const kDefaultServerAuthority = nil;
 static const NSTimeInterval kDefaultTimeout = 0;
 static const BOOL kDefaultFlowControlEnabled = NO;
-static NSArray *const kDefaultInterceptorFactories = nil;
+static NSArray<id<GRPCInterceptorFactory>> *const kDefaultInterceptorFactories = nil;
 static NSDictionary *const kDefaultInitialMetadata = nil;
 static NSString *const kDefaultUserAgentPrefix = nil;
 static const NSUInteger kDefaultResponseSizeLimit = 0;
@@ -41,6 +42,7 @@ static NSString *const kDefaultPEMCertificateChain = nil;
 static NSString *const kDefaultOauth2AccessToken = nil;
 static const id<GRPCAuthorizationProtocol> kDefaultAuthTokenProvider = nil;
 static const GRPCTransportType kDefaultTransportType = GRPCTransportTypeChttp2BoringSSL;
+static const GRPCTransportId kDefaultTransport = NULL;
 static NSString *const kDefaultHostNameOverride = nil;
 static const id kDefaultLogContext = nil;
 static NSString *const kDefaultChannelPoolDomain = nil;
@@ -62,7 +64,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   NSString *_serverAuthority;
   NSTimeInterval _timeout;
   BOOL _flowControlEnabled;
-  NSArray *_interceptorFactories;
+  NSArray<id<GRPCInterceptorFactory>> *_interceptorFactories;
   NSString *_oauth2AccessToken;
   id<GRPCAuthorizationProtocol> _authTokenProvider;
   NSDictionary *_initialMetadata;
@@ -80,6 +82,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   NSString *_PEMPrivateKey;
   NSString *_PEMCertificateChain;
   GRPCTransportType _transportType;
+  GRPCTransportId _transport;
   NSString *_hostNameOverride;
   id<NSObject> _logContext;
   NSString *_channelPoolDomain;
@@ -107,6 +110,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
 @synthesize PEMPrivateKey = _PEMPrivateKey;
 @synthesize PEMCertificateChain = _PEMCertificateChain;
 @synthesize transportType = _transportType;
+@synthesize transport = _transport;
 @synthesize hostNameOverride = _hostNameOverride;
 @synthesize logContext = _logContext;
 @synthesize channelPoolDomain = _channelPoolDomain;
@@ -134,6 +138,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
                          PEMPrivateKey:kDefaultPEMPrivateKey
                    PEMCertificateChain:kDefaultPEMCertificateChain
                          transportType:kDefaultTransportType
+                             transport:kDefaultTransport
                       hostNameOverride:kDefaultHostNameOverride
                             logContext:kDefaultLogContext
                      channelPoolDomain:kDefaultChannelPoolDomain
@@ -143,7 +148,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
 - (instancetype)initWithServerAuthority:(NSString *)serverAuthority
                                 timeout:(NSTimeInterval)timeout
                      flowControlEnabled:(BOOL)flowControlEnabled
-                   interceptorFactories:(NSArray *)interceptorFactories
+                   interceptorFactories:(NSArray<id<GRPCInterceptorFactory>> *)interceptorFactories
                       oauth2AccessToken:(NSString *)oauth2AccessToken
                       authTokenProvider:(id<GRPCAuthorizationProtocol>)authTokenProvider
                         initialMetadata:(NSDictionary *)initialMetadata
@@ -161,6 +166,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
                           PEMPrivateKey:(NSString *)PEMPrivateKey
                     PEMCertificateChain:(NSString *)PEMCertificateChain
                           transportType:(GRPCTransportType)transportType
+                              transport:(GRPCTransportId)transport
                        hostNameOverride:(NSString *)hostNameOverride
                              logContext:(id)logContext
                       channelPoolDomain:(NSString *)channelPoolDomain
@@ -193,6 +199,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
     _PEMPrivateKey = [PEMPrivateKey copy];
     _PEMCertificateChain = [PEMCertificateChain copy];
     _transportType = transportType;
+    _transport = transport;
     _hostNameOverride = [hostNameOverride copy];
     _logContext = logContext;
     _channelPoolDomain = [channelPoolDomain copy];
@@ -224,6 +231,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
                                                       PEMPrivateKey:_PEMPrivateKey
                                                 PEMCertificateChain:_PEMCertificateChain
                                                       transportType:_transportType
+                                                          transport:_transport
                                                    hostNameOverride:_hostNameOverride
                                                          logContext:_logContext
                                                   channelPoolDomain:_channelPoolDomain
@@ -256,6 +264,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
                 PEMPrivateKey:[_PEMPrivateKey copy]
           PEMCertificateChain:[_PEMCertificateChain copy]
                 transportType:_transportType
+                    transport:_transport
              hostNameOverride:[_hostNameOverride copy]
                    logContext:_logContext
             channelPoolDomain:[_channelPoolDomain copy]
@@ -280,6 +289,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   if (!areObjectsEqual(callOptions.PEMCertificateChain, _PEMCertificateChain)) return NO;
   if (!areObjectsEqual(callOptions.hostNameOverride, _hostNameOverride)) return NO;
   if (!(callOptions.transportType == _transportType)) return NO;
+  if (!(TransportIdIsEqual(callOptions.transport, _transport))) return NO;
   if (!areObjectsEqual(callOptions.logContext, _logContext)) return NO;
   if (!areObjectsEqual(callOptions.channelPoolDomain, _channelPoolDomain)) return NO;
   if (!(callOptions.channelID == _channelID)) return NO;
@@ -304,6 +314,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   result ^= _PEMCertificateChain.hash;
   result ^= _hostNameOverride.hash;
   result ^= _transportType;
+  result ^= TransportIdHash(_transport);
   result ^= _logContext.hash;
   result ^= _channelPoolDomain.hash;
   result ^= _channelID;
@@ -336,6 +347,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
 @dynamic PEMPrivateKey;
 @dynamic PEMCertificateChain;
 @dynamic transportType;
+@dynamic transport;
 @dynamic hostNameOverride;
 @dynamic logContext;
 @dynamic channelPoolDomain;
@@ -363,6 +375,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
                          PEMPrivateKey:kDefaultPEMPrivateKey
                    PEMCertificateChain:kDefaultPEMCertificateChain
                          transportType:kDefaultTransportType
+                             transport:kDefaultTransport
                       hostNameOverride:kDefaultHostNameOverride
                             logContext:kDefaultLogContext
                      channelPoolDomain:kDefaultChannelPoolDomain
@@ -392,6 +405,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
                                                       PEMPrivateKey:_PEMPrivateKey
                                                 PEMCertificateChain:_PEMCertificateChain
                                                       transportType:_transportType
+                                                          transport:_transport
                                                    hostNameOverride:_hostNameOverride
                                                          logContext:_logContext
                                                   channelPoolDomain:_channelPoolDomain
@@ -422,6 +436,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
                 PEMPrivateKey:_PEMPrivateKey
           PEMCertificateChain:_PEMCertificateChain
                 transportType:_transportType
+                    transport:_transport
              hostNameOverride:_hostNameOverride
                    logContext:_logContext
             channelPoolDomain:_channelPoolDomain
@@ -445,7 +460,7 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   _flowControlEnabled = flowControlEnabled;
 }
 
-- (void)setInterceptorFactories:(NSArray *)interceptorFactories {
+- (void)setInterceptorFactories:(NSArray<id<GRPCInterceptorFactory>> *)interceptorFactories {
   _interceptorFactories = interceptorFactories;
 }
 
@@ -538,6 +553,10 @@ static BOOL areObjectsEqual(id obj1, id obj2) {
   _transportType = transportType;
 }
 
+- (void)setTransport:(GRPCTransportId)transport {
+  _transport = transport;
+}
+
 - (void)setHostNameOverride:(NSString *)hostNameOverride {
   _hostNameOverride = [hostNameOverride copy];
 }
diff --git a/src/objective-c/GRPCClient/GRPCDispatchable.h b/src/objective-c/GRPCClient/GRPCDispatchable.h
new file mode 100644
index 0000000000000000000000000000000000000000..650103603d4304e599d6e6d9f3f4c0c3c8764cce
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCDispatchable.h
@@ -0,0 +1,30 @@
+
+/*
+ *
+ * Copyright 2019 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.
+ *
+ */
+
+/**
+ * An object that processes its methods with a dispatch queue.
+ */
+@protocol GRPCDispatchable
+
+/**
+ * The dispatch queue where the object's methods should be run on.
+ */
+@property(atomic, readonly) dispatch_queue_t dispatchQueue;
+
+@end
diff --git a/src/objective-c/GRPCClient/GRPCInterceptor.h b/src/objective-c/GRPCClient/GRPCInterceptor.h
index 3b62c1b3ec03fe07fe97da95c99a4464755e5bd9..509749769b333c4619d0236decd28bbe51e99619 100644
--- a/src/objective-c/GRPCClient/GRPCInterceptor.h
+++ b/src/objective-c/GRPCClient/GRPCInterceptor.h
@@ -106,22 +106,20 @@
  */
 
 #import "GRPCCall.h"
+#import "GRPCDispatchable.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
 @class GRPCInterceptorManager;
 @class GRPCInterceptor;
+@class GRPCRequestOptions;
+@class GRPCCallOptions;
+@protocol GRPCResponseHandler;
 
 /**
  * The GRPCInterceptorInterface defines the request events that can occur to an interceptr.
  */
-@protocol GRPCInterceptorInterface<NSObject>
-
-/**
- * The queue on which all methods of this interceptor should be dispatched on. The queue must be a
- * serial queue.
- */
-@property(readonly) dispatch_queue_t requestDispatchQueue;
+@protocol GRPCInterceptorInterface<NSObject, GRPCDispatchable>
 
 /**
  * To start the call. This method will only be called once for each instance.
@@ -171,19 +169,20 @@ NS_ASSUME_NONNULL_BEGIN
  * invoke shutDown method of its corresponding manager so that references to other interceptors can
  * be released.
  */
-@interface GRPCInterceptorManager : NSObject
+@interface GRPCInterceptorManager : NSObject<GRPCInterceptorInterface, GRPCResponseHandler>
 
 - (instancetype)init NS_UNAVAILABLE;
 
 + (instancetype) new NS_UNAVAILABLE;
 
-- (nullable instancetype)initWithNextInterceptor:(id<GRPCInterceptorInterface>)nextInterceptor
-    NS_DESIGNATED_INITIALIZER;
-
-/** Set the previous interceptor in the chain. Can only be set once. */
-- (void)setPreviousInterceptor:(id<GRPCResponseHandler>)previousInterceptor;
+- (nullable instancetype)initWithFactories:(nullable NSArray<id<GRPCInterceptorFactory>> *)factories
+                       previousInterceptor:(nullable id<GRPCResponseHandler>)previousInterceptor
+                               transportId:(GRPCTransportId)transportId;
 
-/** Indicate shutdown of the interceptor; release the reference to other interceptors */
+/**
+ * Notify the manager that the interceptor has shut down and the manager should release references
+ * to other interceptors and stop forwarding requests/responses.
+ */
 - (void)shutDown;
 
 // Methods to forward GRPCInterceptorInterface calls to the next interceptor
@@ -235,7 +234,6 @@ NS_ASSUME_NONNULL_BEGIN
 @interface GRPCInterceptor : NSObject<GRPCInterceptorInterface, GRPCResponseHandler>
 
 - (instancetype)init NS_UNAVAILABLE;
-
 + (instancetype) new NS_UNAVAILABLE;
 
 /**
@@ -243,9 +241,7 @@ NS_ASSUME_NONNULL_BEGIN
  * that this interceptor's methods are dispatched onto.
  */
 - (nullable instancetype)initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
-                               requestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
-                              responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
-    NS_DESIGNATED_INITIALIZER;
+                                      dispatchQueue:(dispatch_queue_t)dispatchQueue;
 
 // Default implementation of GRPCInterceptorInterface
 
diff --git a/src/objective-c/GRPCClient/GRPCInterceptor.m b/src/objective-c/GRPCClient/GRPCInterceptor.m
index a385ecd781330b5de3620d2ece66059b346e68ed..a7ffe05bddcd91d098cd7b8d6eed280af3b73f90 100644
--- a/src/objective-c/GRPCClient/GRPCInterceptor.m
+++ b/src/objective-c/GRPCClient/GRPCInterceptor.m
@@ -19,117 +19,253 @@
 #import <Foundation/Foundation.h>
 
 #import "GRPCInterceptor.h"
+#import "private/GRPCTransport+Private.h"
+
+@interface GRPCInterceptorManager ()<GRPCInterceptorInterface, GRPCResponseHandler>
+
+@end
 
 @implementation GRPCInterceptorManager {
   id<GRPCInterceptorInterface> _nextInterceptor;
   id<GRPCResponseHandler> _previousInterceptor;
+  GRPCInterceptor *_thisInterceptor;
+  dispatch_queue_t _dispatchQueue;
+  NSArray<id<GRPCInterceptorFactory>> *_factories;
+  GRPCTransportId _transportId;
+  BOOL _shutDown;
 }
 
-- (instancetype)initWithNextInterceptor:(id<GRPCInterceptorInterface>)nextInterceptor {
+- (instancetype)initWithFactories:(NSArray<id<GRPCInterceptorFactory>> *)factories
+              previousInterceptor:(id<GRPCResponseHandler>)previousInterceptor
+                      transportId:(nonnull GRPCTransportId)transportId {
   if ((self = [super init])) {
-    _nextInterceptor = nextInterceptor;
+    if (factories.count == 0) {
+      [NSException raise:NSInternalInconsistencyException
+                  format:@"Interceptor manager must have factories"];
+    }
+    _thisInterceptor = [factories[0] createInterceptorWithManager:self];
+    if (_thisInterceptor == nil) {
+      return nil;
+    }
+    _previousInterceptor = previousInterceptor;
+    _factories = factories;
+    // Generate interceptor
+#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
+    if (@available(iOS 8.0, macOS 10.10, *)) {
+      _dispatchQueue = dispatch_queue_create(
+          NULL,
+          dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
+    } else {
+#else
+    {
+#endif
+      _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+    }
+    dispatch_set_target_queue(_dispatchQueue, _thisInterceptor.dispatchQueue);
+    _transportId = transportId;
   }
-
   return self;
 }
 
-- (void)setPreviousInterceptor:(id<GRPCResponseHandler>)previousInterceptor {
-  _previousInterceptor = previousInterceptor;
+- (void)shutDown {
+  dispatch_async(_dispatchQueue, ^{
+    self->_nextInterceptor = nil;
+    self->_previousInterceptor = nil;
+    self->_thisInterceptor = nil;
+    self->_shutDown = YES;
+  });
 }
 
-- (void)shutDown {
-  _nextInterceptor = nil;
-  _previousInterceptor = nil;
+- (void)createNextInterceptor {
+  NSAssert(_nextInterceptor == nil, @"Starting the next interceptor more than once");
+  NSAssert(_factories.count > 0, @"Interceptor manager of transport cannot start next interceptor");
+  if (_nextInterceptor != nil) {
+    NSLog(@"Starting the next interceptor more than once");
+    return;
+  }
+  NSMutableArray<id<GRPCInterceptorFactory>> *interceptorFactories = [NSMutableArray
+      arrayWithArray:[_factories subarrayWithRange:NSMakeRange(1, _factories.count - 1)]];
+  while (_nextInterceptor == nil) {
+    if (interceptorFactories.count == 0) {
+      _nextInterceptor =
+          [[GRPCTransportManager alloc] initWithTransportId:_transportId previousInterceptor:self];
+      break;
+    } else {
+      _nextInterceptor = [[GRPCInterceptorManager alloc] initWithFactories:interceptorFactories
+                                                       previousInterceptor:self
+                                                               transportId:_transportId];
+      if (_nextInterceptor == nil) {
+        [interceptorFactories removeObjectAtIndex:0];
+      }
+    }
+  }
+  NSAssert(_nextInterceptor != nil, @"Failed to create interceptor or transport.");
+  if (_nextInterceptor == nil) {
+    NSLog(@"Failed to create interceptor or transport.");
+  }
 }
 
 - (void)startNextInterceptorWithRequest:(GRPCRequestOptions *)requestOptions
                             callOptions:(GRPCCallOptions *)callOptions {
-  if (_nextInterceptor != nil) {
-    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
-    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
-      [copiedNextInterceptor startWithRequestOptions:requestOptions callOptions:callOptions];
-    });
+  if (_nextInterceptor == nil && !_shutDown) {
+    [self createNextInterceptor];
   }
+  if (_nextInterceptor == nil) {
+    return;
+  }
+  id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+  dispatch_async(copiedNextInterceptor.dispatchQueue, ^{
+    [copiedNextInterceptor startWithRequestOptions:requestOptions callOptions:callOptions];
+  });
 }
 
 - (void)writeNextInterceptorWithData:(id)data {
-  if (_nextInterceptor != nil) {
-    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
-    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
-      [copiedNextInterceptor writeData:data];
-    });
+  if (_nextInterceptor == nil && !_shutDown) {
+    [self createNextInterceptor];
+  }
+  if (_nextInterceptor == nil) {
+    return;
   }
+  id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+  dispatch_async(copiedNextInterceptor.dispatchQueue, ^{
+    [copiedNextInterceptor writeData:data];
+  });
 }
 
 - (void)finishNextInterceptor {
-  if (_nextInterceptor != nil) {
-    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
-    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
-      [copiedNextInterceptor finish];
-    });
+  if (_nextInterceptor == nil && !_shutDown) {
+    [self createNextInterceptor];
+  }
+  if (_nextInterceptor == nil) {
+    return;
   }
+  id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+  dispatch_async(copiedNextInterceptor.dispatchQueue, ^{
+    [copiedNextInterceptor finish];
+  });
 }
 
 - (void)cancelNextInterceptor {
-  if (_nextInterceptor != nil) {
-    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
-    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
-      [copiedNextInterceptor cancel];
-    });
+  if (_nextInterceptor == nil && !_shutDown) {
+    [self createNextInterceptor];
+  }
+  if (_nextInterceptor == nil) {
+    return;
   }
+  id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+  dispatch_async(copiedNextInterceptor.dispatchQueue, ^{
+    [copiedNextInterceptor cancel];
+  });
 }
 
 /** Notify the next interceptor in the chain to receive more messages */
 - (void)receiveNextInterceptorMessages:(NSUInteger)numberOfMessages {
-  if (_nextInterceptor != nil) {
-    id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
-    dispatch_async(copiedNextInterceptor.requestDispatchQueue, ^{
-      [copiedNextInterceptor receiveNextMessages:numberOfMessages];
-    });
+  if (_nextInterceptor == nil && !_shutDown) {
+    [self createNextInterceptor];
+  }
+  if (_nextInterceptor == nil) {
+    return;
   }
+  id<GRPCInterceptorInterface> copiedNextInterceptor = _nextInterceptor;
+  dispatch_async(copiedNextInterceptor.dispatchQueue, ^{
+    [copiedNextInterceptor receiveNextMessages:numberOfMessages];
+  });
 }
 
 // Methods to forward GRPCResponseHandler callbacks to the previous object
 
 /** Forward initial metadata to the previous interceptor in the chain */
-- (void)forwardPreviousInterceptorWithInitialMetadata:(nullable NSDictionary *)initialMetadata {
-  if ([_previousInterceptor respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
-    id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
-    dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
-      [copiedPreviousInterceptor didReceiveInitialMetadata:initialMetadata];
-    });
+- (void)forwardPreviousInterceptorWithInitialMetadata:(NSDictionary *)initialMetadata {
+  if (_previousInterceptor == nil) {
+    return;
   }
+  id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+  dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+    [copiedPreviousInterceptor didReceiveInitialMetadata:initialMetadata];
+  });
 }
 
 /** Forward a received message to the previous interceptor in the chain */
 - (void)forwardPreviousInterceptorWithData:(id)data {
-  if ([_previousInterceptor respondsToSelector:@selector(didReceiveData:)]) {
-    id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
-    dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
-      [copiedPreviousInterceptor didReceiveData:data];
-    });
+  if (_previousInterceptor == nil) {
+    return;
   }
+  id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+  dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+    [copiedPreviousInterceptor didReceiveData:data];
+  });
 }
 
 /** Forward call close and trailing metadata to the previous interceptor in the chain */
-- (void)forwardPreviousInterceptorCloseWithTrailingMetadata:
-            (nullable NSDictionary *)trailingMetadata
-                                                      error:(nullable NSError *)error {
-  if ([_previousInterceptor respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
-    id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
-    dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
-      [copiedPreviousInterceptor didCloseWithTrailingMetadata:trailingMetadata error:error];
-    });
+- (void)forwardPreviousInterceptorCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata
+                                                      error:(NSError *)error {
+  if (_previousInterceptor == nil) {
+    return;
   }
+  id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+  dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+    [copiedPreviousInterceptor didCloseWithTrailingMetadata:trailingMetadata error:error];
+  });
 }
 
 /** Forward write completion to the previous interceptor in the chain */
 - (void)forwardPreviousInterceptorDidWriteData {
-  if ([_previousInterceptor respondsToSelector:@selector(didWriteData)]) {
-    id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
-    dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
-      [copiedPreviousInterceptor didWriteData];
-    });
+  if (_previousInterceptor == nil) {
+    return;
+  }
+  id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+  dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+    [copiedPreviousInterceptor didWriteData];
+  });
+}
+
+- (dispatch_queue_t)dispatchQueue {
+  return _dispatchQueue;
+}
+
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions {
+  [_thisInterceptor startWithRequestOptions:requestOptions callOptions:callOptions];
+}
+
+- (void)writeData:(id)data {
+  [_thisInterceptor writeData:data];
+}
+
+- (void)finish {
+  [_thisInterceptor finish];
+}
+
+- (void)cancel {
+  [_thisInterceptor cancel];
+}
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  [_thisInterceptor receiveNextMessages:numberOfMessages];
+}
+
+- (void)didReceiveInitialMetadata:(nullable NSDictionary *)initialMetadata {
+  if ([_thisInterceptor respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
+    [_thisInterceptor didReceiveInitialMetadata:initialMetadata];
+  }
+}
+
+- (void)didReceiveData:(id)data {
+  if ([_thisInterceptor respondsToSelector:@selector(didReceiveData:)]) {
+    [_thisInterceptor didReceiveData:data];
+  }
+}
+
+- (void)didCloseWithTrailingMetadata:(nullable NSDictionary *)trailingMetadata
+                               error:(nullable NSError *)error {
+  if ([_thisInterceptor respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
+    [_thisInterceptor didCloseWithTrailingMetadata:trailingMetadata error:error];
+  }
+}
+
+- (void)didWriteData {
+  if ([_thisInterceptor respondsToSelector:@selector(didWriteData)]) {
+    [_thisInterceptor didWriteData];
   }
 }
 
@@ -137,28 +273,21 @@
 
 @implementation GRPCInterceptor {
   GRPCInterceptorManager *_manager;
-  dispatch_queue_t _requestDispatchQueue;
-  dispatch_queue_t _responseDispatchQueue;
+  dispatch_queue_t _dispatchQueue;
 }
 
 - (instancetype)initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
-                      requestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
-                     responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue {
+                             dispatchQueue:(dispatch_queue_t)dispatchQueue {
   if ((self = [super init])) {
     _manager = interceptorManager;
-    _requestDispatchQueue = requestDispatchQueue;
-    _responseDispatchQueue = responseDispatchQueue;
+    _dispatchQueue = dispatchQueue;
   }
 
   return self;
 }
 
-- (dispatch_queue_t)requestDispatchQueue {
-  return _requestDispatchQueue;
-}
-
 - (dispatch_queue_t)dispatchQueue {
-  return _responseDispatchQueue;
+  return _dispatchQueue;
 }
 
 - (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
diff --git a/src/objective-c/GRPCClient/GRPCTransport.h b/src/objective-c/GRPCClient/GRPCTransport.h
new file mode 100644
index 0000000000000000000000000000000000000000..d5637922152bb64b861314b96979e04ca39a8f02
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCTransport.h
@@ -0,0 +1,82 @@
+/*
+ *
+ * Copyright 2019 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.
+ *
+ */
+
+// The interface for a transport implementation
+
+#import "GRPCInterceptor.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+#pragma mark Transport ID
+
+/**
+ * The default transport implementations available in gRPC. These implementations will be provided
+ * by gRPC by default unless explicitly excluded.
+ */
+extern const struct GRPCDefaultTransportImplList {
+  const GRPCTransportId core_secure;
+  const GRPCTransportId core_insecure;
+} GRPCDefaultTransportImplList;
+
+/** Returns whether two transport id's are identical. */
+BOOL TransportIdIsEqual(GRPCTransportId lhs, GRPCTransportId rhs);
+
+/** Returns the hash value of a transport id. */
+NSUInteger TransportIdHash(GRPCTransportId);
+
+#pragma mark Transport and factory
+
+@protocol GRPCInterceptorInterface;
+@protocol GRPCResponseHandler;
+@class GRPCTransportManager;
+@class GRPCRequestOptions;
+@class GRPCCallOptions;
+@class GRPCTransport;
+
+/** The factory method to create a transport. */
+@protocol GRPCTransportFactory<NSObject>
+
+- (GRPCTransport *)createTransportWithManager:(GRPCTransportManager *)transportManager;
+
+@end
+
+/** The registry of transport implementations. */
+@interface GRPCTransportRegistry : NSObject
+
++ (instancetype)sharedInstance;
+
+/**
+ * Register a transport implementation with the registry. All transport implementations to be used
+ * in a process must register with the registry on process start-up in its +load: class method.
+ * Parameter \a transportId is the identifier of the implementation, and \a factory is the factory
+ * object to create the corresponding transport instance.
+ */
+- (void)registerTransportWithId:(GRPCTransportId)transportId
+                        factory:(id<GRPCTransportFactory>)factory;
+
+@end
+
+/**
+ * Base class for transport implementations. All transport implementation should inherit from this
+ * class.
+ */
+@interface GRPCTransport : NSObject<GRPCInterceptorInterface>
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/GRPCTransport.m b/src/objective-c/GRPCClient/GRPCTransport.m
new file mode 100644
index 0000000000000000000000000000000000000000..439acfb9cc231a3d2b51bbd48bc4882b8d062123
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCTransport.m
@@ -0,0 +1,142 @@
+/*
+ *
+ * Copyright 2019 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 "GRPCTransport.h"
+
+static const GRPCTransportId gGRPCCoreSecureId = "io.grpc.transport.core.secure";
+static const GRPCTransportId gGRPCCoreInsecureId = "io.grpc.transport.core.insecure";
+
+const struct GRPCDefaultTransportImplList GRPCDefaultTransportImplList = {
+    .core_secure = gGRPCCoreSecureId, .core_insecure = gGRPCCoreInsecureId};
+
+static const GRPCTransportId gDefaultTransportId = gGRPCCoreSecureId;
+
+static GRPCTransportRegistry *gTransportRegistry = nil;
+static dispatch_once_t initTransportRegistry;
+
+BOOL TransportIdIsEqual(GRPCTransportId lhs, GRPCTransportId rhs) {
+  // Directly comparing pointers works because we require users to use the id provided by each
+  // implementation, not coming up with their own string.
+  return lhs == rhs;
+}
+
+NSUInteger TransportIdHash(GRPCTransportId transportId) {
+  if (transportId == NULL) {
+    transportId = gDefaultTransportId;
+  }
+  return [NSString stringWithCString:transportId encoding:NSUTF8StringEncoding].hash;
+}
+
+@implementation GRPCTransportRegistry {
+  NSMutableDictionary<NSString *, id<GRPCTransportFactory>> *_registry;
+  id<GRPCTransportFactory> _defaultFactory;
+}
+
++ (instancetype)sharedInstance {
+  dispatch_once(&initTransportRegistry, ^{
+    gTransportRegistry = [[GRPCTransportRegistry alloc] init];
+    NSAssert(gTransportRegistry != nil, @"Unable to initialize transport registry.");
+    if (gTransportRegistry == nil) {
+      NSLog(@"Unable to initialize transport registry.");
+      [NSException raise:NSGenericException format:@"Unable to initialize transport registry."];
+    }
+  });
+  return gTransportRegistry;
+}
+
+- (instancetype)init {
+  if ((self = [super init])) {
+    _registry = [NSMutableDictionary dictionary];
+  }
+  return self;
+}
+
+- (void)registerTransportWithId:(GRPCTransportId)transportId
+                        factory:(id<GRPCTransportFactory>)factory {
+  NSString *nsTransportId = [NSString stringWithCString:transportId encoding:NSUTF8StringEncoding];
+  NSAssert(_registry[nsTransportId] == nil, @"The transport %@ has already been registered.",
+           nsTransportId);
+  if (_registry[nsTransportId] != nil) {
+    NSLog(@"The transport %@ has already been registered.", nsTransportId);
+    return;
+  }
+  _registry[nsTransportId] = factory;
+
+  // if the default transport is registered, mark it.
+  if (0 == strcmp(transportId, gDefaultTransportId)) {
+    _defaultFactory = factory;
+  }
+}
+
+- (id<GRPCTransportFactory>)getTransportFactoryWithId:(GRPCTransportId)transportId {
+  if (transportId == NULL) {
+    if (_defaultFactory == nil) {
+      [NSException raise:NSInvalidArgumentException
+                  format:@"Unable to get default transport factory"];
+      return nil;
+    }
+    return _defaultFactory;
+  }
+  NSString *nsTransportId = [NSString stringWithCString:transportId encoding:NSUTF8StringEncoding];
+  id<GRPCTransportFactory> transportFactory = _registry[nsTransportId];
+  if (transportFactory == nil) {
+    // User named a transport id that was not registered with the registry.
+    [NSException raise:NSInvalidArgumentException
+                format:@"Unable to get transport factory with id %s", transportId];
+    return nil;
+  }
+  return transportFactory;
+}
+
+@end
+
+@implementation GRPCTransport
+
+- (dispatch_queue_t)dispatchQueue {
+  [NSException raise:NSGenericException
+              format:@"Implementations should override the dispatch queue"];
+  return nil;
+}
+
+- (void)startWithRequestOptions:(nonnull GRPCRequestOptions *)requestOptions
+                    callOptions:(nonnull GRPCCallOptions *)callOptions {
+  [NSException raise:NSGenericException
+              format:@"Implementations should override the methods of GRPCTransport"];
+}
+
+- (void)writeData:(nonnull id)data {
+  [NSException raise:NSGenericException
+              format:@"Implementations should override the methods of GRPCTransport"];
+}
+
+- (void)cancel {
+  [NSException raise:NSGenericException
+              format:@"Implementations should override the methods of GRPCTransport"];
+}
+
+- (void)finish {
+  [NSException raise:NSGenericException
+              format:@"Implementations should override the methods of GRPCTransport"];
+}
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  [NSException raise:NSGenericException
+              format:@"Implementations should override the methods of GRPCTransport"];
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/GRPCTypes.h b/src/objective-c/GRPCClient/GRPCTypes.h
new file mode 100644
index 0000000000000000000000000000000000000000..c804bca4eaaf0a6845106abd749562282c906ad4
--- /dev/null
+++ b/src/objective-c/GRPCClient/GRPCTypes.h
@@ -0,0 +1,187 @@
+/*
+ *
+ * Copyright 2019 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.
+ *
+ */
+
+/**
+ * gRPC error codes.
+ * Note that a few of these are never produced by the gRPC libraries, but are of
+ * general utility for server applications to produce.
+ */
+typedef NS_ENUM(NSUInteger, GRPCErrorCode) {
+  /** The operation was cancelled (typically by the caller). */
+  GRPCErrorCodeCancelled = 1,
+
+  /**
+   * Unknown error. Errors raised by APIs that do not return enough error
+   * information may be converted to this error.
+   */
+  GRPCErrorCodeUnknown = 2,
+
+  /**
+   * The client specified an invalid argument. Note that this differs from
+   * FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
+   * problematic regardless of the state of the server (e.g., a malformed file
+   * name).
+   */
+  GRPCErrorCodeInvalidArgument = 3,
+
+  /**
+   * Deadline expired before operation could complete. For operations that
+   * change the state of the server, this error may be returned even if the
+   * operation has completed successfully. For example, a successful response
+   * from the server could have been delayed long enough for the deadline to
+   * expire.
+   */
+  GRPCErrorCodeDeadlineExceeded = 4,
+
+  /** Some requested entity (e.g., file or directory) was not found. */
+  GRPCErrorCodeNotFound = 5,
+
+  /** Some entity that we attempted to create (e.g., file or directory) already
+     exists. */
+  GRPCErrorCodeAlreadyExists = 6,
+
+  /**
+   * The caller does not have permission to execute the specified operation.
+   * PERMISSION_DENIED isn't used for rejections caused by exhausting some
+   * resource (RESOURCE_EXHAUSTED is used instead for those errors).
+   * PERMISSION_DENIED doesn't indicate a failure to identify the caller
+   * (UNAUTHENTICATED is used instead for those errors).
+   */
+  GRPCErrorCodePermissionDenied = 7,
+
+  /**
+   * The request does not have valid authentication credentials for the
+   * operation (e.g. the caller's identity can't be verified).
+   */
+  GRPCErrorCodeUnauthenticated = 16,
+
+  /** Some resource has been exhausted, perhaps a per-user quota. */
+  GRPCErrorCodeResourceExhausted = 8,
+
+  /**
+   * The RPC was rejected because the server is not in a state required for the
+   * procedure's execution. For example, a directory to be deleted may be
+   * non-empty, etc. The client should not retry until the server state has been
+   * explicitly fixed (e.g. by performing another RPC). The details depend on
+   * the service being called, and should be found in the NSError's userInfo.
+   */
+  GRPCErrorCodeFailedPrecondition = 9,
+
+  /**
+   * The RPC was aborted, typically due to a concurrency issue like sequencer
+   * check failures, transaction aborts, etc. The client should retry at a
+   * higher-level (e.g., restarting a read- modify-write sequence).
+   */
+  GRPCErrorCodeAborted = 10,
+
+  /**
+   * The RPC was attempted past the valid range. E.g., enumerating past the end
+   * of a list. Unlike INVALID_ARGUMENT, this error indicates a problem that may
+   * be fixed if the system state changes. For example, an RPC to get elements
+   * of a list will generate INVALID_ARGUMENT if asked to return the element at
+   * a negative index, but it will generate OUT_OF_RANGE if asked to return the
+   * element at an index past the current size of the list.
+   */
+  GRPCErrorCodeOutOfRange = 11,
+
+  /** The procedure is not implemented or not supported/enabled in this server.
+   */
+  GRPCErrorCodeUnimplemented = 12,
+
+  /**
+   * Internal error. Means some invariant expected by the server application or
+   * the gRPC library has been broken.
+   */
+  GRPCErrorCodeInternal = 13,
+
+  /**
+   * The server is currently unavailable. This is most likely a transient
+   * condition and may be corrected by retrying with a backoff. Note that it is
+   * not always safe to retry non-idempotent operations.
+   */
+  GRPCErrorCodeUnavailable = 14,
+
+  /** Unrecoverable data loss or corruption. */
+  GRPCErrorCodeDataLoss = 15,
+};
+
+/**
+ * Safety remark of a gRPC method as defined in RFC 2616 Section 9.1
+ */
+typedef NS_ENUM(NSUInteger, GRPCCallSafety) {
+  /**
+   * Signal that there is no guarantees on how the call affects the server
+   * state.
+   */
+  GRPCCallSafetyDefault = 0,
+  /** Signal that the call is idempotent. gRPC is free to use PUT verb. */
+  GRPCCallSafetyIdempotentRequest = 1,
+  /**
+   * Signal that the call is cacheable and will not affect server state. gRPC is
+   * free to use GET verb.
+   */
+  GRPCCallSafetyCacheableRequest = 2,
+};
+
+// Compression algorithm to be used by a gRPC call
+typedef NS_ENUM(NSUInteger, GRPCCompressionAlgorithm) {
+  GRPCCompressNone = 0,
+  GRPCCompressDeflate,
+  GRPCCompressGzip,
+  GRPCStreamCompressGzip,
+};
+
+// GRPCCompressAlgorithm is deprecated; use GRPCCompressionAlgorithm
+typedef GRPCCompressionAlgorithm GRPCCompressAlgorithm;
+
+/** The transport to be used by a gRPC call */
+typedef NS_ENUM(NSUInteger, GRPCTransportType) {
+  GRPCTransportTypeDefault = 0,
+  /** gRPC internal HTTP/2 stack with BoringSSL */
+  GRPCTransportTypeChttp2BoringSSL = 0,
+  /** Cronet stack */
+  GRPCTransportTypeCronet,
+  /** Insecure channel. FOR TEST ONLY! */
+  GRPCTransportTypeInsecure,
+};
+
+/** Domain of NSError objects produced by gRPC. */
+extern NSString* _Nonnull const kGRPCErrorDomain;
+
+/**
+ * Keys used in |NSError|'s |userInfo| dictionary to store the response headers
+ * and trailers sent by the server.
+ */
+extern NSString* _Nonnull const kGRPCHeadersKey;
+extern NSString* _Nonnull const kGRPCTrailersKey;
+
+/** The id of a transport implementation. */
+typedef char* _Nonnull GRPCTransportId;
+
+/**
+ * Implement this protocol to provide a token to gRPC when a call is initiated.
+ */
+@protocol GRPCAuthorizationProtocol
+
+/**
+ * This method is called when gRPC is about to start the call. When OAuth token is acquired,
+ * \a handler is expected to be called with \a token being the new token to be used for this call.
+ */
+- (void)getTokenWithHandler:(void (^_Nonnull)(NSString* _Nullable token))handler;
+
+@end
diff --git a/src/objective-c/GRPCClient/internal_testing/GRPCCall+InternalTests.m b/src/objective-c/GRPCClient/internal_testing/GRPCCall+InternalTests.m
index 1bb352f0be2f3eb0f2111d50f02079a8b03e77bc..8f98daa6348679d409f1db3f419e42124824b5f5 100644
--- a/src/objective-c/GRPCClient/internal_testing/GRPCCall+InternalTests.m
+++ b/src/objective-c/GRPCClient/internal_testing/GRPCCall+InternalTests.m
@@ -20,7 +20,7 @@
 
 #import "GRPCCall+InternalTests.h"
 
-#import "../private/GRPCOpBatchLog.h"
+#import "../private/GRPCCore/GRPCOpBatchLog.h"
 
 @implementation GRPCCall (InternalTests)
 
diff --git a/src/objective-c/GRPCClient/private/ChannelArgsUtil.h b/src/objective-c/GRPCClient/private/GRPCCore/ChannelArgsUtil.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/ChannelArgsUtil.h
rename to src/objective-c/GRPCClient/private/GRPCCore/ChannelArgsUtil.h
diff --git a/src/objective-c/GRPCClient/private/ChannelArgsUtil.m b/src/objective-c/GRPCClient/private/GRPCCore/ChannelArgsUtil.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/ChannelArgsUtil.m
rename to src/objective-c/GRPCClient/private/GRPCCore/ChannelArgsUtil.m
diff --git a/src/objective-c/GRPCClient/private/GRPCCall+V2API.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCall+V2API.h
similarity index 79%
rename from src/objective-c/GRPCClient/private/GRPCCall+V2API.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCCall+V2API.h
index 22bf16962c6a7afb290c2000ad02bf2da14a8e3f..f6db3023cac376b4ead3f752145161c2364d9790 100644
--- a/src/objective-c/GRPCClient/private/GRPCCall+V2API.h
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCall+V2API.h
@@ -18,12 +18,6 @@
 
 @interface GRPCCall (V2API)
 
-- (instancetype)initWithHost:(NSString *)host
-                        path:(NSString *)path
-                  callSafety:(GRPCCallSafety)safety
-              requestsWriter:(GRXWriter *)requestsWriter
-                 callOptions:(GRPCCallOptions *)callOptions;
-
 - (instancetype)initWithHost:(NSString *)host
                         path:(NSString *)path
                   callSafety:(GRPCCallSafety)safety
diff --git a/src/objective-c/GRPCClient/private/GRPCCallInternal.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCallInternal.h
similarity index 70%
rename from src/objective-c/GRPCClient/private/GRPCCallInternal.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCCallInternal.h
index ac2d1cba2ec8ab20bc56bc9720aab43bfe0b0be2..641b1fb2e8a9968f42accb36bcda677087749da3 100644
--- a/src/objective-c/GRPCClient/private/GRPCCallInternal.h
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCallInternal.h
@@ -16,20 +16,22 @@
  *
  */
 
-#import <GRPCClient/GRPCInterceptor.h>
+#import <GRPCClient/GRPCTransport.h>
 
 NS_ASSUME_NONNULL_BEGIN
 
-@interface GRPCCall2Internal : NSObject<GRPCInterceptorInterface>
+@protocol GRPCResponseHandler;
+@class GRPCCallOptions;
+@protocol GRPCChannelFactory;
 
-- (instancetype)init;
+@interface GRPCCall2Internal : GRPCTransport
 
-- (void)setResponseHandler:(id<GRPCResponseHandler>)responseHandler;
+- (instancetype)initWithTransportManager:(GRPCTransportManager *)transportManager;
 
 - (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
-                    callOptions:(nullable GRPCCallOptions *)callOptions;
+                    callOptions:(GRPCCallOptions *)callOptions;
 
-- (void)writeData:(NSData *)data;
+- (void)writeData:(id)data;
 
 - (void)finish;
 
diff --git a/src/objective-c/GRPCClient/private/GRPCCallInternal.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCallInternal.m
similarity index 69%
rename from src/objective-c/GRPCClient/private/GRPCCallInternal.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCCallInternal.m
index 32e38158fad5b2264fcce829a45f519db13995a8..ea01fcaf5944f25042ebf623a6d270e7f06e9d3c 100644
--- a/src/objective-c/GRPCClient/private/GRPCCallInternal.m
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCallInternal.m
@@ -19,8 +19,10 @@
 #import "GRPCCallInternal.h"
 
 #import <GRPCClient/GRPCCall.h>
+#import <GRPCClient/GRPCInterceptor.h>
 #import <RxLibrary/GRXBufferedPipe.h>
 
+#import "../GRPCTransport+Private.h"
 #import "GRPCCall+V2API.h"
 
 @implementation GRPCCall2Internal {
@@ -28,8 +30,8 @@
   GRPCRequestOptions *_requestOptions;
   /** Options for the call. */
   GRPCCallOptions *_callOptions;
-  /** The handler of responses. */
-  id<GRPCResponseHandler> _handler;
+  /** The interceptor manager to process responses. */
+  GRPCTransportManager *_transportManager;
 
   /**
    * Make use of legacy GRPCCall to make calls. Nullified when call is finished.
@@ -51,40 +53,28 @@
   NSUInteger _pendingReceiveNextMessages;
 }
 
-- (instancetype)init {
-  if ((self = [super init])) {
+- (instancetype)initWithTransportManager:(GRPCTransportManager *)transportManager {
+  dispatch_queue_t dispatchQueue;
   // Set queue QoS only when iOS version is 8.0 or above and Xcode version is 9.0 or above
 #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 || __MAC_OS_X_VERSION_MAX_ALLOWED >= 101300
-    if (@available(iOS 8.0, macOS 10.10, *)) {
-      _dispatchQueue = dispatch_queue_create(
-          NULL,
-          dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
-    } else {
+  if (@available(iOS 8.0, macOS 10.10, *)) {
+    dispatchQueue = dispatch_queue_create(
+        NULL, dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, 0));
+  } else {
 #else
-    {
+  {
 #endif
-      _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
-    }
+    dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+  }
+  if ((self = [super init])) {
     _pipe = [GRXBufferedPipe pipe];
+    _transportManager = transportManager;
+    _dispatchQueue = dispatchQueue;
   }
   return self;
 }
 
-- (void)setResponseHandler:(id<GRPCResponseHandler>)responseHandler {
-  @synchronized(self) {
-    NSAssert(!_started, @"Call already started.");
-    if (_started) {
-      return;
-    }
-    _handler = responseHandler;
-    _initialMetadataPublished = NO;
-    _started = NO;
-    _canceled = NO;
-    _finished = NO;
-  }
-}
-
-- (dispatch_queue_t)requestDispatchQueue {
+- (dispatch_queue_t)dispatchQueue {
   return _dispatchQueue;
 }
 
@@ -102,26 +92,15 @@
     return;
   }
 
+  GRPCCall *copiedCall = nil;
   @synchronized(self) {
-    NSAssert(_handler != nil, @"Response handler required.");
-    if (_handler == nil) {
-      NSLog(@"Invalid response handler.");
-      return;
-    }
     _requestOptions = requestOptions;
     if (callOptions == nil) {
       _callOptions = [[GRPCCallOptions alloc] init];
     } else {
       _callOptions = [callOptions copy];
     }
-  }
 
-  [self start];
-}
-
-- (void)start {
-  GRPCCall *copiedCall = nil;
-  @synchronized(self) {
     NSAssert(!_started, @"Call already started.");
     NSAssert(!_canceled, @"Call already canceled.");
     if (_started) {
@@ -140,7 +119,7 @@
                                callOptions:_callOptions
                                  writeDone:^{
                                    @synchronized(self) {
-                                     if (self->_handler) {
+                                     if (self->_transportManager) {
                                        [self issueDidWriteData];
                                      }
                                    }
@@ -158,7 +137,7 @@
 
   void (^valueHandler)(id value) = ^(id value) {
     @synchronized(self) {
-      if (self->_handler) {
+      if (self->_transportManager) {
         if (!self->_initialMetadataPublished) {
           self->_initialMetadataPublished = YES;
           [self issueInitialMetadata:self->_call.responseHeaders];
@@ -171,7 +150,7 @@
   };
   void (^completionHandler)(NSError *errorOrNil) = ^(NSError *errorOrNil) {
     @synchronized(self) {
-      if (self->_handler) {
+      if (self->_transportManager) {
         if (!self->_initialMetadataPublished) {
           self->_initialMetadataPublished = YES;
           [self issueInitialMetadata:self->_call.responseHeaders];
@@ -207,20 +186,19 @@
     _call = nil;
     _pipe = nil;
 
-    if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
-      id<GRPCResponseHandler> copiedHandler = _handler;
-      _handler = nil;
-      dispatch_async(copiedHandler.dispatchQueue, ^{
-        [copiedHandler didCloseWithTrailingMetadata:nil
-                                              error:[NSError errorWithDomain:kGRPCErrorDomain
-                                                                        code:GRPCErrorCodeCancelled
-                                                                    userInfo:@{
-                                                                      NSLocalizedDescriptionKey :
-                                                                          @"Canceled by app"
-                                                                    }]];
-      });
-    } else {
-      _handler = nil;
+    if (_transportManager != nil) {
+      [_transportManager
+          forwardPreviousInterceptorCloseWithTrailingMetadata:nil
+                                                        error:
+                                                            [NSError
+                                                                errorWithDomain:kGRPCErrorDomain
+                                                                           code:
+                                                                               GRPCErrorCodeCancelled
+                                                                       userInfo:@{
+                                                                         NSLocalizedDescriptionKey :
+                                                                             @"Canceled by app"
+                                                                       }]];
+      [_transportManager shutDown];
     }
   }
   [copiedCall cancel];
@@ -271,59 +249,25 @@
 }
 
 - (void)issueInitialMetadata:(NSDictionary *)initialMetadata {
-  @synchronized(self) {
-    if (initialMetadata != nil &&
-        [_handler respondsToSelector:@selector(didReceiveInitialMetadata:)]) {
-      id<GRPCResponseHandler> copiedHandler = _handler;
-      dispatch_async(_handler.dispatchQueue, ^{
-        [copiedHandler didReceiveInitialMetadata:initialMetadata];
-      });
-    }
+  if (initialMetadata != nil) {
+    [_transportManager forwardPreviousInterceptorWithInitialMetadata:initialMetadata];
   }
 }
 
 - (void)issueMessage:(id)message {
-  @synchronized(self) {
-    if (message != nil) {
-      if ([_handler respondsToSelector:@selector(didReceiveData:)]) {
-        id<GRPCResponseHandler> copiedHandler = _handler;
-        dispatch_async(_handler.dispatchQueue, ^{
-          [copiedHandler didReceiveData:message];
-        });
-      } else if ([_handler respondsToSelector:@selector(didReceiveRawMessage:)]) {
-        id<GRPCResponseHandler> copiedHandler = _handler;
-        dispatch_async(_handler.dispatchQueue, ^{
-          [copiedHandler didReceiveRawMessage:message];
-        });
-      }
-    }
+  if (message != nil) {
+    [_transportManager forwardPreviousInterceptorWithData:message];
   }
 }
 
 - (void)issueClosedWithTrailingMetadata:(NSDictionary *)trailingMetadata error:(NSError *)error {
-  @synchronized(self) {
-    if ([_handler respondsToSelector:@selector(didCloseWithTrailingMetadata:error:)]) {
-      id<GRPCResponseHandler> copiedHandler = _handler;
-      // Clean up _handler so that no more responses are reported to the handler.
-      _handler = nil;
-      dispatch_async(copiedHandler.dispatchQueue, ^{
-        [copiedHandler didCloseWithTrailingMetadata:trailingMetadata error:error];
-      });
-    } else {
-      _handler = nil;
-    }
-  }
+  [_transportManager forwardPreviousInterceptorCloseWithTrailingMetadata:trailingMetadata
+                                                                   error:error];
+  [_transportManager shutDown];
 }
 
 - (void)issueDidWriteData {
-  @synchronized(self) {
-    if (_callOptions.flowControlEnabled && [_handler respondsToSelector:@selector(didWriteData)]) {
-      id<GRPCResponseHandler> copiedHandler = _handler;
-      dispatch_async(copiedHandler.dispatchQueue, ^{
-        [copiedHandler didWriteData];
-      });
-    }
-  }
+  [_transportManager forwardPreviousInterceptorDidWriteData];
 }
 
 - (void)receiveNextMessages:(NSUInteger)numberOfMessages {
diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannel.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCChannel.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCChannel.h
diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannel.m
similarity index 78%
rename from src/objective-c/GRPCClient/private/GRPCChannel.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCChannel.m
index 1a79fb04a0d5cae04cff1aee09e07b03cb86f338..cd2b4313acd646bec31be7297bcfe3ced04491ed 100644
--- a/src/objective-c/GRPCClient/private/GRPCChannel.m
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannel.m
@@ -20,18 +20,19 @@
 
 #include <grpc/support/log.h>
 
-#import "../internal/GRPCCallOptions+Internal.h"
+#import "../../internal/GRPCCallOptions+Internal.h"
+#import "../GRPCTransport+Private.h"
 #import "ChannelArgsUtil.h"
 #import "GRPCChannelFactory.h"
 #import "GRPCChannelPool.h"
 #import "GRPCCompletionQueue.h"
-#import "GRPCCronetChannelFactory.h"
+#import "GRPCCoreFactory.h"
 #import "GRPCInsecureChannelFactory.h"
 #import "GRPCSecureChannelFactory.h"
-#import "version.h"
 
 #import <GRPCClient/GRPCCall+Cronet.h>
 #import <GRPCClient/GRPCCallOptions.h>
+#import <GRPCClient/version.h>
 
 @implementation GRPCChannelConfiguration
 
@@ -50,32 +51,48 @@
 }
 
 - (id<GRPCChannelFactory>)channelFactory {
-  GRPCTransportType type = _callOptions.transportType;
-  switch (type) {
-    case GRPCTransportTypeChttp2BoringSSL:
-      // TODO (mxyan): Remove when the API is deprecated
-#ifdef GRPC_COMPILE_WITH_CRONET
-      if (![GRPCCall isUsingCronet]) {
-#else
-    {
-#endif
-        NSError *error;
-        id<GRPCChannelFactory> factory = [GRPCSecureChannelFactory
-            factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates
-                                privateKey:_callOptions.PEMPrivateKey
-                                 certChain:_callOptions.PEMCertificateChain
-                                     error:&error];
-        NSAssert(factory != nil, @"Failed to create secure channel factory");
-        if (factory == nil) {
-          NSLog(@"Error creating secure channel factory: %@", error);
+  if (_callOptions.transport != NULL) {
+    id<GRPCTransportFactory> transportFactory =
+        [[GRPCTransportRegistry sharedInstance] getTransportFactoryWithId:_callOptions.transport];
+    if (!
+        [transportFactory respondsToSelector:@selector(createCoreChannelFactoryWithCallOptions:)]) {
+      // impossible because we are using GRPCCore now
+      [NSException raise:NSInternalInconsistencyException
+                  format:@"Transport factory type is wrong"];
+    }
+    id<GRPCCoreTransportFactory> coreTransportFactory =
+        (id<GRPCCoreTransportFactory>)transportFactory;
+    return [coreTransportFactory createCoreChannelFactoryWithCallOptions:_callOptions];
+  } else {
+    // To maintain backwards compatibility with tranportType
+    GRPCTransportType type = _callOptions.transportType;
+    switch (type) {
+      case GRPCTransportTypeChttp2BoringSSL:
+        // TODO (mxyan): Remove when the API is deprecated
+        {
+          NSError *error;
+          id<GRPCChannelFactory> factory = [GRPCSecureChannelFactory
+              factoryWithPEMRootCertificates:_callOptions.PEMRootCertificates
+                                  privateKey:_callOptions.PEMPrivateKey
+                                   certChain:_callOptions.PEMCertificateChain
+                                       error:&error];
+          NSAssert(factory != nil, @"Failed to create secure channel factory");
+          if (factory == nil) {
+            NSLog(@"Error creating secure channel factory: %@", error);
+          }
+          return factory;
         }
-        return factory;
+      case GRPCTransportTypeCronet: {
+        id<GRPCCoreTransportFactory> transportFactory = (id<GRPCCoreTransportFactory>)[
+            [GRPCTransportRegistry sharedInstance] getTransportFactoryWithId:gGRPCCoreCronetId];
+        return [transportFactory createCoreChannelFactoryWithCallOptions:_callOptions];
       }
-      // fallthrough
-    case GRPCTransportTypeCronet:
-      return [GRPCCronetChannelFactory sharedInstance];
-    case GRPCTransportTypeInsecure:
-      return [GRPCInsecureChannelFactory sharedInstance];
+      case GRPCTransportTypeInsecure:
+        return [GRPCInsecureChannelFactory sharedInstance];
+      default:
+        NSLog(@"Unrecognized transport type");
+        return nil;
+    }
   }
 }
 
@@ -198,6 +215,7 @@
     } else {
       channelArgs = channelConfiguration.channelArgs;
     }
+
     id<GRPCChannelFactory> factory = channelConfiguration.channelFactory;
     _unmanagedChannel = [factory createChannelWithHost:host channelArgs:channelArgs];
     NSAssert(_unmanagedChannel != NULL, @"Failed to create channel");
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelFactory.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCChannelFactory.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelFactory.h
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelPool+Test.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCChannelPool+Test.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelPool+Test.h
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelPool.h
similarity index 98%
rename from src/objective-c/GRPCClient/private/GRPCChannelPool.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelPool.h
index e00ee69e63ab99480aeb7b843476c7e5ebf773db..b83a28a236878408a3a86f9fb45e7d06e18722d7 100644
--- a/src/objective-c/GRPCClient/private/GRPCChannelPool.h
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelPool.h
@@ -18,8 +18,6 @@
 
 #import <GRPCClient/GRPCCallOptions.h>
 
-#import "GRPCChannelFactory.h"
-
 NS_ASSUME_NONNULL_BEGIN
 
 @protocol GRPCChannel;
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelPool.m
similarity index 98%
rename from src/objective-c/GRPCClient/private/GRPCChannelPool.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelPool.m
index d545793fccec14cea438850b9cde3dbb7d205912..92f52e67b754a98394bb6cdc70a0763a323903a4 100644
--- a/src/objective-c/GRPCClient/private/GRPCChannelPool.m
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCChannelPool.m
@@ -18,19 +18,16 @@
 
 #import <Foundation/Foundation.h>
 
-#import "../internal/GRPCCallOptions+Internal.h"
+#import "../../internal/GRPCCallOptions+Internal.h"
 #import "GRPCChannel.h"
 #import "GRPCChannelFactory.h"
 #import "GRPCChannelPool+Test.h"
 #import "GRPCChannelPool.h"
 #import "GRPCCompletionQueue.h"
-#import "GRPCCronetChannelFactory.h"
 #import "GRPCInsecureChannelFactory.h"
 #import "GRPCSecureChannelFactory.h"
 #import "GRPCWrappedCall.h"
-#import "version.h"
 
-#import <GRPCClient/GRPCCall+Cronet.h>
 #include <grpc/support/log.h>
 
 extern const char *kCFStreamVarName;
diff --git a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCompletionQueue.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCCompletionQueue.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCCompletionQueue.h
diff --git a/src/objective-c/GRPCClient/private/GRPCCompletionQueue.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCompletionQueue.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCCompletionQueue.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCCompletionQueue.m
diff --git a/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.h
new file mode 100644
index 0000000000000000000000000000000000000000..83d279d2c72e573a007be33d03fb50c1d89380d4
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.h
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright 2019 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 "../GRPCCoreFactory.h"
+
+/**
+ * The factory for gRPC Core + Cronet transport implementation. The
+ * implementation is not part of the default transports of gRPC and is for
+ * testing purpose only on Github.
+ *
+ * To use this transport, a user must include the GRPCCoreCronet module as a
+ * dependency of the project and use gGRPCCoreCronetId in call options to
+ * specify that this is the transport to be used for a call.
+ */
+@interface GRPCCoreCronetFactory : NSObject<GRPCCoreTransportFactory>
+
+@end
diff --git a/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.m
new file mode 100644
index 0000000000000000000000000000000000000000..5772694fc5402465e97f78beba54e20b83971052
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCoreCronetFactory.m
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright 2019 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 "GRPCCoreCronetFactory.h"
+
+#import <GRPCClient/GRPCCall+Cronet.h>
+#import <GRPCClient/GRPCTransport.h>
+
+#import "../GRPCCallInternal.h"
+#import "../GRPCCoreFactory.h"
+#import "GRPCCronetChannelFactory.h"
+
+static GRPCCoreCronetFactory *gGRPCCoreCronetFactory = nil;
+static dispatch_once_t gInitGRPCCoreCronetFactory;
+
+@implementation GRPCCoreCronetFactory
+
++ (instancetype)sharedInstance {
+  dispatch_once(&gInitGRPCCoreCronetFactory, ^{
+    gGRPCCoreCronetFactory = [[GRPCCoreCronetFactory alloc] init];
+  });
+  return gGRPCCoreCronetFactory;
+}
+
++ (void)load {
+  [[GRPCTransportRegistry sharedInstance]
+      registerTransportWithId:gGRPCCoreCronetId
+                      factory:[GRPCCoreCronetFactory sharedInstance]];
+}
+
+- (GRPCTransport *)createTransportWithManager:(GRPCTransportManager *)transportManager {
+  return [[GRPCCall2Internal alloc] initWithTransportManager:transportManager];
+}
+
+- (id<GRPCChannelFactory>)createCoreChannelFactoryWithCallOptions:(GRPCCallOptions *)callOptions {
+  return [GRPCCronetChannelFactory sharedInstance];
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCronetChannelFactory.h
similarity index 96%
rename from src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCronetChannelFactory.h
index 738dfdb737029115642ce18ebf330275a213b58b..138ddf1f73010df8506bc38917f6b619be6902f0 100644
--- a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCronetChannelFactory.h
@@ -15,7 +15,7 @@
  * limitations under the License.
  *
  */
-#import "GRPCChannelFactory.h"
+#import "../GRPCChannelFactory.h"
 
 @class GRPCChannel;
 typedef struct stream_engine stream_engine;
diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCronetChannelFactory.m
similarity index 76%
rename from src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCronetChannelFactory.m
index 5bcb021dc4bc1f37d5b4ab0339688832933adedc..da3f3afd8558f88a79ebbec16acc9785a55dea76 100644
--- a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/GRPCCronetChannelFactory.m
@@ -18,10 +18,8 @@
 
 #import "GRPCCronetChannelFactory.h"
 
-#import "ChannelArgsUtil.h"
-#import "GRPCChannel.h"
-
-#ifdef GRPC_COMPILE_WITH_CRONET
+#import "../ChannelArgsUtil.h"
+#import "../GRPCChannel.h"
 
 #import <Cronet/Cronet.h>
 #include <grpc/grpc_cronet.h>
@@ -59,21 +57,3 @@
 }
 
 @end
-
-#else
-
-@implementation GRPCCronetChannelFactory
-
-+ (instancetype)sharedInstance {
-  NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel.");
-  return nil;
-}
-
-- (grpc_channel *)createChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)args {
-  NSAssert(NO, @"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel.");
-  return NULL;
-}
-
-@end
-
-#endif
diff --git a/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.h
new file mode 100644
index 0000000000000000000000000000000000000000..3cd312d4ad045aee2c383c3a4453f170b4b5850e
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.h
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright 2019 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 <GRPCClient/GRPCTransport.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol GRPCChannelFactory;
+@protocol GRPCCallOptions;
+
+/** The interface for transport implementations that are based on Core. */
+@protocol GRPCCoreTransportFactory<GRPCTransportFactory>
+
+/** Get the channel factory for GRPCChannel from call options. */
+- (nullable id<GRPCChannelFactory>)createCoreChannelFactoryWithCallOptions:
+    (GRPCCallOptions *)callOptions;
+
+@end
+
+/** The factory for gRPC Core + CFStream + TLS secure channel transport implementation. */
+@interface GRPCCoreSecureFactory : NSObject<GRPCCoreTransportFactory>
+
+@end
+
+/** The factory for gRPC Core + CFStream + insecure channel transport implementation. */
+@interface GRPCCoreInsecureFactory : NSObject<GRPCCoreTransportFactory>
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.m
new file mode 100644
index 0000000000000000000000000000000000000000..19d7231a203816d7a5e33b8dcb2f4a0a73a37369
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreFactory.m
@@ -0,0 +1,90 @@
+/*
+ *
+ * Copyright 2019 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 "GRPCCoreFactory.h"
+
+#import <GRPCClient/GRPCTransport.h>
+
+#import "GRPCCallInternal.h"
+#import "GRPCInsecureChannelFactory.h"
+#import "GRPCSecureChannelFactory.h"
+
+static GRPCCoreSecureFactory *gGRPCCoreSecureFactory = nil;
+static GRPCCoreInsecureFactory *gGRPCCoreInsecureFactory = nil;
+static dispatch_once_t gInitGRPCCoreSecureFactory;
+static dispatch_once_t gInitGRPCCoreInsecureFactory;
+
+@implementation GRPCCoreSecureFactory
+
++ (instancetype)sharedInstance {
+  dispatch_once(&gInitGRPCCoreSecureFactory, ^{
+    gGRPCCoreSecureFactory = [[GRPCCoreSecureFactory alloc] init];
+  });
+  return gGRPCCoreSecureFactory;
+}
+
++ (void)load {
+  [[GRPCTransportRegistry sharedInstance]
+      registerTransportWithId:GRPCDefaultTransportImplList.core_secure
+                      factory:[self sharedInstance]];
+}
+
+- (GRPCTransport *)createTransportWithManager:(GRPCTransportManager *)transportManager {
+  return [[GRPCCall2Internal alloc] initWithTransportManager:transportManager];
+}
+
+- (id<GRPCChannelFactory>)createCoreChannelFactoryWithCallOptions:(GRPCCallOptions *)callOptions {
+  NSError *error;
+  id<GRPCChannelFactory> factory =
+      [GRPCSecureChannelFactory factoryWithPEMRootCertificates:callOptions.PEMRootCertificates
+                                                    privateKey:callOptions.PEMPrivateKey
+                                                     certChain:callOptions.PEMCertificateChain
+                                                         error:&error];
+  if (error != nil) {
+    NSLog(@"Unable to create secure channel factory");
+    return nil;
+  }
+  return factory;
+}
+
+@end
+
+@implementation GRPCCoreInsecureFactory
+
++ (instancetype)sharedInstance {
+  dispatch_once(&gInitGRPCCoreInsecureFactory, ^{
+    gGRPCCoreInsecureFactory = [[GRPCCoreInsecureFactory alloc] init];
+  });
+  return gGRPCCoreInsecureFactory;
+}
+
++ (void)load {
+  [[GRPCTransportRegistry sharedInstance]
+      registerTransportWithId:GRPCDefaultTransportImplList.core_insecure
+                      factory:[self sharedInstance]];
+}
+
+- (GRPCTransport *)createTransportWithManager:(GRPCTransportManager *)transportManager {
+  return [[GRPCCall2Internal alloc] initWithTransportManager:transportManager];
+}
+
+- (id<GRPCChannelFactory>)createCoreChannelFactoryWithCallOptions:(GRPCCallOptions *)callOptions {
+  return [GRPCInsecureChannelFactory sharedInstance];
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCHost.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCHost.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCHost.h
diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCHost.m
similarity index 82%
rename from src/objective-c/GRPCClient/private/GRPCHost.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCHost.m
index 63ffc92741134b5bcea64a23a08f29d0eef77d35..1f6a25ff78f066aa84db39d859b0de559390ba75 100644
--- a/src/objective-c/GRPCClient/private/GRPCHost.m
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCHost.m
@@ -21,17 +21,16 @@
 #import <GRPCClient/GRPCCall+Cronet.h>
 #import <GRPCClient/GRPCCall.h>
 #import <GRPCClient/GRPCCallOptions.h>
+#import <GRPCClient/GRPCTransport.h>
 
 #include <grpc/grpc.h>
 #include <grpc/grpc_security.h>
 
-#import "../internal/GRPCCallOptions+Internal.h"
+#import "../../internal/GRPCCallOptions+Internal.h"
 #import "GRPCChannelFactory.h"
 #import "GRPCCompletionQueue.h"
-#import "GRPCCronetChannelFactory.h"
 #import "GRPCSecureChannelFactory.h"
 #import "NSDictionary+GRPC.h"
-#import "version.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
@@ -113,20 +112,12 @@ static NSMutableDictionary *gHostCache;
   options.PEMPrivateKey = _PEMPrivateKey;
   options.PEMCertificateChain = _PEMCertificateChain;
   options.hostNameOverride = _hostNameOverride;
-#ifdef GRPC_COMPILE_WITH_CRONET
-  // By old API logic, insecure channel precedes Cronet channel; Cronet channel preceeds default
-  // channel.
-  if ([GRPCCall isUsingCronet]) {
-    if (_transportType == GRPCTransportTypeInsecure) {
-      options.transportType = GRPCTransportTypeInsecure;
-    } else {
-      NSAssert(_transportType == GRPCTransportTypeDefault, @"Invalid transport type");
-      options.transportType = GRPCTransportTypeCronet;
-    }
-  } else
-#endif
-  {
-    options.transportType = _transportType;
+  if (_transportType == GRPCTransportTypeInsecure) {
+    options.transport = GRPCDefaultTransportImplList.core_insecure;
+  } else if ([GRPCCall isUsingCronet]) {
+    options.transport = gGRPCCoreCronetId;
+  } else {
+    options.transport = GRPCDefaultTransportImplList.core_secure;
   }
   options.logContext = _logContext;
 
@@ -135,16 +126,14 @@ static NSMutableDictionary *gHostCache;
 
 + (GRPCCallOptions *)callOptionsForHost:(NSString *)host {
   // TODO (mxyan): Remove when old API is deprecated
-  NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]];
-  if (hostURL.host && hostURL.port == nil) {
-    host = [hostURL.host stringByAppendingString:@":443"];
-  }
-
   GRPCCallOptions *callOptions = nil;
   @synchronized(gHostCache) {
-    callOptions = [gHostCache[host] callOptions];
+    GRPCHost *hostConfig = [GRPCHost hostWithAddress:host];
+    callOptions = [hostConfig callOptions];
   }
+  NSAssert(callOptions != nil, @"Unable to create call options object");
   if (callOptions == nil) {
+    NSLog(@"Unable to create call options object");
     callOptions = [[GRPCCallOptions alloc] init];
   }
   return callOptions;
diff --git a/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCInsecureChannelFactory.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCInsecureChannelFactory.h
diff --git a/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCInsecureChannelFactory.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCInsecureChannelFactory.m
diff --git a/src/objective-c/GRPCClient/private/GRPCOpBatchLog.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCOpBatchLog.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCOpBatchLog.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCOpBatchLog.h
diff --git a/src/objective-c/GRPCClient/private/GRPCOpBatchLog.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCOpBatchLog.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCOpBatchLog.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCOpBatchLog.m
diff --git a/src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCReachabilityFlagNames.xmacro.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCReachabilityFlagNames.xmacro.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCReachabilityFlagNames.xmacro.h
diff --git a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCRequestHeaders.h
similarity index 95%
rename from src/objective-c/GRPCClient/private/GRPCRequestHeaders.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCRequestHeaders.h
index 545ff1cea820ce0a47d11da0fb8f81fe296b6399..0fced0c385a77c454a4f99f6bb95a50a1855a788 100644
--- a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.h
+++ b/src/objective-c/GRPCClient/private/GRPCCore/GRPCRequestHeaders.h
@@ -18,7 +18,7 @@
 
 #import <Foundation/Foundation.h>
 
-#import "../GRPCCall.h"
+#import <GRPCClient/GRPCCallLegacy.h>
 
 @interface GRPCRequestHeaders : NSMutableDictionary
 
diff --git a/src/objective-c/GRPCClient/private/GRPCRequestHeaders.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCRequestHeaders.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCRequestHeaders.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCRequestHeaders.m
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCSecureChannelFactory.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCSecureChannelFactory.h
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCSecureChannelFactory.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCSecureChannelFactory.m
diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCCore/GRPCWrappedCall.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCWrappedCall.h
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCWrappedCall.h
diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCCore/GRPCWrappedCall.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/GRPCWrappedCall.m
rename to src/objective-c/GRPCClient/private/GRPCCore/GRPCWrappedCall.m
diff --git a/src/objective-c/GRPCClient/private/NSData+GRPC.h b/src/objective-c/GRPCClient/private/GRPCCore/NSData+GRPC.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/NSData+GRPC.h
rename to src/objective-c/GRPCClient/private/GRPCCore/NSData+GRPC.h
diff --git a/src/objective-c/GRPCClient/private/NSData+GRPC.m b/src/objective-c/GRPCClient/private/GRPCCore/NSData+GRPC.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/NSData+GRPC.m
rename to src/objective-c/GRPCClient/private/GRPCCore/NSData+GRPC.m
diff --git a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.h b/src/objective-c/GRPCClient/private/GRPCCore/NSDictionary+GRPC.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/NSDictionary+GRPC.h
rename to src/objective-c/GRPCClient/private/GRPCCore/NSDictionary+GRPC.h
diff --git a/src/objective-c/GRPCClient/private/NSDictionary+GRPC.m b/src/objective-c/GRPCClient/private/GRPCCore/NSDictionary+GRPC.m
similarity index 100%
rename from src/objective-c/GRPCClient/private/NSDictionary+GRPC.m
rename to src/objective-c/GRPCClient/private/GRPCCore/NSDictionary+GRPC.m
diff --git a/src/objective-c/GRPCClient/private/NSError+GRPC.h b/src/objective-c/GRPCClient/private/GRPCCore/NSError+GRPC.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/NSError+GRPC.h
rename to src/objective-c/GRPCClient/private/GRPCCore/NSError+GRPC.h
diff --git a/src/objective-c/GRPCClient/private/NSError+GRPC.m b/src/objective-c/GRPCClient/private/GRPCCore/NSError+GRPC.m
similarity index 96%
rename from src/objective-c/GRPCClient/private/NSError+GRPC.m
rename to src/objective-c/GRPCClient/private/GRPCCore/NSError+GRPC.m
index 3eefed88d638e90959406b738ff292317679465c..5b5fc6263c5dea20fc8ba4ebf1d8ff637fcf794e 100644
--- a/src/objective-c/GRPCClient/private/NSError+GRPC.m
+++ b/src/objective-c/GRPCClient/private/GRPCCore/NSError+GRPC.m
@@ -18,10 +18,9 @@
 
 #import "NSError+GRPC.h"
 
+#import <GRPCClient/GRPCTypes.h>
 #include <grpc/grpc.h>
 
-NSString *const kGRPCErrorDomain = @"io.grpc";
-
 @implementation NSError (GRPC)
 + (instancetype)grpc_errorFromStatusCode:(grpc_status_code)statusCode
                                  details:(const char *)details
diff --git a/src/objective-c/GRPCClient/private/GRPCTransport+Private.h b/src/objective-c/GRPCClient/private/GRPCTransport+Private.h
new file mode 100644
index 0000000000000000000000000000000000000000..2dc7357c363b6d8ccd1740172219f0fa74171994
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCTransport+Private.h
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright 2019 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 <GRPCClient/GRPCInterceptor.h>
+#import <GRPCClient/GRPCTransport.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ * Private interfaces of the transport registry.
+ */
+@interface GRPCTransportRegistry (Private)
+
+/**
+ * Get a transport implementation's factory by its transport id. If the transport id was not
+ * registered with the registry, the default transport factory (core + secure) is returned. If the
+ * default transport does not exist, an exception is thrown.
+ */
+- (id<GRPCTransportFactory>)getTransportFactoryWithId:(GRPCTransportId)transportId;
+
+@end
+
+@interface GRPCTransportManager : NSObject<GRPCInterceptorInterface>
+
+- (instancetype)initWithTransportId:(GRPCTransportId)transportId
+                previousInterceptor:(id<GRPCResponseHandler>)previousInterceptor;
+
+/**
+ * Notify the manager that the transport has shut down and the manager should release references to
+ * its response handler and stop forwarding requests/responses.
+ */
+- (void)shutDown;
+
+/** Forward initial metadata to the previous interceptor in the interceptor chain */
+- (void)forwardPreviousInterceptorWithInitialMetadata:(nullable NSDictionary *)initialMetadata;
+
+/** Forward a received message to the previous interceptor in the interceptor chain */
+- (void)forwardPreviousInterceptorWithData:(nullable id)data;
+
+/** Forward call close and trailing metadata to the previous interceptor in the interceptor chain */
+- (void)forwardPreviousInterceptorCloseWithTrailingMetadata:
+            (nullable NSDictionary *)trailingMetadata
+                                                      error:(nullable NSError *)error;
+
+/** Forward write completion to the previous interceptor in the interceptor chain */
+- (void)forwardPreviousInterceptorDidWriteData;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCTransport+Private.m b/src/objective-c/GRPCClient/private/GRPCTransport+Private.m
new file mode 100644
index 0000000000000000000000000000000000000000..9072f7afbe2d4786850e77904b2c6348d0d2b32c
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCTransport+Private.m
@@ -0,0 +1,131 @@
+/*
+ *
+ * Copyright 2019 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 "GRPCTransport+Private.h"
+
+#import <GRPCClient/GRPCTransport.h>
+
+@implementation GRPCTransportManager {
+  GRPCTransportId _transportId;
+  GRPCTransport *_transport;
+  id<GRPCResponseHandler> _previousInterceptor;
+  dispatch_queue_t _dispatchQueue;
+}
+
+- (instancetype)initWithTransportId:(GRPCTransportId)transportId
+                previousInterceptor:(id<GRPCResponseHandler>)previousInterceptor {
+  if ((self = [super init])) {
+    id<GRPCTransportFactory> factory =
+        [[GRPCTransportRegistry sharedInstance] getTransportFactoryWithId:transportId];
+
+    _transport = [factory createTransportWithManager:self];
+    NSAssert(_transport != nil, @"Failed to create transport with id: %s", transportId);
+    if (_transport == nil) {
+      NSLog(@"Failed to create transport with id: %s", transportId);
+      return nil;
+    }
+    _previousInterceptor = previousInterceptor;
+    _dispatchQueue = _transport.dispatchQueue;
+    _transportId = transportId;
+  }
+  return self;
+}
+
+- (void)shutDown {
+  dispatch_async(_dispatchQueue, ^{
+    self->_transport = nil;
+    self->_previousInterceptor = nil;
+  });
+}
+
+- (dispatch_queue_t)dispatchQueue {
+  return _dispatchQueue;
+}
+
+- (void)startWithRequestOptions:(GRPCRequestOptions *)requestOptions
+                    callOptions:(GRPCCallOptions *)callOptions {
+  if (_transportId != callOptions.transport) {
+    [NSException raise:NSInvalidArgumentException
+                format:@"Interceptors cannot change the call option 'transport'"];
+    return;
+  }
+  [_transport startWithRequestOptions:requestOptions callOptions:callOptions];
+}
+
+- (void)writeData:(id)data {
+  [_transport writeData:data];
+}
+
+- (void)finish {
+  [_transport finish];
+}
+
+- (void)cancel {
+  [_transport cancel];
+}
+
+- (void)receiveNextMessages:(NSUInteger)numberOfMessages {
+  [_transport receiveNextMessages:numberOfMessages];
+}
+
+/** Forward initial metadata to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorWithInitialMetadata:(NSDictionary *)initialMetadata {
+  if (initialMetadata == nil || _previousInterceptor == nil) {
+    return;
+  }
+  id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+  dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+    [copiedPreviousInterceptor didReceiveInitialMetadata:initialMetadata];
+  });
+}
+
+/** Forward a received message to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorWithData:(id)data {
+  if (data == nil || _previousInterceptor == nil) {
+    return;
+  }
+  id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+  dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+    [copiedPreviousInterceptor didReceiveData:data];
+  });
+}
+
+/** Forward call close and trailing metadata to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorCloseWithTrailingMetadata:(NSDictionary *)trailingMetadata
+                                                      error:(NSError *)error {
+  if (_previousInterceptor == nil) {
+    return;
+  }
+  id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+  dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+    [copiedPreviousInterceptor didCloseWithTrailingMetadata:trailingMetadata error:error];
+  });
+}
+
+/** Forward write completion to the previous interceptor in the chain */
+- (void)forwardPreviousInterceptorDidWriteData {
+  if (_previousInterceptor == nil) {
+    return;
+  }
+  id<GRPCResponseHandler> copiedPreviousInterceptor = _previousInterceptor;
+  dispatch_async(copiedPreviousInterceptor.dispatchQueue, ^{
+    [copiedPreviousInterceptor didWriteData];
+  });
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/private/version.h b/src/objective-c/GRPCClient/version.h
similarity index 100%
rename from src/objective-c/GRPCClient/private/version.h
rename to src/objective-c/GRPCClient/version.h
diff --git a/src/objective-c/ProtoRPC/ProtoRPC.h b/src/objective-c/ProtoRPC/ProtoRPC.h
index 12db46adeda5cc076b00d88e06192977ffdf96fc..c91adc7b7cdbc1ec314ab66b4e0437961462f10a 100644
--- a/src/objective-c/ProtoRPC/ProtoRPC.h
+++ b/src/objective-c/ProtoRPC/ProtoRPC.h
@@ -17,12 +17,16 @@
  */
 
 #import <Foundation/Foundation.h>
-#import <GRPCClient/GRPCCall.h>
+
+// import legacy header for compatibility with users using the ProtoRPC interface
+#import "ProtoRPCLegacy.h"
 
 #import "ProtoMethod.h"
 
 NS_ASSUME_NONNULL_BEGIN
 
+@class GRPCRequestOptions;
+@class GRPCCallOptions;
 @class GPBMessage;
 
 /** An object can implement this protocol to receive responses from server from a call. */
@@ -160,35 +164,3 @@ NS_ASSUME_NONNULL_BEGIN
 @end
 
 NS_ASSUME_NONNULL_END
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wnullability-completeness"
-
-__attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC
-    : GRPCCall
-
-      /**
-       * host parameter should not contain the scheme (http:// or https://), only the name or IP
-       * addr and the port number, for example @"localhost:5050".
-       */
-      -
-      (instancetype)initWithHost : (NSString *)host method
-    : (GRPCProtoMethod *)method requestsWriter : (GRXWriter *)requestsWriter responseClass
-    : (Class)responseClass responsesWriteable
-    : (id<GRXWriteable>)responsesWriteable NS_DESIGNATED_INITIALIZER;
-
-- (void)start;
-@end
-
-/**
- * This subclass is empty now. Eventually we'll remove ProtoRPC class
- * to avoid potential naming conflict
- */
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-declarations"
-    @interface GRPCProtoCall : ProtoRPC
-#pragma clang diagnostic pop
-
-                               @end
-
-#pragma clang diagnostic pop
diff --git a/src/objective-c/ProtoRPC/ProtoRPC.m b/src/objective-c/ProtoRPC/ProtoRPC.m
index 4700fdd1124044b40375263d7dd81132515edbdf..dbfa3c0f23da4a25adfea8fb65599cbe00358588 100644
--- a/src/objective-c/ProtoRPC/ProtoRPC.m
+++ b/src/objective-c/ProtoRPC/ProtoRPC.m
@@ -27,24 +27,6 @@
 #import <RxLibrary/GRXWriteable.h>
 #import <RxLibrary/GRXWriter+Transformations.h>
 
-/**
- * Generate an NSError object that represents a failure in parsing a proto class.
- */
-static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) {
-  NSDictionary *info = @{
-    NSLocalizedDescriptionKey : @"Unable to parse response from the server",
-    NSLocalizedRecoverySuggestionErrorKey :
-        @"If this RPC is idempotent, retry "
-        @"with exponential backoff. Otherwise, query the server status before "
-        @"retrying.",
-    NSUnderlyingErrorKey : parsingError,
-    @"Expected class" : expectedClass,
-    @"Received value" : proto,
-  };
-  // TODO(jcanizales): Use kGRPCErrorDomain and GRPCErrorCodeInternal when they're public.
-  return [NSError errorWithDomain:@"io.grpc" code:13 userInfo:info];
-}
-
 @implementation GRPCUnaryProtoCall {
   GRPCStreamingProtoCall *_call;
   GPBMessage *_message;
@@ -291,76 +273,3 @@ static NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsing
 }
 
 @end
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wdeprecated-implementations"
-@implementation ProtoRPC {
-#pragma clang diagnostic pop
-  id<GRXWriteable> _responseWriteable;
-}
-
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
-- (instancetype)initWithHost:(NSString *)host
-                        path:(NSString *)path
-              requestsWriter:(GRXWriter *)requestsWriter {
-  [NSException raise:NSInvalidArgumentException
-              format:@"Please use ProtoRPC's designated initializer instead."];
-  return nil;
-}
-#pragma clang diagnostic pop
-
-// Designated initializer
-- (instancetype)initWithHost:(NSString *)host
-                      method:(GRPCProtoMethod *)method
-              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:
-  if (![responseClass respondsToSelector:@selector(parseFromData:error:)]) {
-    [NSException raise:NSInvalidArgumentException
-                format:@"A protobuf class to parse the responses must be provided."];
-  }
-  // A writer that serializes the proto messages to send.
-  GRXWriter *bytesWriter = [requestsWriter map:^id(GPBMessage *proto) {
-    if (![proto isKindOfClass:[GPBMessage class]]) {
-      [NSException raise:NSInvalidArgumentException
-                  format:@"Request must be a proto message: %@", proto];
-    }
-    return [proto data];
-  }];
-  if ((self = [super initWithHost:host path:method.HTTPPath requestsWriter:bytesWriter])) {
-    __weak ProtoRPC *weakSelf = self;
-
-    // A writeable that parses the proto messages received.
-    _responseWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
-      // TODO(jcanizales): This is done in the main thread, and needs to happen in another thread.
-      NSError *error = nil;
-      id parsed = [responseClass parseFromData:value error:&error];
-      if (parsed) {
-        [responsesWriteable writeValue:parsed];
-      } else {
-        [weakSelf finishWithError:ErrorForBadProto(value, responseClass, error)];
-      }
-    }
-        completionHandler:^(NSError *errorOrNil) {
-          [responsesWriteable writesFinishedWithError:errorOrNil];
-        }];
-  }
-  return self;
-}
-
-- (void)start {
-  [self startWithWriteable:_responseWriteable];
-}
-
-- (void)startWithWriteable:(id<GRXWriteable>)writeable {
-  [super startWithWriteable:writeable];
-  // Break retain cycles.
-  _responseWriteable = nil;
-}
-@end
-
-@implementation GRPCProtoCall
-
-@end
diff --git a/src/objective-c/ProtoRPC/ProtoRPCLegacy.h b/src/objective-c/ProtoRPC/ProtoRPCLegacy.h
new file mode 100644
index 0000000000000000000000000000000000000000..b8d4003f6957a8f6aaabeeef02e039ff399b325f
--- /dev/null
+++ b/src/objective-c/ProtoRPC/ProtoRPCLegacy.h
@@ -0,0 +1,67 @@
+/*
+ *
+ * Copyright 2019 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 <GRPCClient/GRPCCallLegacy.h>
+
+// Import category headers for Swift build
+#import <GRPCClient/GRPCCall+ChannelArg.h>
+#import <GRPCClient/GRPCCall+ChannelCredentials.h>
+#import <GRPCClient/GRPCCall+Cronet.h>
+#import <GRPCClient/GRPCCall+OAuth2.h>
+#import <GRPCClient/GRPCCall+Tests.h>
+#import <RxLibrary/GRXWriteable.h>
+
+@class GRPCProtoMethod;
+@class GRXWriter;
+@protocol GRXWriteable;
+
+__attribute__((deprecated("Please use GRPCProtoCall."))) @interface ProtoRPC
+    : GRPCCall
+
+      /**
+       * host parameter should not contain the scheme (http:// or https://), only the name or IP
+       * addr and the port number, for example @"localhost:5050".
+       */
+      -
+      (instancetype)initWithHost : (NSString *)host method
+    : (GRPCProtoMethod *)method requestsWriter : (GRXWriter *)requestsWriter responseClass
+    : (Class)responseClass responsesWriteable
+    : (id<GRXWriteable>)responsesWriteable NS_DESIGNATED_INITIALIZER;
+
+- (void)start;
+
+@end
+
+/**
+ * This subclass is empty now. Eventually we'll remove ProtoRPC class
+ * to avoid potential naming conflict
+ */
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+    @interface GRPCProtoCall
+    : ProtoRPC
+#pragma clang diagnostic pop
+
+      @end
+
+          /**
+           * Generate an NSError object that represents a failure in parsing a proto class. For gRPC
+           * internal use only.
+           */
+          NSError *
+          ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError);
diff --git a/src/objective-c/ProtoRPC/ProtoRPCLegacy.m b/src/objective-c/ProtoRPC/ProtoRPCLegacy.m
new file mode 100644
index 0000000000000000000000000000000000000000..4ba93674063fbffaabfa8f16fd4819bc5bd01406
--- /dev/null
+++ b/src/objective-c/ProtoRPC/ProtoRPCLegacy.m
@@ -0,0 +1,121 @@
+/*
+ *
+ * Copyright 2019 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 "ProtoRPCLegacy.h"
+
+#import "ProtoMethod.h"
+
+#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
+#import <Protobuf/GPBProtocolBuffers.h>
+#else
+#import <GPBProtocolBuffers.h>
+#endif
+#import <GRPCClient/GRPCCall.h>
+#import <RxLibrary/GRXWriteable.h>
+#import <RxLibrary/GRXWriter+Transformations.h>
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+@implementation ProtoRPC {
+#pragma clang diagnostic pop
+  id<GRXWriteable> _responseWriteable;
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
+- (instancetype)initWithHost:(NSString *)host
+                        path:(NSString *)path
+              requestsWriter:(GRXWriter *)requestsWriter {
+  [NSException raise:NSInvalidArgumentException
+              format:@"Please use ProtoRPC's designated initializer instead."];
+  return nil;
+}
+#pragma clang diagnostic pop
+
+// Designated initializer
+- (instancetype)initWithHost:(NSString *)host
+                      method:(GRPCProtoMethod *)method
+              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:
+  if (![responseClass respondsToSelector:@selector(parseFromData:error:)]) {
+    [NSException raise:NSInvalidArgumentException
+                format:@"A protobuf class to parse the responses must be provided."];
+  }
+  // A writer that serializes the proto messages to send.
+  GRXWriter *bytesWriter = [requestsWriter map:^id(GPBMessage *proto) {
+    if (![proto isKindOfClass:[GPBMessage class]]) {
+      [NSException raise:NSInvalidArgumentException
+                  format:@"Request must be a proto message: %@", proto];
+    }
+    return [proto data];
+  }];
+  if ((self = [super initWithHost:host path:method.HTTPPath requestsWriter:bytesWriter])) {
+    __weak ProtoRPC *weakSelf = self;
+
+    // A writeable that parses the proto messages received.
+    _responseWriteable = [[GRXWriteable alloc] initWithValueHandler:^(NSData *value) {
+      // TODO(jcanizales): This is done in the main thread, and needs to happen in another thread.
+      NSError *error = nil;
+      id parsed = [responseClass parseFromData:value error:&error];
+      if (parsed) {
+        [responsesWriteable writeValue:parsed];
+      } else {
+        [weakSelf finishWithError:ErrorForBadProto(value, responseClass, error)];
+      }
+    }
+        completionHandler:^(NSError *errorOrNil) {
+          [responsesWriteable writesFinishedWithError:errorOrNil];
+        }];
+  }
+  return self;
+}
+
+- (void)start {
+  [self startWithWriteable:_responseWriteable];
+}
+
+- (void)startWithWriteable:(id<GRXWriteable>)writeable {
+  [super startWithWriteable:writeable];
+  // Break retain cycles.
+  _responseWriteable = nil;
+}
+@end
+
+@implementation GRPCProtoCall
+
+@end
+
+/**
+ * Generate an NSError object that represents a failure in parsing a proto class.
+ */
+NSError *ErrorForBadProto(id proto, Class expectedClass, NSError *parsingError) {
+  NSDictionary *info = @{
+    NSLocalizedDescriptionKey : @"Unable to parse response from the server",
+    NSLocalizedRecoverySuggestionErrorKey :
+        @"If this RPC is idempotent, retry "
+        @"with exponential backoff. Otherwise, query the server status before "
+        @"retrying.",
+    NSUnderlyingErrorKey : parsingError,
+    @"Expected class" : expectedClass,
+    @"Received value" : proto,
+  };
+  // TODO(jcanizales): Use kGRPCErrorDomain and GRPCErrorCodeInternal when they're public.
+  return [NSError errorWithDomain:@"io.grpc" code:13 userInfo:info];
+}
diff --git a/src/objective-c/ProtoRPC/ProtoService.h b/src/objective-c/ProtoRPC/ProtoService.h
index 900ec8d0e1ea7d69b2e0674506cf11c3800bf531..fd8a86bb2b29c2d8956fa728ee5e91e61e2988e6 100644
--- a/src/objective-c/ProtoRPC/ProtoService.h
+++ b/src/objective-c/ProtoRPC/ProtoService.h
@@ -18,14 +18,12 @@
 
 #import <Foundation/Foundation.h>
 
-@class GRPCProtoCall;
+#import <GRPCClient/GRPCCallOptions.h>
+#import "ProtoRPC.h"
+
 @protocol GRXWriteable;
 @class GRXWriter;
 @class GRPCCallOptions;
-@class GRPCProtoCall;
-@class GRPCUnaryProtoCall;
-@class GRPCStreamingProtoCall;
-@protocol GRPCProtoResponseHandler;
 
 #pragma clang diagnostic push
 #pragma clang diagnostic ignored "-Wnullability-completeness"
@@ -38,15 +36,6 @@ __attribute__((deprecated("Please use GRPCProtoService."))) @interface ProtoServ
     : (nonnull NSString *)packageName serviceName : (nonnull NSString *)serviceName callOptions
     : (nullable GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER;
 
-- (instancetype)initWithHost:(NSString *)host
-                 packageName:(NSString *)packageName
-                 serviceName:(NSString *)serviceName;
-
-- (GRPCProtoCall *)RPCToMethod:(NSString *)method
-                requestsWriter:(GRXWriter *)requestsWriter
-                 responseClass:(Class)responseClass
-            responsesWriteable:(id<GRXWriteable>)responsesWriteable;
-
 - (nullable GRPCUnaryProtoCall *)RPCToMethod:(nonnull NSString *)method
                                      message:(nonnull id)message
                              responseHandler:(nonnull id<GRPCProtoResponseHandler>)handler
@@ -58,6 +47,18 @@ __attribute__((deprecated("Please use GRPCProtoService."))) @interface ProtoServ
                                      callOptions:(nullable GRPCCallOptions *)callOptions
                                    responseClass:(nonnull Class)responseClass;
 
+@end
+
+    @interface ProtoService(Legacy)
+
+    - (instancetype)initWithHost : (NSString *)host packageName
+    : (NSString *)packageName serviceName : (NSString *)serviceName;
+
+- (GRPCProtoCall *)RPCToMethod:(NSString *)method
+                requestsWriter:(GRXWriter *)requestsWriter
+                 responseClass:(Class)responseClass
+            responsesWriteable:(id<GRXWriteable>)responsesWriteable;
+
 @end
 
 #pragma clang diagnostic pop
diff --git a/src/objective-c/ProtoRPC/ProtoService.m b/src/objective-c/ProtoRPC/ProtoService.m
index 80a1f2f226c34cc8815dbfc5f1c3ddd6beac7eaa..e84cab8903f9610a23d6c4c945fccc165bf32eae 100644
--- a/src/objective-c/ProtoRPC/ProtoService.m
+++ b/src/objective-c/ProtoRPC/ProtoService.m
@@ -29,15 +29,21 @@
 #pragma clang diagnostic ignored "-Wdeprecated-implementations"
 @implementation ProtoService {
 #pragma clang diagnostic pop
+
+  GRPCCallOptions *_callOptions;
   NSString *_host;
   NSString *_packageName;
   NSString *_serviceName;
-  GRPCCallOptions *_callOptions;
 }
 
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wnonnull"
+// Do not call the default init method
 - (instancetype)init {
-  return [self initWithHost:nil packageName:nil serviceName:nil];
+  [NSException raise:NSGenericException format:@"Do not call init method of ProtoService"];
+  return [self initWithHost:nil packageName:nil serviceName:nil callOptions:nil];
 }
+#pragma clang diagnostic pop
 
 // Designated initializer
 - (instancetype)initWithHost:(NSString *)host
@@ -58,38 +64,6 @@
   return self;
 }
 
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
-// Do not call designated initializer here due to nullability incompatibility. This method is from
-// old API and does not assert on nullability of the parameters.
-
-- (instancetype)initWithHost:(NSString *)host
-                 packageName:(NSString *)packageName
-                 serviceName:(NSString *)serviceName {
-  if ((self = [super init])) {
-    _host = [host copy];
-    _packageName = [packageName copy];
-    _serviceName = [serviceName copy];
-    _callOptions = nil;
-  }
-  return self;
-}
-
-#pragma clang diagnostic pop
-
-- (GRPCProtoCall *)RPCToMethod:(NSString *)method
-                requestsWriter:(GRXWriter *)requestsWriter
-                 responseClass:(Class)responseClass
-            responsesWriteable:(id<GRXWriteable>)responsesWriteable {
-  GRPCProtoMethod *methodName =
-      [[GRPCProtoMethod alloc] initWithPackage:_packageName service:_serviceName method:method];
-  return [[GRPCProtoCall alloc] initWithHost:_host
-                                      method:methodName
-                              requestsWriter:requestsWriter
-                               responseClass:responseClass
-                          responsesWriteable:responsesWriteable];
-}
-
 - (GRPCUnaryProtoCall *)RPCToMethod:(NSString *)method
                             message:(id)message
                     responseHandler:(id<GRPCProtoResponseHandler>)handler
diff --git a/src/objective-c/ProtoRPC/ProtoServiceLegacy.h b/src/objective-c/ProtoRPC/ProtoServiceLegacy.h
new file mode 100644
index 0000000000000000000000000000000000000000..7b0b7a4de7e3814de01fb4123ba7f215b824bdaa
--- /dev/null
+++ b/src/objective-c/ProtoRPC/ProtoServiceLegacy.h
@@ -0,0 +1,23 @@
+/*
+ *
+ * Copyright 2019 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 "ProtoService.h"
+
+@class GRPCProtoCall;
+@class GRXWriter;
+@protocol GRXWriteable;
diff --git a/src/objective-c/ProtoRPC/ProtoServiceLegacy.m b/src/objective-c/ProtoRPC/ProtoServiceLegacy.m
new file mode 100644
index 0000000000000000000000000000000000000000..6aa64955381119add6c538d515c3214094c6bfd9
--- /dev/null
+++ b/src/objective-c/ProtoRPC/ProtoServiceLegacy.m
@@ -0,0 +1,71 @@
+/*
+ *
+ * Copyright 2019 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 <objc/runtime.h>
+
+#import "ProtoMethod.h"
+#import "ProtoRPCLegacy.h"
+#import "ProtoService.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-implementations"
+@implementation ProtoService (Legacy)
+#pragma clang diagnostic pop
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wobjc-designated-initializers"
+// Do not call designated initializer here due to nullability incompatibility. This method is from
+// old API and does not assert on nullability of the parameters.
+
+- (instancetype)initWithHost:(NSString *)host
+                 packageName:(NSString *)packageName
+                 serviceName:(NSString *)serviceName {
+  if ((self = [super init])) {
+    Ivar hostIvar = class_getInstanceVariable([ProtoService class], "_host");
+    Ivar packageNameIvar = class_getInstanceVariable([ProtoService class], "_packageName");
+    Ivar serviceNameIvar = class_getInstanceVariable([ProtoService class], "_serviceName");
+
+    object_setIvar(self, hostIvar, [host copy]);
+    object_setIvar(self, packageNameIvar, [packageName copy]);
+    object_setIvar(self, serviceNameIvar, [serviceName copy]);
+  }
+  return self;
+}
+#pragma clang diagnostic pop
+
+- (GRPCProtoCall *)RPCToMethod:(NSString *)method
+                requestsWriter:(GRXWriter *)requestsWriter
+                 responseClass:(Class)responseClass
+            responsesWriteable:(id<GRXWriteable>)responsesWriteable {
+  Ivar hostIvar = class_getInstanceVariable([ProtoService class], "_host");
+  Ivar packageNameIvar = class_getInstanceVariable([ProtoService class], "_packageName");
+  Ivar serviceNameIvar = class_getInstanceVariable([ProtoService class], "_serviceName");
+  NSString *host = object_getIvar(self, hostIvar);
+  NSString *packageName = object_getIvar(self, packageNameIvar);
+  NSString *serviceName = object_getIvar(self, serviceNameIvar);
+
+  GRPCProtoMethod *methodName =
+      [[GRPCProtoMethod alloc] initWithPackage:packageName service:serviceName method:method];
+  return [[GRPCProtoCall alloc] initWithHost:host
+                                      method:methodName
+                              requestsWriter:requestsWriter
+                               responseClass:responseClass
+                          responsesWriteable:responsesWriteable];
+}
+
+@end
diff --git a/src/objective-c/examples/tvOS-sample/tvOS-sample/ViewController.m b/src/objective-c/examples/tvOS-sample/tvOS-sample/ViewController.m
index c94ee8c256977346074e13307302330928265543..9c46626012161df775b166068624c848644c8e04 100644
--- a/src/objective-c/examples/tvOS-sample/tvOS-sample/ViewController.m
+++ b/src/objective-c/examples/tvOS-sample/tvOS-sample/ViewController.m
@@ -25,6 +25,8 @@
 #import "src/objective-c/examples/RemoteTestClient/Messages.pbobjc.h"
 #import "src/objective-c/examples/RemoteTestClient/Test.pbrpc.h"
 #endif
+#import <GRPCClient/GRPCCallOptions.h>
+#import <ProtoRPC/ProtoRPC.h>
 
 @interface ViewController ()<GRPCProtoResponseHandler>
 
diff --git a/src/objective-c/examples/watchOS-sample/WatchKit-Extension/InterfaceController.m b/src/objective-c/examples/watchOS-sample/WatchKit-Extension/InterfaceController.m
index 3e97767cf0cffd3bae072f1cdc81b7e62965a722..ce204a9c1462e92a52ff6fd071e2d6287b5ac146 100644
--- a/src/objective-c/examples/watchOS-sample/WatchKit-Extension/InterfaceController.m
+++ b/src/objective-c/examples/watchOS-sample/WatchKit-Extension/InterfaceController.m
@@ -24,6 +24,8 @@
 #import "src/objective-c/examples/RemoteTestClient/Messages.pbobjc.h"
 #import "src/objective-c/examples/RemoteTestClient/Test.pbrpc.h"
 #endif
+#import <GRPCClient/GRPCCallOptions.h>
+#import <ProtoRPC/ProtoRPC.h>
 
 @interface InterfaceController ()<GRPCProtoResponseHandler>
 
diff --git a/src/objective-c/tests/BUILD b/src/objective-c/tests/BUILD
index fed92596b17d2a640c73820571d4d0f82a667179..65a32d742a910f4f6cb41075c510c231be0ec24c 100644
--- a/src/objective-c/tests/BUILD
+++ b/src/objective-c/tests/BUILD
@@ -95,19 +95,12 @@ tvos_application(
     deps = ["host-lib"],
 )
 
-grpc_objc_testing_library(
-    name = "CronetConfig",
-    srcs = ["ConfigureCronet.m"],
-    hdrs = ["ConfigureCronet.h"],
-)
-
 grpc_objc_testing_library(
     name = "InteropTests-lib",
     hdrs = ["InteropTests/InteropTests.h"],
     srcs = ["InteropTests/InteropTests.m"],
     deps = [
         ":InteropTestsBlockCallbacks-lib",
-        ":CronetConfig",
     ],
 )
 
@@ -200,7 +193,6 @@ ios_unit_test(
         ":InteropTestsRemote-lib",
         ":InteropTestsLocalSSL-lib",
         ":InteropTestsLocalCleartext-lib",
-        # ":InteropTestsMulitpleChannels-lib", # needs Cronet
     ],
     test_host = ":ios-host",
 )
diff --git a/src/objective-c/tests/ConfigureCronet.h b/src/objective-c/tests/ConfigureCronet.h
index cc5c038f3c61db280d20509297356b5d44dd9c74..ba0efc4df663dca679955f7febc258319ee541b6 100644
--- a/src/objective-c/tests/ConfigureCronet.h
+++ b/src/objective-c/tests/ConfigureCronet.h
@@ -16,8 +16,6 @@
  *
  */
 
-#ifdef GRPC_COMPILE_WITH_CRONET
-
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -30,5 +28,3 @@ void configureCronet(void);
 #ifdef __cplusplus
 }
 #endif
-
-#endif
diff --git a/src/objective-c/tests/ConfigureCronet.m b/src/objective-c/tests/ConfigureCronet.m
index ab137e28cad0875438bb0a0d38c3d4e93024ee0f..6fc7e0ee9f59a4fa3d89f2e7ebb3ad4507877a69 100644
--- a/src/objective-c/tests/ConfigureCronet.m
+++ b/src/objective-c/tests/ConfigureCronet.m
@@ -16,8 +16,6 @@
  *
  */
 
-#ifdef GRPC_COMPILE_WITH_CRONET
-
 #import "ConfigureCronet.h"
 #import <Cronet/Cronet.h>
 
@@ -35,5 +33,3 @@ void configureCronet(void) {
     [Cronet startNetLogToFile:@"cronet_netlog.json" logBytes:YES];
   });
 }
-
-#endif
diff --git a/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m b/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m
index a2a79c463167d851df013e1501f2b1544f7eed34..aa1af301b960795fae74b35eea1092c77b38346f 100644
--- a/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m
+++ b/src/objective-c/tests/CronetTests/InteropTestsRemoteWithCronet.m
@@ -22,6 +22,7 @@
 #import <Cronet/Cronet.h>
 #import <GRPCClient/GRPCCall+Cronet.h>
 
+#import "../ConfigureCronet.h"
 #import "InteropTests.h"
 
 // The server address is derived from preprocessor macro, which is
@@ -40,12 +41,19 @@ static int32_t kRemoteInteropServerOverhead = 12;
 
 @implementation InteropTestsRemoteWithCronet
 
++ (void)setUp {
+  configureCronet();
+  [GRPCCall useCronetWithEngine:[Cronet getGlobalEngine]];
+
+  [super setUp];
+}
+
 + (NSString *)host {
   return kRemoteSSLHost;
 }
 
-+ (BOOL)useCronet {
-  return YES;
++ (GRPCTransportId)transport {
+  return gGRPCCoreCronetId;
 }
 
 - (int32_t)encodingOverhead {
diff --git a/src/objective-c/tests/InteropTests/InteropTests.h b/src/objective-c/tests/InteropTests/InteropTests.h
index 28fcbff9695a7dafaffd066244cf1946b5e31e0a..a4adecd541574ac1e8da434ed124582ca2f2676d 100644
--- a/src/objective-c/tests/InteropTests/InteropTests.h
+++ b/src/objective-c/tests/InteropTests/InteropTests.h
@@ -48,11 +48,19 @@
 - (int32_t)encodingOverhead;
 
 /**
+ * DEPRECATED: \a transportType is a deprecated option. Please use \a transport instead.
+ *
  * The type of transport to be used. The base implementation returns default. Subclasses should
  * override to appropriate settings.
  */
 + (GRPCTransportType)transportType;
 
+/*
+ * The transport to be used. The base implementation returns NULL. Subclasses should override to
+ * appropriate settings.
+ */
++ (GRPCTransportId)transport;
+
 /**
  * The root certificates to be used. The base implementation returns nil. Subclasses should override
  * to appropriate settings.
@@ -65,9 +73,4 @@
  */
 + (NSString *)hostNameOverride;
 
-/**
- * Whether to use Cronet for all the v1 API tests in the test suite.
- */
-+ (BOOL)useCronet;
-
 @end
diff --git a/src/objective-c/tests/InteropTests/InteropTests.m b/src/objective-c/tests/InteropTests/InteropTests.m
index a8f7db7ee9343bad1af9961e027fe366fb216c58..21198f7bad9762b6e8ceb7b5b29ef4ff656c0c18 100644
--- a/src/objective-c/tests/InteropTests/InteropTests.m
+++ b/src/objective-c/tests/InteropTests/InteropTests.m
@@ -20,9 +20,6 @@
 
 #include <grpc/status.h>
 
-#ifdef GRPC_COMPILE_WITH_CRONET
-#import <Cronet/Cronet.h>
-#endif
 #import <GRPCClient/GRPCCall+ChannelArg.h>
 #import <GRPCClient/GRPCCall+Cronet.h>
 #import <GRPCClient/GRPCCall+Interceptor.h>
@@ -38,7 +35,6 @@
 #import "src/objective-c/tests/RemoteTestClient/Test.pbobjc.h"
 #import "src/objective-c/tests/RemoteTestClient/Test.pbrpc.h"
 
-#import "../ConfigureCronet.h"
 #import "InteropTestsBlockCallbacks.h"
 
 #define TEST_TIMEOUT 32
@@ -91,9 +87,8 @@ BOOL isRemoteInteropTest(NSString *host) {
 
 - (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager {
   dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
-  return [[GRPCInterceptor alloc] initWithInterceptorManager:interceptorManager
-                                        requestDispatchQueue:queue
-                                       responseDispatchQueue:queue];
+  return
+      [[GRPCInterceptor alloc] initWithInterceptorManager:interceptorManager dispatchQueue:queue];
 }
 
 @end
@@ -101,21 +96,19 @@ BOOL isRemoteInteropTest(NSString *host) {
 @interface HookInterceptorFactory : NSObject<GRPCInterceptorFactory>
 
 - (instancetype)
-initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
-       responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
-                   startHook:(void (^)(GRPCRequestOptions *requestOptions,
-                                       GRPCCallOptions *callOptions,
-                                       GRPCInterceptorManager *manager))startHook
-               writeDataHook:(void (^)(id data, GRPCInterceptorManager *manager))writeDataHook
-                  finishHook:(void (^)(GRPCInterceptorManager *manager))finishHook
-     receiveNextMessagesHook:(void (^)(NSUInteger numberOfMessages,
-                                       GRPCInterceptorManager *manager))receiveNextMessagesHook
-          responseHeaderHook:(void (^)(NSDictionary *initialMetadata,
-                                       GRPCInterceptorManager *manager))responseHeaderHook
-            responseDataHook:(void (^)(id data, GRPCInterceptorManager *manager))responseDataHook
-           responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
-                                       GRPCInterceptorManager *manager))responseCloseHook
-            didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook;
+  initWithDispatchQueue:(dispatch_queue_t)dispatchQueue
+              startHook:(void (^)(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
+                                  GRPCInterceptorManager *manager))startHook
+          writeDataHook:(void (^)(id data, GRPCInterceptorManager *manager))writeDataHook
+             finishHook:(void (^)(GRPCInterceptorManager *manager))finishHook
+receiveNextMessagesHook:(void (^)(NSUInteger numberOfMessages,
+                                  GRPCInterceptorManager *manager))receiveNextMessagesHook
+     responseHeaderHook:(void (^)(NSDictionary *initialMetadata,
+                                  GRPCInterceptorManager *manager))responseHeaderHook
+       responseDataHook:(void (^)(id data, GRPCInterceptorManager *manager))responseDataHook
+      responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
+                                  GRPCInterceptorManager *manager))responseCloseHook
+       didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook;
 
 - (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager;
 
@@ -125,8 +118,7 @@ initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
 
 - (instancetype)
 initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
-      requestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
-     responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
+             dispatchQueue:(dispatch_queue_t)dispatchQueue
                  startHook:(void (^)(GRPCRequestOptions *requestOptions,
                                      GRPCCallOptions *callOptions,
                                      GRPCInterceptorManager *manager))startHook
@@ -155,29 +147,25 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
   void (^_responseCloseHook)(NSDictionary *trailingMetadata, NSError *error,
                              GRPCInterceptorManager *manager);
   void (^_didWriteDataHook)(GRPCInterceptorManager *manager);
-  dispatch_queue_t _requestDispatchQueue;
-  dispatch_queue_t _responseDispatchQueue;
+  dispatch_queue_t _dispatchQueue;
 }
 
 - (instancetype)
-initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
-       responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
-                   startHook:(void (^)(GRPCRequestOptions *requestOptions,
-                                       GRPCCallOptions *callOptions,
-                                       GRPCInterceptorManager *manager))startHook
-               writeDataHook:(void (^)(id data, GRPCInterceptorManager *manager))writeDataHook
-                  finishHook:(void (^)(GRPCInterceptorManager *manager))finishHook
-     receiveNextMessagesHook:(void (^)(NSUInteger numberOfMessages,
-                                       GRPCInterceptorManager *manager))receiveNextMessagesHook
-          responseHeaderHook:(void (^)(NSDictionary *initialMetadata,
-                                       GRPCInterceptorManager *manager))responseHeaderHook
-            responseDataHook:(void (^)(id data, GRPCInterceptorManager *manager))responseDataHook
-           responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
-                                       GRPCInterceptorManager *manager))responseCloseHook
-            didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook {
+  initWithDispatchQueue:(dispatch_queue_t)dispatchQueue
+              startHook:(void (^)(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
+                                  GRPCInterceptorManager *manager))startHook
+          writeDataHook:(void (^)(id data, GRPCInterceptorManager *manager))writeDataHook
+             finishHook:(void (^)(GRPCInterceptorManager *manager))finishHook
+receiveNextMessagesHook:(void (^)(NSUInteger numberOfMessages,
+                                  GRPCInterceptorManager *manager))receiveNextMessagesHook
+     responseHeaderHook:(void (^)(NSDictionary *initialMetadata,
+                                  GRPCInterceptorManager *manager))responseHeaderHook
+       responseDataHook:(void (^)(id data, GRPCInterceptorManager *manager))responseDataHook
+      responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
+                                  GRPCInterceptorManager *manager))responseCloseHook
+       didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook {
   if ((self = [super init])) {
-    _requestDispatchQueue = requestDispatchQueue;
-    _responseDispatchQueue = responseDispatchQueue;
+    _dispatchQueue = dispatchQueue;
     _startHook = startHook;
     _writeDataHook = writeDataHook;
     _finishHook = finishHook;
@@ -192,8 +180,7 @@ initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
 
 - (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager {
   return [[HookInterceptor alloc] initWithInterceptorManager:interceptorManager
-                                        requestDispatchQueue:_requestDispatchQueue
-                                       responseDispatchQueue:_responseDispatchQueue
+                                               dispatchQueue:_dispatchQueue
                                                    startHook:_startHook
                                                writeDataHook:_writeDataHook
                                                   finishHook:_finishHook
@@ -218,22 +205,16 @@ initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
                              GRPCInterceptorManager *manager);
   void (^_didWriteDataHook)(GRPCInterceptorManager *manager);
   GRPCInterceptorManager *_manager;
-  dispatch_queue_t _requestDispatchQueue;
-  dispatch_queue_t _responseDispatchQueue;
-}
-
-- (dispatch_queue_t)requestDispatchQueue {
-  return _requestDispatchQueue;
+  dispatch_queue_t _dispatchQueue;
 }
 
 - (dispatch_queue_t)dispatchQueue {
-  return _responseDispatchQueue;
+  return _dispatchQueue;
 }
 
 - (instancetype)
 initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
-      requestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
-     responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue
+             dispatchQueue:(dispatch_queue_t)dispatchQueue
                  startHook:(void (^)(GRPCRequestOptions *requestOptions,
                                      GRPCCallOptions *callOptions,
                                      GRPCInterceptorManager *manager))startHook
@@ -247,9 +228,7 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
          responseCloseHook:(void (^)(NSDictionary *trailingMetadata, NSError *error,
                                      GRPCInterceptorManager *manager))responseCloseHook
           didWriteDataHook:(void (^)(GRPCInterceptorManager *manager))didWriteDataHook {
-  if ((self = [super initWithInterceptorManager:interceptorManager
-                           requestDispatchQueue:requestDispatchQueue
-                          responseDispatchQueue:responseDispatchQueue])) {
+  if ((self = [super initWithInterceptorManager:interceptorManager dispatchQueue:dispatchQueue])) {
     _startHook = startHook;
     _writeDataHook = writeDataHook;
     _finishHook = finishHook;
@@ -258,8 +237,7 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
     _responseDataHook = responseDataHook;
     _responseCloseHook = responseCloseHook;
     _didWriteDataHook = didWriteDataHook;
-    _requestDispatchQueue = requestDispatchQueue;
-    _responseDispatchQueue = responseDispatchQueue;
+    _dispatchQueue = dispatchQueue;
     _manager = interceptorManager;
   }
   return self;
@@ -320,8 +298,7 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
 
 @property BOOL enabled;
 
-- (instancetype)initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
-                       responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue;
+- (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue;
 
 - (void)setStartHook:(void (^)(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
                                GRPCInterceptorManager *manager))startHook
@@ -340,26 +317,23 @@ initWithInterceptorManager:(GRPCInterceptorManager *)interceptorManager
 
 @implementation GlobalInterceptorFactory
 
-- (instancetype)initWithRequestDispatchQueue:(dispatch_queue_t)requestDispatchQueue
-                       responseDispatchQueue:(dispatch_queue_t)responseDispatchQueue {
+- (instancetype)initWithDispatchQueue:(dispatch_queue_t)dispatchQueue {
   _enabled = NO;
-  return [super initWithRequestDispatchQueue:requestDispatchQueue
-                       responseDispatchQueue:responseDispatchQueue
-                                   startHook:nil
-                               writeDataHook:nil
-                                  finishHook:nil
-                     receiveNextMessagesHook:nil
-                          responseHeaderHook:nil
-                            responseDataHook:nil
-                           responseCloseHook:nil
-                            didWriteDataHook:nil];
+  return [super initWithDispatchQueue:dispatchQueue
+                            startHook:nil
+                        writeDataHook:nil
+                           finishHook:nil
+              receiveNextMessagesHook:nil
+                   responseHeaderHook:nil
+                     responseDataHook:nil
+                    responseCloseHook:nil
+                     didWriteDataHook:nil];
 }
 
 - (GRPCInterceptor *)createInterceptorWithManager:(GRPCInterceptorManager *)interceptorManager {
   if (_enabled) {
     return [[HookInterceptor alloc] initWithInterceptorManager:interceptorManager
-                                          requestDispatchQueue:_requestDispatchQueue
-                                         responseDispatchQueue:_responseDispatchQueue
+                                                 dispatchQueue:_dispatchQueue
                                                      startHook:_startHook
                                                  writeDataHook:_writeDataHook
                                                     finishHook:_finishHook
@@ -425,10 +399,15 @@ static dispatch_once_t initGlobalInterceptorFactory;
   return 0;
 }
 
+// For backwards compatibility
 + (GRPCTransportType)transportType {
   return GRPCTransportTypeChttp2BoringSSL;
 }
 
++ (GRPCTransportId)transport {
+  return NULL;
+}
+
 + (NSString *)PEMRootCertificates {
   return nil;
 }
@@ -437,26 +416,11 @@ static dispatch_once_t initGlobalInterceptorFactory;
   return nil;
 }
 
-+ (BOOL)useCronet {
-  return NO;
-}
-
 + (void)setUp {
-#ifdef GRPC_COMPILE_WITH_CRONET
-  configureCronet();
-  if ([self useCronet]) {
-    [GRPCCall useCronetWithEngine:[Cronet getGlobalEngine]];
-  }
-#endif
-#ifdef GRPC_CFSTREAM
-  setenv(kCFStreamVarName, "1", 1);
-#endif
-
   dispatch_once(&initGlobalInterceptorFactory, ^{
     dispatch_queue_t globalInterceptorQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
     globalInterceptorFactory =
-        [[GlobalInterceptorFactory alloc] initWithRequestDispatchQueue:globalInterceptorQueue
-                                                 responseDispatchQueue:globalInterceptorQueue];
+        [[GlobalInterceptorFactory alloc] initWithDispatchQueue:globalInterceptorQueue];
     [GRPCCall2 registerGlobalInterceptor:globalInterceptorFactory];
   });
 }
@@ -502,7 +466,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
 
   GPBEmpty *request = [GPBEmpty message];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
 
@@ -531,7 +497,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
 
   GPBEmpty *request = [GPBEmpty message];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
 
@@ -608,7 +576,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   request.payload.body = [NSMutableData dataWithLength:271828];
 
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
 
@@ -656,7 +626,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
       request.responseStatus.code = GRPC_STATUS_CANCELLED;
     }
     GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+    // For backwards compatibility
     options.transportType = [[self class] transportType];
+    options.transport = [[self class] transport];
     options.PEMRootCertificates = [[self class] PEMRootCertificates];
     options.hostNameOverride = [[self class] hostNameOverride];
 
@@ -958,7 +930,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
                                                requestedResponseSize:responses[index]];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
 
@@ -1010,7 +984,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
                                                requestedResponseSize:responses[index]];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
   options.flowControlEnabled = YES;
@@ -1167,7 +1143,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   __block BOOL receivedResponse = NO;
 
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = self.class.transportType;
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = self.class.PEMRootCertificates;
   options.hostNameOverride = [[self class] hostNameOverride];
 
@@ -1200,7 +1178,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
       [self expectationWithDescription:@"Call completed."];
 
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = self.class.transportType;
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = self.class.PEMRootCertificates;
   options.hostNameOverride = [[self class] hostNameOverride];
 
@@ -1286,48 +1266,47 @@ static dispatch_once_t initGlobalInterceptorFactory;
   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
 }
 
-#ifndef GRPC_COMPILE_WITH_CRONET
-- (void)testKeepalive {
+- (void)testKeepaliveWithV2API {
   XCTAssertNotNil([[self class] host]);
+  if ([[self class] transport] == gGRPCCoreCronetId) {
+    // Cronet does not support keepalive
+    return;
+  }
   __weak XCTestExpectation *expectation = [self expectationWithDescription:@"Keepalive"];
 
-  [GRPCCall setKeepaliveWithInterval:1500 timeout:0 forHost:[[self class] host]];
-
-  NSArray *requests = @[ @27182, @8 ];
-  NSArray *responses = @[ @31415, @9 ];
-
-  GRXBufferedPipe *requestsBuffer = [[GRXBufferedPipe alloc] init];
-
-  __block int index = 0;
+  NSNumber *kRequestSize = @27182;
+  NSNumber *kResponseSize = @31415;
 
-  id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
-                                               requestedResponseSize:responses[index]];
-  [requestsBuffer writeValue:request];
+  id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:kRequestSize
+                                               requestedResponseSize:kResponseSize];
+  GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
+  options.PEMRootCertificates = [[self class] PEMRootCertificates];
+  options.hostNameOverride = [[self class] hostNameOverride];
+  options.keepaliveInterval = 1.5;
+  options.keepaliveTimeout = 0;
 
-  [_service
-      fullDuplexCallWithRequestsWriter:requestsBuffer
-                          eventHandler:^(BOOL done, RMTStreamingOutputCallResponse *response,
-                                         NSError *error) {
-                            if (index == 0) {
-                              XCTAssertNil(error, @"Finished with unexpected error: %@", error);
-                              XCTAssertTrue(response, @"Event handler called without an event.");
-                              XCTAssertFalse(done);
-                              index++;
-                            } else {
-                              // Keepalive should kick after 1s elapsed and fails the call.
-                              XCTAssertNotNil(error);
-                              XCTAssertEqual(error.code, GRPC_STATUS_UNAVAILABLE);
-                              XCTAssertEqualObjects(
-                                  error.localizedDescription, @"keepalive watchdog timeout",
-                                  @"Unexpected failure that is not keepalive watchdog timeout.");
-                              XCTAssertTrue(done);
-                              [expectation fulfill];
-                            }
-                          }];
+  __block GRPCStreamingProtoCall *call = [_service
+      fullDuplexCallWithResponseHandler:
+          [[InteropTestsBlockCallbacks alloc]
+              initWithInitialMetadataCallback:nil
+                              messageCallback:nil
+                                closeCallback:^(NSDictionary *trailingMetadata, NSError *error) {
+                                  XCTAssertNotNil(error);
+                                  XCTAssertEqual(
+                                      error.code, GRPC_STATUS_UNAVAILABLE,
+                                      @"Received status %ld instead of UNAVAILABLE (14).",
+                                      error.code);
+                                  [expectation fulfill];
+                                }]
+                            callOptions:options];
+  [call writeMessage:request];
+  [call start];
 
   [self waitForExpectationsWithTimeout:TEST_TIMEOUT handler:nil];
+  [call finish];
 }
-#endif
 
 - (void)testDefaultInterceptor {
   XCTAssertNotNil([[self class] host]);
@@ -1342,7 +1321,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
                                                requestedResponseSize:responses[index]];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
   options.interceptorFactories = @[ [[DefaultInterceptorFactory alloc] init] ];
@@ -1397,8 +1378,7 @@ static dispatch_once_t initGlobalInterceptorFactory;
   __block NSUInteger responseCloseCount = 0;
   __block NSUInteger didWriteDataCount = 0;
   id<GRPCInterceptorFactory> factory = [[HookInterceptorFactory alloc]
-      initWithRequestDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
-      responseDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
+      initWithDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
       startHook:^(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
                   GRPCInterceptorManager *manager) {
         startCount++;
@@ -1446,7 +1426,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
                                                requestedResponseSize:responses[index]];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
   options.flowControlEnabled = YES;
@@ -1524,8 +1506,7 @@ static dispatch_once_t initGlobalInterceptorFactory;
   __block NSUInteger responseDataCount = 0;
   __block NSUInteger responseCloseCount = 0;
   id<GRPCInterceptorFactory> factory = [[HookInterceptorFactory alloc]
-      initWithRequestDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
-      responseDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
+      initWithDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
       startHook:^(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
                   GRPCInterceptorManager *manager) {
         startCount++;
@@ -1552,6 +1533,7 @@ static dispatch_once_t initGlobalInterceptorFactory;
         // finish must happen after the hijacking, so directly reply with a close
         [manager forwardPreviousInterceptorCloseWithTrailingMetadata:@{@"grpc-status" : @"0"}
                                                                error:nil];
+        [manager shutDown];
       }
       receiveNextMessagesHook:nil
       responseHeaderHook:^(NSDictionary *initialMetadata, GRPCInterceptorManager *manager) {
@@ -1578,7 +1560,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
                                                requestedResponseSize:responses[index]];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
   options.interceptorFactories = @[ [[DefaultInterceptorFactory alloc] init], factory ];
@@ -1687,7 +1671,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
                                                requestedResponseSize:responses[index]];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
   options.flowControlEnabled = YES;
@@ -1742,16 +1728,15 @@ static dispatch_once_t initGlobalInterceptorFactory;
 
 - (void)testConflictingGlobalInterceptors {
   id<GRPCInterceptorFactory> factory = [[HookInterceptorFactory alloc]
-      initWithRequestDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
-             responseDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
-                         startHook:nil
-                     writeDataHook:nil
-                        finishHook:nil
-           receiveNextMessagesHook:nil
-                responseHeaderHook:nil
-                  responseDataHook:nil
-                 responseCloseHook:nil
-                  didWriteDataHook:nil];
+        initWithDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
+                    startHook:nil
+                writeDataHook:nil
+                   finishHook:nil
+      receiveNextMessagesHook:nil
+           responseHeaderHook:nil
+             responseDataHook:nil
+            responseCloseHook:nil
+             didWriteDataHook:nil];
   @try {
     [GRPCCall2 registerGlobalInterceptor:factory];
     XCTFail(@"Did not receive an exception when registering global interceptor the second time");
@@ -1775,8 +1760,7 @@ static dispatch_once_t initGlobalInterceptorFactory;
   __block NSUInteger didWriteDataCount = 0;
 
   id<GRPCInterceptorFactory> factory = [[HookInterceptorFactory alloc]
-      initWithRequestDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
-      responseDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
+      initWithDispatchQueue:dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL)
       startHook:^(GRPCRequestOptions *requestOptions, GRPCCallOptions *callOptions,
                   GRPCInterceptorManager *manager) {
         startCount++;
@@ -1872,7 +1856,9 @@ static dispatch_once_t initGlobalInterceptorFactory;
   id request = [RMTStreamingOutputCallRequest messageWithPayloadSize:requests[index]
                                                requestedResponseSize:responses[index]];
   GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+  // For backwards compatibility
   options.transportType = [[self class] transportType];
+  options.transport = [[self class] transport];
   options.PEMRootCertificates = [[self class] PEMRootCertificates];
   options.hostNameOverride = [[self class] hostNameOverride];
   options.flowControlEnabled = YES;
diff --git a/src/objective-c/tests/InteropTests/InteropTestsLocalCleartext.m b/src/objective-c/tests/InteropTests/InteropTestsLocalCleartext.m
index a9c6918333229e261b3a57692dde6297ae604f92..2e638099e1e7b2fe24133248da47afaede6a005c 100644
--- a/src/objective-c/tests/InteropTests/InteropTestsLocalCleartext.m
+++ b/src/objective-c/tests/InteropTests/InteropTestsLocalCleartext.m
@@ -17,6 +17,7 @@
  */
 
 #import <GRPCClient/GRPCCall+Tests.h>
+#import <GRPCClient/GRPCTransport.h>
 #import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
 
 #import "InteropTests.h"
@@ -60,8 +61,8 @@ static int32_t kLocalInteropServerOverhead = 10;
   [GRPCCall useInsecureConnectionsForHost:kLocalCleartextHost];
 }
 
-+ (GRPCTransportType)transportType {
-  return GRPCTransportTypeInsecure;
++ (GRPCTransportId)transport {
+  return GRPCDefaultTransportImplList.core_insecure;
 }
 
 @end
diff --git a/src/objective-c/tests/InteropTests/InteropTestsLocalSSL.m b/src/objective-c/tests/InteropTests/InteropTestsLocalSSL.m
index e8222f602f4a30510881925fc65442b7d59a14d0..30d8f4c34af0db6fd3127f9ec547904b32f2f19b 100644
--- a/src/objective-c/tests/InteropTests/InteropTestsLocalSSL.m
+++ b/src/objective-c/tests/InteropTests/InteropTestsLocalSSL.m
@@ -17,6 +17,7 @@
  */
 
 #import <GRPCClient/GRPCCall+Tests.h>
+#import <GRPCClient/GRPCTransport.h>
 #import <GRPCClient/internal_testing/GRPCCall+InternalTests.h>
 
 #import "InteropTests.h"
@@ -56,8 +57,8 @@ static int32_t kLocalInteropServerOverhead = 10;
   return kLocalInteropServerOverhead;  // bytes
 }
 
-+ (GRPCTransportType)transportType {
-  return GRPCTransportTypeChttp2BoringSSL;
++ (GRPCTransportId)transport {
+  return GRPCDefaultTransportImplList.core_secure;
 }
 
 - (void)setUp {
diff --git a/src/objective-c/tests/InteropTests/InteropTestsMultipleChannels.m b/src/objective-c/tests/InteropTests/InteropTestsMultipleChannels.m
index 98893a466bd8bead3015b232a1069af0bf94409c..dc48391cbccb9d2a56f364da070fcf82d158fa3d 100644
--- a/src/objective-c/tests/InteropTests/InteropTestsMultipleChannels.m
+++ b/src/objective-c/tests/InteropTests/InteropTestsMultipleChannels.m
@@ -18,9 +18,8 @@
 
 #import <XCTest/XCTest.h>
 
-#ifdef GRPC_COMPILE_WITH_CRONET
 #import <Cronet/Cronet.h>
-#endif
+#import <GRPCClient/GRPCCallOptions.h>
 #import <RxLibrary/GRXBufferedPipe.h>
 #import "src/objective-c/tests/RemoteTestClient/Messages.pbobjc.h"
 #import "src/objective-c/tests/RemoteTestClient/Test.pbobjc.h"
diff --git a/src/objective-c/tests/InteropTests/InteropTestsRemote.m b/src/objective-c/tests/InteropTests/InteropTestsRemote.m
index c1cd9b81efc7da2695b40c7916ac3b9cb7312aa2..2dd8f0aed89e7e3d2d01a2eb1e90d2f120c2c929 100644
--- a/src/objective-c/tests/InteropTests/InteropTestsRemote.m
+++ b/src/objective-c/tests/InteropTests/InteropTestsRemote.m
@@ -53,14 +53,8 @@ static int32_t kRemoteInteropServerOverhead = 12;
   return kRemoteInteropServerOverhead;  // bytes
 }
 
-#ifdef GRPC_COMPILE_WITH_CRONET
-+ (GRPCTransportType)transportType {
-  return GRPCTransportTypeCronet;
-}
-#else
 + (GRPCTransportType)transportType {
   return GRPCTransportTypeChttp2BoringSSL;
 }
-#endif
 
 @end
diff --git a/src/objective-c/tests/Podfile b/src/objective-c/tests/Podfile
index c2297aa00fdbbcabdcc52ccf827f53f1fb1618ca..c83e8861e933b40e09085e4fcaaef365ee815a7d 100644
--- a/src/objective-c/tests/Podfile
+++ b/src/objective-c/tests/Podfile
@@ -30,25 +30,25 @@ target 'MacTests' do
   grpc_deps
 end
 
-target 'UnitTests' do 
-  platform :ios, '8.0'
-  grpc_deps
-end
-
 %w(
+  UnitTests
   InteropTests
-  CronetTests
 ).each do |target_name|
   target target_name do
     platform :ios, '8.0'
     grpc_deps
-
-    pod 'gRPC-Core/Cronet-Implementation', :path => GRPC_LOCAL_SRC
-    pod 'CronetFramework', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c"
-    pod 'gRPC-Core/Tests', :path => GRPC_LOCAL_SRC, :inhibit_warnings => true
   end
 end
 
+target 'CronetTests' do
+  platform :ios, '8.0'
+  grpc_deps
+
+  pod 'gRPC/GRPCCoreCronet',           :path => GRPC_LOCAL_SRC
+  pod 'CronetFramework', :podspec => "#{GRPC_LOCAL_SRC}/src/objective-c"
+  pod 'gRPC-Core/Tests', :path => GRPC_LOCAL_SRC, :inhibit_warnings => true
+end
+
 # gRPC-Core.podspec needs to be modified to be successfully used for local development. A Podfile's
 # pre_install hook lets us do that. The block passed to it runs after the podspecs are downloaded
 # and before they are installed in the user project.
@@ -103,7 +103,7 @@ post_install do |installer|
     # the test target 'InteropTestsRemoteWithCronet'
     # Activate GRPCCall+InternalTests functions for the dedicated build configuration 'Test', which will
     # be used by all test targets using it.
-    if /gRPC-(mac|i|tv)OS/.match(target.name)
+    if /gRPC(-macOS|-iOS|-tvOS|\.|-[0-9a-f])/.match(target.name)
       target.build_configurations.each do |config|
         if config.name == 'Cronet'
           config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] = '$(inherited) COCOAPODS=1 GRPC_COMPILE_WITH_CRONET=1 GRPC_TEST_OBJC=1'
@@ -114,7 +114,7 @@ post_install do |installer|
     end
 
     # Enable NSAssert on gRPC
-    if /(gRPC|ProtoRPC|RxLibrary)-(mac|i|tv)OS/.match(target.name)
+    if /(gRPC|ProtoRPC|RxLibrary)/.match(target.name)
       target.build_configurations.each do |config|
         if config.name != 'Release'
           config.build_settings['ENABLE_NS_ASSERTIONS'] = 'YES'
diff --git a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
index a88838fdc8b779547e733eb3bd28221c044e3aa4..6cb9f5560b90e02679ffbdeecfb00450fd8244c0 100644
--- a/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
+++ b/src/objective-c/tests/Tests.xcodeproj/project.pbxproj
@@ -8,15 +8,14 @@
 
 /* Begin PBXBuildFile section */
 		5E0282E9215AA697007AC99D /* NSErrorUnitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E0282E8215AA697007AC99D /* NSErrorUnitTests.m */; };
+		5E08D07023021E3B006D76EA /* InteropTestsMultipleChannels.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E7F487722778226006656AD /* InteropTestsMultipleChannels.m */; };
 		5E3F14842278B461007C6D90 /* InteropTestsBlockCallbacks.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E3F14832278B461007C6D90 /* InteropTestsBlockCallbacks.m */; };
 		5E3F14852278BF5D007C6D90 /* InteropTestsBlockCallbacks.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E3F14832278B461007C6D90 /* InteropTestsBlockCallbacks.m */; };
 		5E3F14862278BFFF007C6D90 /* InteropTestsBlockCallbacks.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E3F14832278B461007C6D90 /* InteropTestsBlockCallbacks.m */; };
 		5E3F148D22792856007C6D90 /* ConfigureCronet.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E3F1487227918AA007C6D90 /* ConfigureCronet.m */; };
-		5E3F148E22792AF5007C6D90 /* ConfigureCronet.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E3F1487227918AA007C6D90 /* ConfigureCronet.m */; };
 		5E7F486422775B37006656AD /* InteropTestsRemoteWithCronet.m in Sources */ = {isa = PBXBuildFile; fileRef = 5EE84BF31D4717E40050C6CC /* InteropTestsRemoteWithCronet.m */; };
 		5E7F486522775B41006656AD /* CronetUnitTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5EAD6D261E27047400002378 /* CronetUnitTests.mm */; };
 		5E7F486E22778086006656AD /* CoreCronetEnd2EndTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 5E7F486D22778086006656AD /* CoreCronetEnd2EndTests.mm */; };
-		5E7F487922778226006656AD /* InteropTestsMultipleChannels.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E7F487722778226006656AD /* InteropTestsMultipleChannels.m */; };
 		5E7F487D22778256006656AD /* ChannelPoolTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E7F487B22778256006656AD /* ChannelPoolTest.m */; };
 		5E7F487E22778256006656AD /* ChannelTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E7F487C22778256006656AD /* ChannelTests.m */; };
 		5E7F4880227782C1006656AD /* APIv2Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 5E7F487F227782C1006656AD /* APIv2Tests.m */; };
@@ -34,6 +33,7 @@
 		5EA4770322736178000F72FC /* InteropTestsLocalSSL.m in Sources */ = {isa = PBXBuildFile; fileRef = 63E240CD1B6C4E2B005F3B0E /* InteropTestsLocalSSL.m */; };
 		5EA477042273617B000F72FC /* InteropTestsLocalCleartext.m in Sources */ = {isa = PBXBuildFile; fileRef = 63715F551B780C020029CB0B /* InteropTestsLocalCleartext.m */; };
 		5EA4770522736AC4000F72FC /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; };
+		5ECFED8623030DCC00626501 /* TestCertificates.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 63E240CF1B6C63DC005F3B0E /* TestCertificates.bundle */; };
 		65EB19E418B39A8374D407BB /* libPods-CronetTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B1511C20E16A8422B58D61A /* libPods-CronetTests.a */; };
 		903163C7FE885838580AEC7A /* libPods-InteropTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D457AD9797664CFA191C3280 /* libPods-InteropTests.a */; };
 		953CD2942A3A6D6CE695BE87 /* libPods-MacTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 276873A05AC5479B60DF6079 /* libPods-MacTests.a */; };
@@ -515,7 +515,6 @@
 				5EA476F12272816A000F72FC /* Frameworks */,
 				5EA476F22272816A000F72FC /* Resources */,
 				D11CB94CF56A1E53760D29D8 /* [CP] Copy Pods Resources */,
-				0FEFD5FC6B323AC95549AE4A /* [CP] Embed Pods Frameworks */,
 			);
 			buildRules = (
 			);
@@ -630,6 +629,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				5ECFED8623030DCC00626501 /* TestCertificates.bundle in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -678,24 +678,6 @@
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
-		0FEFD5FC6B323AC95549AE4A /* [CP] Embed Pods Frameworks */ = {
-			isa = PBXShellScriptBuildPhase;
-			buildActionMask = 2147483647;
-			files = (
-			);
-			inputPaths = (
-				"${PODS_ROOT}/Target Support Files/Pods-InteropTests/Pods-InteropTests-frameworks.sh",
-				"${PODS_ROOT}/CronetFramework/Cronet.framework",
-			);
-			name = "[CP] Embed Pods Frameworks";
-			outputPaths = (
-				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cronet.framework",
-			);
-			runOnlyForDeploymentPostprocessing = 0;
-			shellPath = /bin/sh;
-			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-InteropTests/Pods-InteropTests-frameworks.sh\"\n";
-			showEnvVarsInLog = 0;
-		};
 		292EA42A76AC7933A37235FD /* [CP] Embed Pods Frameworks */ = {
 			isa = PBXShellScriptBuildPhase;
 			buildActionMask = 2147483647;
@@ -721,7 +703,7 @@
 			);
 			inputPaths = (
 				"${PODS_ROOT}/Target Support Files/Pods-CronetTests/Pods-CronetTests-resources.sh",
-				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC-iOS/gRPCCertificates.bundle",
+				"${PODS_CONFIGURATION_BUILD_DIR}/gRPC.default-GRPCCoreCronet/gRPCCertificates.bundle",
 			);
 			name = "[CP] Copy Pods Resources";
 			outputPaths = (
@@ -898,6 +880,7 @@
 			files = (
 				5E3F14852278BF5D007C6D90 /* InteropTestsBlockCallbacks.m in Sources */,
 				5E3F148D22792856007C6D90 /* ConfigureCronet.m in Sources */,
+				5E08D07023021E3B006D76EA /* InteropTestsMultipleChannels.m in Sources */,
 				5E7F486E22778086006656AD /* CoreCronetEnd2EndTests.mm in Sources */,
 				5E7F488522778A88006656AD /* InteropTests.m in Sources */,
 				5E7F486422775B37006656AD /* InteropTestsRemoteWithCronet.m in Sources */,
@@ -910,9 +893,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				5E3F14842278B461007C6D90 /* InteropTestsBlockCallbacks.m in Sources */,
-				5E3F148E22792AF5007C6D90 /* ConfigureCronet.m in Sources */,
 				5E7F488922778B04006656AD /* InteropTestsRemote.m in Sources */,
-				5E7F487922778226006656AD /* InteropTestsMultipleChannels.m in Sources */,
 				5EA477042273617B000F72FC /* InteropTestsLocalCleartext.m in Sources */,
 				5EA4770322736178000F72FC /* InteropTestsLocalSSL.m in Sources */,
 				5E7F488422778A88006656AD /* InteropTests.m in Sources */,
diff --git a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTests.xcscheme b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTests.xcscheme
index adb3c366af2d06b46b474f7ccab2d8dc4e664ccb..cbde360a33810f31e00c97ab9a5a72b1b85ad5ff 100644
--- a/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTests.xcscheme
+++ b/src/objective-c/tests/Tests.xcodeproj/xcshareddata/xcschemes/InteropTests.xcscheme
@@ -23,7 +23,7 @@
       </BuildActionEntries>
    </BuildAction>
    <TestAction
-      buildConfiguration = "Cronet"
+      buildConfiguration = "Test"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       shouldUseLaunchSchemeArgsEnv = "YES">
@@ -48,7 +48,7 @@
       </AdditionalOptions>
    </TestAction>
    <LaunchAction
-      buildConfiguration = "Cronet"
+      buildConfiguration = "Test"
       selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
       selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
       launchStyle = "0"
@@ -70,7 +70,7 @@
       </AdditionalOptions>
    </LaunchAction>
    <ProfileAction
-      buildConfiguration = "Release"
+      buildConfiguration = "Test"
       shouldUseLaunchSchemeArgsEnv = "YES"
       savedToolIdentifier = ""
       useCustomWorkingDirectory = "NO"
@@ -86,7 +86,7 @@
       </MacroExpansion>
    </ProfileAction>
    <AnalyzeAction
-      buildConfiguration = "Cronet">
+      buildConfiguration = "Test">
    </AnalyzeAction>
    <ArchiveAction
       buildConfiguration = "Release"
diff --git a/src/objective-c/tests/UnitTests/ChannelPoolTest.m b/src/objective-c/tests/UnitTests/ChannelPoolTest.m
index eab8c5193fb9832bc2b109e1fb79673cfffbdd28..0b1eca389b93d65069d9febbbd50b1da2a3c99c2 100644
--- a/src/objective-c/tests/UnitTests/ChannelPoolTest.m
+++ b/src/objective-c/tests/UnitTests/ChannelPoolTest.m
@@ -18,9 +18,9 @@
 
 #import <XCTest/XCTest.h>
 
-#import "../../GRPCClient/private/GRPCChannel.h"
-#import "../../GRPCClient/private/GRPCChannelPool+Test.h"
-#import "../../GRPCClient/private/GRPCCompletionQueue.h"
+#import "../../GRPCClient/private/GRPCCore/GRPCChannel.h"
+#import "../../GRPCClient/private/GRPCCore/GRPCChannelPool+Test.h"
+#import "../../GRPCClient/private/GRPCCore/GRPCCompletionQueue.h"
 
 #define TEST_TIMEOUT 32
 
diff --git a/src/objective-c/tests/UnitTests/ChannelTests.m b/src/objective-c/tests/UnitTests/ChannelTests.m
index df78e8b11629a45f8f0c42423613326b2ab95d50..1ed0f16ecafb23643262ee2e2a29a1388908a79d 100644
--- a/src/objective-c/tests/UnitTests/ChannelTests.m
+++ b/src/objective-c/tests/UnitTests/ChannelTests.m
@@ -19,11 +19,11 @@
 #import <XCTest/XCTest.h>
 
 #import "../../GRPCClient/GRPCCallOptions.h"
-#import "../../GRPCClient/private/GRPCChannel.h"
-#import "../../GRPCClient/private/GRPCChannelPool+Test.h"
-#import "../../GRPCClient/private/GRPCChannelPool.h"
-#import "../../GRPCClient/private/GRPCCompletionQueue.h"
-#import "../../GRPCClient/private/GRPCWrappedCall.h"
+#import "../../GRPCClient/private/GRPCCore/GRPCChannel.h"
+#import "../../GRPCClient/private/GRPCCore/GRPCChannelPool+Test.h"
+#import "../../GRPCClient/private/GRPCCore/GRPCChannelPool.h"
+#import "../../GRPCClient/private/GRPCCore/GRPCCompletionQueue.h"
+#import "../../GRPCClient/private/GRPCCore/GRPCWrappedCall.h"
 
 static NSString *kDummyHost = @"dummy.host";
 static NSString *kDummyPath = @"/dummy/path";
diff --git a/src/objective-c/tests/UnitTests/NSErrorUnitTests.m b/src/objective-c/tests/UnitTests/NSErrorUnitTests.m
index 8a4f03a44603b13e978355f0887f586ec7c61cc1..74e5794c48006916d01fea14691451bd8179402c 100644
--- a/src/objective-c/tests/UnitTests/NSErrorUnitTests.m
+++ b/src/objective-c/tests/UnitTests/NSErrorUnitTests.m
@@ -20,7 +20,7 @@
 
 #import <GRPCClient/GRPCCall.h>
 
-#import "../../GRPCClient/private/NSError+GRPC.h"
+#import "../../GRPCClient/private/GRPCCore/NSError+GRPC.h"
 
 @interface NSErrorUnitTests : XCTestCase
 
diff --git a/templates/gRPC-ProtoRPC.podspec.template b/templates/gRPC-ProtoRPC.podspec.template
index 457d2988036235c5bfc1adf3fcebc447941c0e00..e94f149ee4aa2cd8ea4ac411bbdf240b025bf6e5 100644
--- a/templates/gRPC-ProtoRPC.podspec.template
+++ b/templates/gRPC-ProtoRPC.podspec.template
@@ -44,22 +44,40 @@
     s.module_name = name
     s.header_dir = name
 
-    src_dir = 'src/objective-c/ProtoRPC'
+    s.default_subspec = 'Main', 'Legacy', 'Legacy-Header'
 
-    s.default_subspec = 'Main'
+    s.subspec 'Legacy-Header' do |ss|
+      ss.header_mappings_dir = "src/objective-c/ProtoRPC"
+      ss.public_header_files = "src/objective-c/ProtoRPC/ProtoRPCLegacy.h"
+      ss.source_files = "src/objective-c/ProtoRPC/ProtoRPCLegacy.h"
+    end
 
     s.subspec 'Main' do |ss|
-      ss.header_mappings_dir = "#{src_dir}"
-      ss.dependency 'gRPC', version
+      ss.header_mappings_dir = "src/objective-c/ProtoRPC"
+      ss.dependency "#{s.name}/Legacy-Header", version
+      ss.dependency 'gRPC/Interface', version
+      ss.dependency 'Protobuf', '~> 3.0'
+
+      ss.source_files = "src/objective-c/ProtoRPC/ProtoMethod.{h,m}",
+                        "src/objective-c/ProtoRPC/ProtoRPC.{h,m}",
+                        "src/objective-c/ProtoRPC/ProtoService.{h,m}"
+    end
+
+    s.subspec 'Legacy' do |ss|
+      ss.header_mappings_dir = "src/objective-c/ProtoRPC"
+      ss.dependency "#{s.name}/Main", version
+      ss.dependency "#{s.name}/Legacy-Header", version
+      ss.dependency 'gRPC/GRPCCore', version
       ss.dependency 'gRPC-RxLibrary', version
       ss.dependency 'Protobuf', '~> 3.0'
 
-      ss.source_files = "#{src_dir}/*.{h,m}"
+      ss.source_files = "src/objective-c/ProtoRPC/ProtoRPCLegacy.m",
+                        "src/objective-c/ProtoRPC/ProtoServiceLegacy.m"
     end
 
     # CFStream is now default. Leaving this subspec only for compatibility purpose.
     s.subspec 'CFStream' do |ss|
-      ss.dependency "#{s.name}/Main", version
+      ss.dependency "#{s.name}/Legacy", version
     end
 
     s.pod_target_xcconfig = {
diff --git a/templates/gRPC-RxLibrary.podspec.template b/templates/gRPC-RxLibrary.podspec.template
index 9389c5ecd8d2fe0d616c1463cac97866eda68fac..772265dadda134b481fd29c6fa4d0eb709db83a3 100644
--- a/templates/gRPC-RxLibrary.podspec.template
+++ b/templates/gRPC-RxLibrary.podspec.template
@@ -44,6 +44,23 @@
     s.module_name = name
     s.header_dir = name
 
+    s.default_subspec = 'Interface', 'Implementation'
+
+    src_dir = 'src/objective-c/RxLibrary'
+    s.subspec 'Interface' do |ss|
+      ss.header_mappings_dir = "#{src_dir}"
+      ss.source_files = "#{src_dir}/*.h"
+      ss.public_header_files = "#{src_dir}/*.h"
+    end
+
+    s.subspec 'Implementation' do |ss|
+      ss.header_mappings_dir = "#{src_dir}"
+      ss.source_files = "#{src_dir}/*.m", "#{src_dir}/**/*.{h,m}"
+      ss.private_header_files = "#{src_dir}/**/*.h"
+
+      ss.dependency "#{s.name}/Interface"
+    end
+
     src_dir = 'src/objective-c/RxLibrary'
     s.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}"
     s.private_header_files = "#{src_dir}/private/*.h"
diff --git a/templates/gRPC.podspec.template b/templates/gRPC.podspec.template
index 8cb380ede03da2270b02ae0511dc63db58381021..e705edc1748acc256499e29e062677ff6c4390fd 100644
--- a/templates/gRPC.podspec.template
+++ b/templates/gRPC.podspec.template
@@ -43,13 +43,7 @@
     s.module_name = name
     s.header_dir = name
 
-    src_dir = 'src/objective-c/GRPCClient'
-
-    s.dependency 'gRPC-RxLibrary', version
-    s.default_subspec = 'Main'
-
-    # Certificates, to be able to establish TLS connections:
-    s.resource_bundles = { 'gRPCCertificates' => ['etc/roots.pem'] }
+    s.default_subspec = 'Interface', 'GRPCCore', 'Interface-Legacy'
 
     s.pod_target_xcconfig = {
       # This is needed by all pods that depend on gRPC-RxLibrary:
@@ -57,29 +51,103 @@
       'CLANG_WARN_STRICT_PROTOTYPES' => 'NO',
     }
 
-    s.subspec 'Main' do |ss|
-      ss.header_mappings_dir = "#{src_dir}"
-
-      ss.source_files = "#{src_dir}/*.{h,m}", "#{src_dir}/**/*.{h,m}"
-      ss.exclude_files = "#{src_dir}/GRPCCall+GID.{h,m}"
-      ss.private_header_files = "#{src_dir}/private/*.h", "#{src_dir}/internal/*.h"
-
-      ss.dependency 'gRPC-Core', version
+    s.subspec 'Interface-Legacy' do |ss|
+      ss.header_mappings_dir = 'src/objective-c/GRPCClient'
+
+      ss.public_header_files = "GRPCClient/GRPCCall+ChannelArg.h",
+                               "GRPCClient/GRPCCall+ChannelCredentials.h",
+                               "GRPCClient/GRPCCall+Cronet.h",
+                               "GRPCClient/GRPCCall+OAuth2.h",
+                               "GRPCClient/GRPCCall+Tests.h",
+                               "src/objective-c/GRPCClient/GRPCCallLegacy.h",
+                               "src/objective-c/GRPCClient/GRPCTypes.h"
+
+      ss.source_files = "GRPCClient/GRPCCall+ChannelArg.h",
+                        "GRPCClient/GRPCCall+ChannelCredentials.h",
+                        "GRPCClient/GRPCCall+Cronet.h",
+                        "GRPCClient/GRPCCall+OAuth2.h",
+                        "GRPCClient/GRPCCall+Tests.h",
+                        "src/objective-c/GRPCClient/GRPCCallLegacy.h",
+                        "src/objective-c/GRPCClient/GRPCTypes.h"
+      ss.dependency "gRPC-RxLibrary/Interface", version
     end
 
-    # CFStream is now default. Leaving this subspec only for compatibility purpose.
-    s.subspec 'CFStream' do |ss|
-      ss.dependency "#{s.name}/Main", version
+    s.subspec 'Interface' do |ss|
+      ss.header_mappings_dir = 'src/objective-c/GRPCClient'
+
+      ss.public_header_files = 'src/objective-c/GRPCClient/GRPCCall.h',
+                               'src/objective-c/GRPCClient/GRPCCall+Interceptor.h',
+                               'src/objective-c/GRPCClient/GRPCCallOptions.h',
+                               'src/objective-c/GRPCClient/GRPCInterceptor.h',
+                               'src/objective-c/GRPCClient/GRPCTransport.h',
+                               'src/objective-c/GRPCClient/GRPCDispatchable.h',
+                               'src/objective-c/GRPCClient/version.h'
+
+      ss.source_files = 'src/objective-c/GRPCClient/GRPCCall.h',
+                        'src/objective-c/GRPCClient/GRPCCall.m',
+                        'src/objective-c/GRPCClient/GRPCCall+Interceptor.h',
+                        'src/objective-c/GRPCClient/GRPCCall+Interceptor.m',
+                        'src/objective-c/GRPCClient/GRPCCallOptions.h',
+                        'src/objective-c/GRPCClient/GRPCCallOptions.m',
+                        'src/objective-c/GRPCClient/GRPCDispatchable.h',
+                        'src/objective-c/GRPCClient/GRPCInterceptor.h',
+                        'src/objective-c/GRPCClient/GRPCInterceptor.m',
+                        'src/objective-c/GRPCClient/GRPCTransport.h',
+                        'src/objective-c/GRPCClient/GRPCTransport.m',
+                        'src/objective-c/GRPCClient/internal/*.h',
+                        'src/objective-c/GRPCClient/private/GRPCTransport+Private.h',
+                        'src/objective-c/GRPCClient/private/GRPCTransport+Private.m',
+                        'src/objective-c/GRPCClient/version.h'
+
+      ss.dependency "#{s.name}/Interface-Legacy", version
     end
 
-    s.subspec 'GID' do |ss|
-      ss.ios.deployment_target = '7.0'
+    s.subspec 'GRPCCore' do |ss|
+      ss.header_mappings_dir = 'src/objective-c/GRPCClient'
+
+      ss.public_header_files = 'src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h',
+                               'src/objective-c/GRPCClient/GRPCCall+Cronet.h',
+                               'src/objective-c/GRPCClient/GRPCCall+OAuth2.h',
+                               'src/objective-c/GRPCClient/GRPCCall+Tests.h',
+                               'src/objective-c/GRPCClient/GRPCCall+ChannelArg.h',
+                               'src/objective-c/GRPCClient/internal_testing/*.h'
+      ss.private_header_files = 'src/objective-c/GRPCClient/private/GRPCCore/*.h'
+      ss.source_files = 'src/objective-c/GRPCClient/internal_testing/*.{h,m}',
+                        'src/objective-c/GRPCClient/private/GRPCCore/*.{h,m}',
+                        'src/objective-c/GRPCClient/GRPCCall+ChannelArg.h',
+                        'src/objective-c/GRPCClient/GRPCCall+ChannelArg.m',
+                        'src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.h',
+                        'src/objective-c/GRPCClient/GRPCCall+ChannelCredentials.m',
+                        'src/objective-c/GRPCClient/GRPCCall+Cronet.h',
+                        'src/objective-c/GRPCClient/GRPCCall+Cronet.m',
+                        'src/objective-c/GRPCClient/GRPCCall+OAuth2.h',
+                        'src/objective-c/GRPCClient/GRPCCall+OAuth2.m',
+                        'src/objective-c/GRPCClient/GRPCCall+Tests.h',
+                        'src/objective-c/GRPCClient/GRPCCall+Tests.m',
+                        'src/objective-c/GRPCClient/GRPCCallLegacy.m'
+
+      # Certificates, to be able to establish TLS connections:
+      ss.resource_bundles = { 'gRPCCertificates' => ['etc/roots.pem'] }
+
+      ss.dependency "#{s.name}/Interface-Legacy", version
+      ss.dependency "#{s.name}/Interface", version
+      ss.dependency 'gRPC-Core', version
+      ss.dependency 'gRPC-RxLibrary', version
+    end
 
-      ss.header_mappings_dir = "#{src_dir}"
+    s.subspec 'GRPCCoreCronet' do |ss|
+      ss.header_mappings_dir = 'src/objective-c/GRPCClient'
 
-      ss.source_files = "#{src_dir}/GRPCCall+GID.{h,m}"
+      ss.source_files = 'src/objective-c/GRPCClient/GRPCCall+Cronet.h',
+                        'src/objective-c/GRPCClient/GRPCCall+Cronet.m',
+                        'src/objective-c/GRPCClient/private/GRPCCore/GRPCCoreCronet/*.{h,m}'
+      ss.dependency "#{s.name}/GRPCCore", version
+      ss.dependency 'gRPC-Core/Cronet-Implementation', version
+      ss.dependency 'CronetFramework'
+    end
 
-      ss.dependency "#{s.name}/Main", version
-      ss.dependency 'Google/SignIn'
+    # CFStream is now default. Leaving this subspec only for compatibility purpose.
+    s.subspec 'CFStream' do |ss|
+      ss.dependency "#{s.name}/GRPCCore", version
     end
   end
diff --git a/templates/src/objective-c/GRPCClient/private/version.h.template b/templates/src/objective-c/GRPCClient/version.h.template
similarity index 100%
rename from templates/src/objective-c/GRPCClient/private/version.h.template
rename to templates/src/objective-c/GRPCClient/version.h.template