diff --git a/tools/run_tests/run_interop_tests.py b/tools/run_tests/run_interop_tests.py
index d2bf529fef33a59503cfac736fb69df43093e6e0..fcc752fdea49e1ec2a32a35acaaa04d960a36fac 100755
--- a/tools/run_tests/run_interop_tests.py
+++ b/tools/run_tests/run_interop_tests.py
@@ -371,6 +371,39 @@ class PHP7Language:
   def __str__(self):
     return 'php7'
 
+class ObjcLanguage:
+
+  def __init__(self):
+    self.client_cwd = 'src/objective-c/tests'
+    self.safename = str(self)
+
+  def client_cmd(self, args):
+    # from args, extract the server port and craft xcodebuild command out of it
+    for arg in args:
+      port = re.search('--server_port=(\d+)', arg)
+      if port:
+        portnum = port.group(1)
+        cmdline = 'pod install && xcodebuild -workspace Tests.xcworkspace -scheme InteropTestsLocalSSL -destination name="iPhone 6" HOST_PORT_LOCALSSL=localhost:%s test'%portnum
+        return [cmdline]
+
+  def cloud_to_prod_env(self):
+    return {}
+
+  def global_env(self):
+    return {}
+
+  def unimplemented_test_cases(self):
+    # ObjC test runs all cases with the same command. It ignores the testcase
+    # cmdline argument. Here we return all but one test cases as unimplemented,
+    # and depend upon ObjC test's behavior that it runs all cases even when
+    # we tell it to run just one.
+    return _TEST_CASES[1:]
+
+  def unimplemented_test_cases_server(self):
+    return _SKIP_COMPRESSION
+
+  def __str__(self):
+    return 'objc'
 
 class RubyLanguage:
 
@@ -402,7 +435,6 @@ class RubyLanguage:
   def __str__(self):
     return 'ruby'
 
-
 class PythonLanguage:
 
   def __init__(self):
@@ -460,6 +492,7 @@ _LANGUAGES = {
     'node' : NodeLanguage(),
     'php' :  PHPLanguage(),
     'php7' :  PHP7Language(),
+    'objc' : ObjcLanguage(),
     'ruby' : RubyLanguage(),
     'python' : PythonLanguage(),
 }
@@ -667,7 +700,8 @@ def cloud_to_cloud_jobspec(language, test_case, server_name, server_host,
     cwd = language.client_cwd
 
   environ = language.global_env()
-  if docker_image:
+  if docker_image and language.safename != 'objc':
+    # we can't run client in docker for objc.
     container_name = dockerjob.random_name('interop_client_%s' % language.safename)
     cmdline = docker_run_cmdline(cmdline,
                                  image=docker_image,
@@ -820,7 +854,7 @@ argp.add_argument('-l', '--language',
                   choices=['all'] + sorted(_LANGUAGES),
                   nargs='+',
                   default=['all'],
-                  help='Clients to run.')
+                  help='Clients to run. Objc client can be only run on OSX.')
 argp.add_argument('-j', '--jobs', default=multiprocessing.cpu_count(), type=int)
 argp.add_argument('--cloud_to_prod',
                   default=False,
@@ -942,6 +976,9 @@ if args.use_docker:
 
   build_jobs = []
   for l in languages_to_build:
+    if str(l) == 'objc':
+      # we don't need to build a docker image for objc
+      continue
     job = build_interop_image_jobspec(l)
     docker_images[str(l)] = job.tag
     build_jobs.append(job)