aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Muxi Yan <mxyan@google.com>2018-10-08 15:52:27 -0700
committerGravatar Muxi Yan <mxyan@google.com>2018-10-08 15:52:27 -0700
commit309ba191525a96c5314e5762ecaa2fffbc9eccbb (patch)
treeff431202f113bf6b11bac5a48282fa8394faccf3
parent0fd4727defda5f8bef106a1f3b59263886b9b6c6 (diff)
Channel pool
-rw-r--r--src/objective-c/GRPCClient/private/ChannelArgsUtil.h25
-rw-r--r--src/objective-c/GRPCClient/private/ChannelArgsUtil.m95
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannel.h43
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannel.m242
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelFactory.h32
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelPool.h69
-rw-r--r--src/objective-c/GRPCClient/private/GRPCChannelPool.m387
-rw-r--r--src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h36
-rw-r--r--src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m94
-rw-r--r--src/objective-c/GRPCClient/private/GRPCHost.h31
-rw-r--r--src/objective-c/GRPCClient/private/GRPCHost.m261
-rw-r--r--src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h35
-rw-r--r--src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m48
-rw-r--r--src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h38
-rw-r--r--src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m129
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.h3
-rw-r--r--src/objective-c/GRPCClient/private/GRPCWrappedCall.m18
17 files changed, 1138 insertions, 448 deletions
diff --git a/src/objective-c/GRPCClient/private/ChannelArgsUtil.h b/src/objective-c/GRPCClient/private/ChannelArgsUtil.h
new file mode 100644
index 0000000000..d9d6933c6b
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/ChannelArgsUtil.h
@@ -0,0 +1,25 @@
+/*
+ *
+ * Copyright 2018 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 <Foundation/Foundation.h>
+
+#include <grpc/impl/codegen/grpc_types.h>
+
+void FreeChannelArgs(grpc_channel_args* channel_args);
+
+grpc_channel_args* BuildChannelArgs(NSDictionary* dictionary);
diff --git a/src/objective-c/GRPCClient/private/ChannelArgsUtil.m b/src/objective-c/GRPCClient/private/ChannelArgsUtil.m
new file mode 100644
index 0000000000..8ebf3a2659
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/ChannelArgsUtil.m
@@ -0,0 +1,95 @@
+/*
+ *
+ * Copyright 2018 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 "ChannelArgsUtil.h"
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/string_util.h>
+
+static void *copy_pointer_arg(void *p) {
+ // Add ref count to the object when making copy
+ id obj = (__bridge id)p;
+ return (__bridge_retained void *)obj;
+}
+
+static void destroy_pointer_arg(void *p) {
+ // Decrease ref count to the object when destroying
+ CFRelease((CFTreeRef)p);
+}
+
+static int cmp_pointer_arg(void *p, void *q) { return p == q; }
+
+static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg,
+ cmp_pointer_arg};
+
+void FreeChannelArgs(grpc_channel_args *channel_args) {
+ for (size_t i = 0; i < channel_args->num_args; ++i) {
+ grpc_arg *arg = &channel_args->args[i];
+ gpr_free(arg->key);
+ if (arg->type == GRPC_ARG_STRING) {
+ gpr_free(arg->value.string);
+ }
+ }
+ gpr_free(channel_args->args);
+ gpr_free(channel_args);
+}
+
+/**
+ * Allocates a @c grpc_channel_args and populates it with the options specified in the
+ * @c dictionary. Keys must be @c NSString. If the value responds to @c @selector(UTF8String) then
+ * it will be mapped to @c GRPC_ARG_STRING. If not, it will be mapped to @c GRPC_ARG_INTEGER if the
+ * value responds to @c @selector(intValue). Otherwise, an exception will be raised. The caller of
+ * this function is responsible for calling @c freeChannelArgs on a non-NULL returned value.
+ */
+grpc_channel_args *BuildChannelArgs(NSDictionary *dictionary) {
+ if (!dictionary) {
+ return NULL;
+ }
+
+ NSArray *keys = [dictionary allKeys];
+ NSUInteger argCount = [keys count];
+
+ grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args));
+ channelArgs->num_args = argCount;
+ channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg));
+
+ // TODO(kriswuollett) Check that keys adhere to GRPC core library requirements
+
+ for (NSUInteger i = 0; i < argCount; ++i) {
+ grpc_arg *arg = &channelArgs->args[i];
+ arg->key = gpr_strdup([keys[i] UTF8String]);
+
+ id value = dictionary[keys[i]];
+ if ([value respondsToSelector:@selector(UTF8String)]) {
+ arg->type = GRPC_ARG_STRING;
+ arg->value.string = gpr_strdup([value UTF8String]);
+ } else if ([value respondsToSelector:@selector(intValue)]) {
+ arg->type = GRPC_ARG_INTEGER;
+ arg->value.integer = [value intValue];
+ } else if (value != nil) {
+ arg->type = GRPC_ARG_POINTER;
+ arg->value.pointer.p = (__bridge_retained void *)value;
+ arg->value.pointer.vtable = &objc_arg_vtable;
+ } else {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Invalid value type: %@", [value class]];
+ }
+ }
+
+ return channelArgs;
+}
diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.h b/src/objective-c/GRPCClient/private/GRPCChannel.h
index 6499d4398c..7d5039a8a2 100644
--- a/src/objective-c/GRPCClient/private/GRPCChannel.h
+++ b/src/objective-c/GRPCClient/private/GRPCChannel.h
@@ -21,6 +21,8 @@
#include <grpc/grpc.h>
@class GRPCCompletionQueue;
+@class GRPCCallOptions;
+@class GRPCChannelConfiguration;
struct grpc_channel_credentials;
/**
@@ -28,41 +30,30 @@ struct grpc_channel_credentials;
*/
@interface GRPCChannel : NSObject
-@property(nonatomic, readonly, nonnull) struct grpc_channel *unmanagedChannel;
-
- (nullable instancetype)init NS_UNAVAILABLE;
/**
- * Creates a secure channel to the specified @c host using default credentials and channel
- * arguments. If certificates could not be found to create a secure channel, then @c nil is
- * returned.
+ * Returns a channel connecting to \a host with options as \a callOptions. The channel may be new
+ * or a cached channel that is already connected.
*/
-+ (nullable GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host;
++ (nullable instancetype)channelWithHost:(nonnull NSString *)host
+ callOptions:(nullable GRPCCallOptions *)callOptions;
/**
- * Creates a secure channel to the specified @c host using Cronet as a transport mechanism.
- */
-#ifdef GRPC_COMPILE_WITH_CRONET
-+ (nullable GRPCChannel *)secureCronetChannelWithHost:(nonnull NSString *)host
- channelArgs:(nonnull NSDictionary *)channelArgs;
-#endif
-/**
- * Creates a secure channel to the specified @c host using the specified @c credentials and
- * @c channelArgs. Only in tests should @c GRPC_SSL_TARGET_NAME_OVERRIDE_ARG channel arg be set.
+ * Get a grpc core call object from this channel.
*/
-+ (nonnull GRPCChannel *)secureChannelWithHost:(nonnull NSString *)host
- credentials:
- (nonnull struct grpc_channel_credentials *)credentials
- channelArgs:(nullable NSDictionary *)channelArgs;
+- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path
+ completionQueue:(nonnull GRPCCompletionQueue *)queue
+ callOptions:(nonnull GRPCCallOptions *)callOptions;
+
+- (void)unmanagedCallUnref;
/**
- * Creates an insecure channel to the specified @c host using the specified @c channelArgs.
+ * Create a channel object with the signature \a config. This function is used for testing only. Use
+ * channelWithHost:callOptions: in production.
*/
-+ (nonnull GRPCChannel *)insecureChannelWithHost:(nonnull NSString *)host
- channelArgs:(nullable NSDictionary *)channelArgs;
++ (nullable instancetype)createChannelWithConfiguration:(nonnull GRPCChannelConfiguration *)config;
-- (nullable grpc_call *)unmanagedCallWithPath:(nonnull NSString *)path
- serverName:(nonnull NSString *)serverName
- timeout:(NSTimeInterval)timeout
- completionQueue:(nonnull GRPCCompletionQueue *)queue;
+// TODO (mxyan): deprecate with GRPCCall:closeOpenConnections
++ (void)closeOpenConnections;
@end
diff --git a/src/objective-c/GRPCClient/private/GRPCChannel.m b/src/objective-c/GRPCClient/private/GRPCChannel.m
index b1f6ea270e..cf44b96e22 100644
--- a/src/objective-c/GRPCClient/private/GRPCChannel.m
+++ b/src/objective-c/GRPCClient/private/GRPCChannel.m
@@ -18,206 +18,106 @@
#import "GRPCChannel.h"
-#include <grpc/grpc_security.h>
-#ifdef GRPC_COMPILE_WITH_CRONET
-#include <grpc/grpc_cronet.h>
-#endif
-#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
-#include <grpc/support/string_util.h>
-#ifdef GRPC_COMPILE_WITH_CRONET
-#import <Cronet/Cronet.h>
-#import <GRPCClient/GRPCCall+Cronet.h>
-#endif
+#import "ChannelArgsUtil.h"
+#import "GRPCChannelFactory.h"
+#import "GRPCChannelPool.h"
#import "GRPCCompletionQueue.h"
+#import "GRPCConnectivityMonitor.h"
+#import "GRPCCronetChannelFactory.h"
+#import "GRPCInsecureChannelFactory.h"
+#import "GRPCSecureChannelFactory.h"
+#import "version.h"
-static void *copy_pointer_arg(void *p) {
- // Add ref count to the object when making copy
- id obj = (__bridge id)p;
- return (__bridge_retained void *)obj;
-}
-
-static void destroy_pointer_arg(void *p) {
- // Decrease ref count to the object when destroying
- CFRelease((CFTreeRef)p);
-}
-
-static int cmp_pointer_arg(void *p, void *q) { return p == q; }
-
-static const grpc_arg_pointer_vtable objc_arg_vtable = {copy_pointer_arg, destroy_pointer_arg,
- cmp_pointer_arg};
-
-static void FreeChannelArgs(grpc_channel_args *channel_args) {
- for (size_t i = 0; i < channel_args->num_args; ++i) {
- grpc_arg *arg = &channel_args->args[i];
- gpr_free(arg->key);
- if (arg->type == GRPC_ARG_STRING) {
- gpr_free(arg->value.string);
- }
- }
- gpr_free(channel_args->args);
- gpr_free(channel_args);
-}
-
-/**
- * Allocates a @c grpc_channel_args and populates it with the options specified in the
- * @c dictionary. Keys must be @c NSString. If the value responds to @c @selector(UTF8String) then
- * it will be mapped to @c GRPC_ARG_STRING. If not, it will be mapped to @c GRPC_ARG_INTEGER if the
- * value responds to @c @selector(intValue). Otherwise, an exception will be raised. The caller of
- * this function is responsible for calling @c freeChannelArgs on a non-NULL returned value.
- */
-static grpc_channel_args *BuildChannelArgs(NSDictionary *dictionary) {
- if (!dictionary) {
- return NULL;
- }
-
- NSArray *keys = [dictionary allKeys];
- NSUInteger argCount = [keys count];
-
- grpc_channel_args *channelArgs = gpr_malloc(sizeof(grpc_channel_args));
- channelArgs->num_args = argCount;
- channelArgs->args = gpr_malloc(argCount * sizeof(grpc_arg));
-
- // TODO(kriswuollett) Check that keys adhere to GRPC core library requirements
-
- for (NSUInteger i = 0; i < argCount; ++i) {
- grpc_arg *arg = &channelArgs->args[i];
- arg->key = gpr_strdup([keys[i] UTF8String]);
-
- id value = dictionary[keys[i]];
- if ([value respondsToSelector:@selector(UTF8String)]) {
- arg->type = GRPC_ARG_STRING;
- arg->value.string = gpr_strdup([value UTF8String]);
- } else if ([value respondsToSelector:@selector(intValue)]) {
- arg->type = GRPC_ARG_INTEGER;
- arg->value.integer = [value intValue];
- } else if (value != nil) {
- arg->type = GRPC_ARG_POINTER;
- arg->value.pointer.p = (__bridge_retained void *)value;
- arg->value.pointer.vtable = &objc_arg_vtable;
- } else {
- [NSException raise:NSInvalidArgumentException
- format:@"Invalid value type: %@", [value class]];
- }
- }
-
- return channelArgs;
-}
+#import <GRPCClient/GRPCCall+Cronet.h>
+#import <GRPCClient/GRPCCallOptions.h>
@implementation GRPCChannel {
- // Retain arguments to channel_create because they may not be used on the thread that invoked
- // the channel_create function.
- NSString *_host;
- grpc_channel_args *_channelArgs;
+ GRPCChannelConfiguration *_configuration;
+ grpc_channel *_unmanagedChannel;
}
-#ifdef GRPC_COMPILE_WITH_CRONET
-- (instancetype)initWithHost:(NSString *)host
- cronetEngine:(stream_engine *)cronetEngine
- channelArgs:(NSDictionary *)channelArgs {
- if (!host) {
- [NSException raise:NSInvalidArgumentException format:@"host argument missing"];
+- (grpc_call *)unmanagedCallWithPath:(NSString *)path
+ completionQueue:(nonnull GRPCCompletionQueue *)queue
+ callOptions:(GRPCCallOptions *)callOptions {
+ NSString *serverAuthority = callOptions.serverAuthority;
+ NSTimeInterval timeout = callOptions.timeout;
+ GPR_ASSERT(timeout >= 0);
+ grpc_slice host_slice = grpc_empty_slice();
+ if (serverAuthority) {
+ host_slice = grpc_slice_from_copied_string(serverAuthority.UTF8String);
}
-
- if (self = [super init]) {
- _channelArgs = BuildChannelArgs(channelArgs);
- _host = [host copy];
- _unmanagedChannel =
- grpc_cronet_secure_channel_create(cronetEngine, _host.UTF8String, _channelArgs, NULL);
+ grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String);
+ gpr_timespec deadline_ms =
+ timeout == 0 ? gpr_inf_future(GPR_CLOCK_REALTIME)
+ : gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
+ gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN));
+ grpc_call *call = grpc_channel_create_call(
+ _unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS, queue.unmanagedQueue, path_slice,
+ serverAuthority ? &host_slice : NULL, deadline_ms, NULL);
+ if (serverAuthority) {
+ grpc_slice_unref(host_slice);
}
-
- return self;
+ grpc_slice_unref(path_slice);
+ return call;
}
-#endif
-
-- (instancetype)initWithHost:(NSString *)host
- secure:(BOOL)secure
- credentials:(struct grpc_channel_credentials *)credentials
- channelArgs:(NSDictionary *)channelArgs {
- if (!host) {
- [NSException raise:NSInvalidArgumentException format:@"host argument missing"];
- }
- if (secure && !credentials) {
- return nil;
- }
+- (void)unmanagedCallUnref {
+ [kChannelPool unrefChannelWithConfiguration:_configuration];
+}
- if (self = [super init]) {
- _channelArgs = BuildChannelArgs(channelArgs);
- _host = [host copy];
- if (secure) {
- _unmanagedChannel =
- grpc_secure_channel_create(credentials, _host.UTF8String, _channelArgs, NULL);
- } else {
- _unmanagedChannel = grpc_insecure_channel_create(_host.UTF8String, _channelArgs, NULL);
- }
+- (nullable instancetype)initWithUnmanagedChannel:(nullable grpc_channel *)unmanagedChannel
+ configuration:(GRPCChannelConfiguration *)configuration {
+ if ((self = [super init])) {
+ _unmanagedChannel = unmanagedChannel;
+ _configuration = configuration;
}
-
return self;
}
- (void)dealloc {
- // TODO(jcanizales): Be sure to add a test with a server that closes the connection prematurely,
- // as in the past that made this call to crash.
grpc_channel_destroy(_unmanagedChannel);
- FreeChannelArgs(_channelArgs);
}
-#ifdef GRPC_COMPILE_WITH_CRONET
-+ (GRPCChannel *)secureCronetChannelWithHost:(NSString *)host
- channelArgs:(NSDictionary *)channelArgs {
- stream_engine *engine = [GRPCCall cronetEngine];
- if (!engine) {
- [NSException raise:NSInvalidArgumentException format:@"cronet_engine is NULL. Set it first."];
++ (nullable instancetype)createChannelWithConfiguration:(GRPCChannelConfiguration *)config {
+ NSString *host = config.host;
+ if (host == nil || [host length] == 0) {
return nil;
}
- return [[GRPCChannel alloc] initWithHost:host cronetEngine:engine channelArgs:channelArgs];
-}
-#endif
-+ (GRPCChannel *)secureChannelWithHost:(NSString *)host {
- return [[GRPCChannel alloc] initWithHost:host secure:YES credentials:NULL channelArgs:NULL];
+ NSMutableDictionary *channelArgs = config.channelArgs;
+ [channelArgs addEntriesFromDictionary:config.callOptions.additionalChannelArgs];
+ id<GRPCChannelFactory> factory = config.channelFactory;
+ grpc_channel *unmanaged_channel = [factory createChannelWithHost:host channelArgs:channelArgs];
+ return [[GRPCChannel alloc] initWithUnmanagedChannel:unmanaged_channel configuration:config];
}
-+ (GRPCChannel *)secureChannelWithHost:(NSString *)host
- credentials:(struct grpc_channel_credentials *)credentials
- channelArgs:(NSDictionary *)channelArgs {
- return [[GRPCChannel alloc] initWithHost:host
- secure:YES
- credentials:credentials
- channelArgs:channelArgs];
-}
+static dispatch_once_t initChannelPool;
+static GRPCChannelPool *kChannelPool;
-+ (GRPCChannel *)insecureChannelWithHost:(NSString *)host channelArgs:(NSDictionary *)channelArgs {
- return [[GRPCChannel alloc] initWithHost:host secure:NO credentials:NULL channelArgs:channelArgs];
-}
++ (nullable instancetype)channelWithHost:(NSString *)host
+ callOptions:(GRPCCallOptions *)callOptions {
+ dispatch_once(&initChannelPool, ^{
+ kChannelPool = [[GRPCChannelPool alloc] init];
+ });
-- (grpc_call *)unmanagedCallWithPath:(NSString *)path
- serverName:(NSString *)serverName
- timeout:(NSTimeInterval)timeout
- completionQueue:(GRPCCompletionQueue *)queue {
- GPR_ASSERT(timeout >= 0);
- if (timeout < 0) {
- timeout = 0;
+ NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:host]];
+ if (hostURL.host && !hostURL.port) {
+ host = [hostURL.host stringByAppendingString:@":443"];
}
- grpc_slice host_slice = grpc_empty_slice();
- if (serverName) {
- host_slice = grpc_slice_from_copied_string(serverName.UTF8String);
- }
- grpc_slice path_slice = grpc_slice_from_copied_string(path.UTF8String);
- gpr_timespec deadline_ms =
- timeout == 0 ? gpr_inf_future(GPR_CLOCK_REALTIME)
- : gpr_time_add(gpr_now(GPR_CLOCK_MONOTONIC),
- gpr_time_from_millis((int64_t)(timeout * 1000), GPR_TIMESPAN));
- grpc_call *call = grpc_channel_create_call(_unmanagedChannel, NULL, GRPC_PROPAGATE_DEFAULTS,
- queue.unmanagedQueue, path_slice,
- serverName ? &host_slice : NULL, deadline_ms, NULL);
- if (serverName) {
- grpc_slice_unref(host_slice);
- }
- grpc_slice_unref(path_slice);
- return call;
+
+ GRPCChannelConfiguration *channelConfig =
+ [[GRPCChannelConfiguration alloc] initWithHost:host callOptions:callOptions];
+ return [kChannelPool channelWithConfiguration:channelConfig
+ createChannel:^{
+ return
+ [GRPCChannel createChannelWithConfiguration:channelConfig];
+ }];
+}
+
++ (void)closeOpenConnections {
+ [kChannelPool clear];
}
@end
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCChannelFactory.h
new file mode 100644
index 0000000000..e44f8260df
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCChannelFactory.h
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright 2018 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 <Foundation/Foundation.h>
+
+#include <grpc/impl/codegen/grpc_types.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@protocol GRPCChannelFactory
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSMutableDictionary *)args;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.h b/src/objective-c/GRPCClient/private/GRPCChannelPool.h
new file mode 100644
index 0000000000..1145039549
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.h
@@ -0,0 +1,69 @@
+/*
+ *
+ * Copyright 2018 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.
+ *
+ */
+
+/**
+ * Signature for the channel. If two channel's signatures are the same, they share the same
+ * underlying \a GRPCChannel object.
+ */
+
+#import <GRPCClient/GRPCCallOptions.h>
+
+#import "GRPCChannelFactory.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@class GRPCChannel;
+
+@interface GRPCChannelConfiguration : NSObject<NSCopying>
+
+@property(atomic, strong, readwrite) NSString *host;
+@property(atomic, strong, readwrite) GRPCCallOptions *callOptions;
+
+@property(readonly) id<GRPCChannelFactory> channelFactory;
+@property(readonly) NSMutableDictionary *channelArgs;
+
+- (nullable instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions;
+
+@end
+
+/**
+ * Manage the pool of connected channels. When a channel is no longer referenced by any call,
+ * destroy the channel after a certain period of time elapsed.
+ */
+@interface GRPCChannelPool : NSObject
+
+- (instancetype)init;
+
+- (instancetype)initWithChannelDestroyDelay:(NSTimeInterval)channelDestroyDelay;
+
+/**
+ * Return a channel with a particular configuration. If the channel does not exist, execute \a
+ * createChannel then add it in the pool. If the channel exists, increase its reference count.
+ */
+- (GRPCChannel *)channelWithConfiguration:(GRPCChannelConfiguration *)configuration
+ createChannel:(GRPCChannel * (^)(void))createChannel;
+
+/** Decrease a channel's refcount. */
+- (void)unrefChannelWithConfiguration:configuration;
+
+/** Clear all channels in the pool. */
+- (void)clear;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCChannelPool.m b/src/objective-c/GRPCClient/private/GRPCChannelPool.m
new file mode 100644
index 0000000000..b5f0949ef7
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCChannelPool.m
@@ -0,0 +1,387 @@
+/*
+ *
+ * Copyright 2015 gRPC authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#import <Foundation/Foundation.h>
+
+#import "GRPCChannelFactory.h"
+#import "GRPCChannelPool.h"
+#import "GRPCConnectivityMonitor.h"
+#import "GRPCCronetChannelFactory.h"
+#import "GRPCInsecureChannelFactory.h"
+#import "GRPCSecureChannelFactory.h"
+#import "version.h"
+
+#import <GRPCClient/GRPCCall+Cronet.h>
+#include <grpc/support/log.h>
+
+extern const char *kCFStreamVarName;
+
+// When all calls of a channel are destroyed, destroy the channel after this much seconds.
+const NSTimeInterval kChannelDestroyDelay = 30;
+
+@implementation GRPCChannelConfiguration
+
+- (nullable instancetype)initWithHost:(NSString *)host callOptions:(GRPCCallOptions *)callOptions {
+ if ((self = [super init])) {
+ _host = host;
+ _callOptions = callOptions;
+ }
+ return self;
+}
+
+- (id<GRPCChannelFactory>)channelFactory {
+ NSError *error;
+ id<GRPCChannelFactory> factory;
+ GRPCTransportType type = _callOptions.transportType;
+ switch (type) {
+ case GRPCTransportTypeDefault:
+ // TODO (mxyan): Remove when the API is deprecated
+#ifdef GRPC_COMPILE_WITH_CRONET
+ if (![GRPCCall isUsingCronet]) {
+#endif
+ factory = [GRPCSecureChannelFactory factoryWithPEMRootCerts:_callOptions.pemRootCert
+ privateKey:_callOptions.pemPrivateKey
+ certChain:_callOptions.pemCertChain
+ error:&error];
+ if (error) {
+ NSLog(@"Error creating secure channel factory: %@", error);
+ return nil;
+ }
+ return factory;
+#ifdef GRPC_COMPILE_WITH_CRONET
+ }
+#endif
+ // fallthrough
+ case GRPCTransportTypeCronet:
+ return [GRPCCronetChannelFactory sharedInstance];
+ case GRPCTransportTypeInsecure:
+ return [GRPCInsecureChannelFactory sharedInstance];
+ default:
+ GPR_UNREACHABLE_CODE(return nil);
+ }
+}
+
+- (NSMutableDictionary *)channelArgs {
+ NSMutableDictionary *args = [NSMutableDictionary new];
+
+ NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
+ NSString *userAgentPrefix = _callOptions.userAgentPrefix;
+ if (userAgentPrefix) {
+ args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] =
+ [_callOptions.userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
+ } else {
+ args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent;
+ }
+
+ NSString *hostNameOverride = _callOptions.hostNameOverride;
+ if (hostNameOverride) {
+ args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = hostNameOverride;
+ }
+
+ if (_callOptions.responseSizeLimit) {
+ args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] =
+ [NSNumber numberWithUnsignedInteger:_callOptions.responseSizeLimit];
+ }
+
+ if (_callOptions.compressAlgorithm != GRPC_COMPRESS_NONE) {
+ args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] =
+ [NSNumber numberWithInt:_callOptions.compressAlgorithm];
+ }
+
+ if (_callOptions.keepaliveInterval != 0) {
+ args[@GRPC_ARG_KEEPALIVE_TIME_MS] =
+ [NSNumber numberWithUnsignedInteger:(unsigned int)(_callOptions.keepaliveInterval * 1000)];
+ args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] =
+ [NSNumber numberWithUnsignedInteger:(unsigned int)(_callOptions.keepaliveTimeout * 1000)];
+ }
+
+ if (_callOptions.enableRetry == NO) {
+ args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:_callOptions.enableRetry];
+ }
+
+ if (_callOptions.connectMinTimeout > 0) {
+ args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] =
+ [NSNumber numberWithUnsignedInteger:(unsigned int)(_callOptions.connectMinTimeout * 1000)];
+ }
+ if (_callOptions.connectInitialBackoff > 0) {
+ args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber
+ numberWithUnsignedInteger:(unsigned int)(_callOptions.connectInitialBackoff * 1000)];
+ }
+ if (_callOptions.connectMaxBackoff > 0) {
+ args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] =
+ [NSNumber numberWithUnsignedInteger:(unsigned int)(_callOptions.connectMaxBackoff * 1000)];
+ }
+
+ if (_callOptions.logContext != nil) {
+ args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = _callOptions.logContext;
+ }
+
+ if (_callOptions.channelPoolDomain != nil) {
+ args[@GRPC_ARG_CHANNEL_POOL_DOMAIN] = _callOptions.channelPoolDomain;
+ }
+
+ [args addEntriesFromDictionary:_callOptions.additionalChannelArgs];
+
+ return args;
+}
+
+- (nonnull id)copyWithZone:(nullable NSZone *)zone {
+ GRPCChannelConfiguration *newConfig = [[GRPCChannelConfiguration alloc] init];
+ newConfig.host = _host;
+ newConfig.callOptions = _callOptions;
+
+ return newConfig;
+}
+
+- (BOOL)isEqual:(id)object {
+ NSAssert([object isKindOfClass:[GRPCChannelConfiguration class]], @"Illegal :isEqual");
+ GRPCChannelConfiguration *obj = (GRPCChannelConfiguration *)object;
+ if (!(obj.host == _host || [obj.host isEqualToString:_host])) return NO;
+ if (!(obj.callOptions.userAgentPrefix == _callOptions.userAgentPrefix ||
+ [obj.callOptions.userAgentPrefix isEqualToString:_callOptions.userAgentPrefix]))
+ return NO;
+ if (!(obj.callOptions.responseSizeLimit == _callOptions.responseSizeLimit)) return NO;
+ if (!(obj.callOptions.compressAlgorithm == _callOptions.compressAlgorithm)) return NO;
+ if (!(obj.callOptions.enableRetry == _callOptions.enableRetry)) return NO;
+ if (!(obj.callOptions.keepaliveInterval == _callOptions.keepaliveInterval)) return NO;
+ if (!(obj.callOptions.keepaliveTimeout == _callOptions.keepaliveTimeout)) return NO;
+ if (!(obj.callOptions.connectMinTimeout == _callOptions.connectMinTimeout)) return NO;
+ if (!(obj.callOptions.connectInitialBackoff == _callOptions.connectInitialBackoff)) return NO;
+ if (!(obj.callOptions.connectMaxBackoff == _callOptions.connectMaxBackoff)) return NO;
+ if (!(obj.callOptions.additionalChannelArgs == _callOptions.additionalChannelArgs ||
+ [obj.callOptions.additionalChannelArgs
+ isEqualToDictionary:_callOptions.additionalChannelArgs]))
+ return NO;
+ if (!(obj.callOptions.pemRootCert == _callOptions.pemRootCert ||
+ [obj.callOptions.pemRootCert isEqualToString:_callOptions.pemRootCert]))
+ return NO;
+ if (!(obj.callOptions.pemPrivateKey == _callOptions.pemPrivateKey ||
+ [obj.callOptions.pemPrivateKey isEqualToString:_callOptions.pemPrivateKey]))
+ return NO;
+ if (!(obj.callOptions.pemCertChain == _callOptions.pemCertChain ||
+ [obj.callOptions.pemCertChain isEqualToString:_callOptions.pemCertChain]))
+ return NO;
+ if (!(obj.callOptions.hostNameOverride == _callOptions.hostNameOverride ||
+ [obj.callOptions.hostNameOverride isEqualToString:_callOptions.hostNameOverride]))
+ return NO;
+ if (!(obj.callOptions.transportType == _callOptions.transportType)) return NO;
+ if (!(obj.callOptions.logContext == _callOptions.logContext ||
+ [obj.callOptions.logContext isEqual:_callOptions.logContext]))
+ return NO;
+ if (!(obj.callOptions.channelPoolDomain == _callOptions.channelPoolDomain ||
+ [obj.callOptions.channelPoolDomain isEqualToString:_callOptions.channelPoolDomain]))
+ return NO;
+ if (!(obj.callOptions.channelId == _callOptions.channelId)) return NO;
+
+ return YES;
+}
+
+- (NSUInteger)hash {
+ NSUInteger result = 0;
+ result ^= _host.hash;
+ result ^= _callOptions.userAgentPrefix.hash;
+ result ^= _callOptions.responseSizeLimit;
+ result ^= _callOptions.compressAlgorithm;
+ result ^= _callOptions.enableRetry;
+ result ^= (unsigned int)(_callOptions.keepaliveInterval * 1000);
+ result ^= (unsigned int)(_callOptions.keepaliveTimeout * 1000);
+ result ^= (unsigned int)(_callOptions.connectMinTimeout * 1000);
+ result ^= (unsigned int)(_callOptions.connectInitialBackoff * 1000);
+ result ^= (unsigned int)(_callOptions.connectMaxBackoff * 1000);
+ result ^= _callOptions.additionalChannelArgs.hash;
+ result ^= _callOptions.pemRootCert.hash;
+ result ^= _callOptions.pemPrivateKey.hash;
+ result ^= _callOptions.pemCertChain.hash;
+ result ^= _callOptions.hostNameOverride.hash;
+ result ^= _callOptions.transportType;
+ result ^= [_callOptions.logContext hash];
+ result ^= _callOptions.channelPoolDomain.hash;
+ result ^= _callOptions.channelId;
+
+ return result;
+}
+
+@end
+
+/**
+ * Time the channel destroy when the channel's calls are unreffed. If there's new call, reset the
+ * timer.
+ */
+@interface GRPCChannelCallRef : NSObject
+
+- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)configuration
+ destroyDelay:(NSTimeInterval)destroyDelay
+ dispatchQueue:(dispatch_queue_t)dispatchQueue
+ destroyChannel:(void (^)())destroyChannel;
+
+/** Add call ref count to the channel and maybe reset the timer. */
+- (void)refChannel;
+
+/** Reduce call ref count to the channel and maybe set the timer. */
+- (void)unrefChannel;
+
+@end
+
+@implementation GRPCChannelCallRef {
+ GRPCChannelConfiguration *_configuration;
+ NSTimeInterval _destroyDelay;
+ // We use dispatch queue for this purpose since timer invalidation must happen on the same
+ // thread which issued the timer.
+ dispatch_queue_t _dispatchQueue;
+ void (^_destroyChannel)();
+
+ NSUInteger _refCount;
+ NSTimer *_timer;
+}
+
+- (instancetype)initWithChannelConfiguration:(GRPCChannelConfiguration *)configuration
+ destroyDelay:(NSTimeInterval)destroyDelay
+ dispatchQueue:(dispatch_queue_t)dispatchQueue
+ destroyChannel:(void (^)())destroyChannel {
+ if ((self = [super init])) {
+ _configuration = configuration;
+ _destroyDelay = destroyDelay;
+ _dispatchQueue = dispatchQueue;
+ _destroyChannel = destroyChannel;
+
+ _refCount = 0;
+ _timer = nil;
+ }
+ return self;
+}
+
+// This function is protected by channel pool dispatch queue.
+- (void)refChannel {
+ _refCount++;
+ if (_timer) {
+ [_timer invalidate];
+ }
+ _timer = nil;
+}
+
+// This function is protected by channel spool dispatch queue.
+- (void)unrefChannel {
+ self->_refCount--;
+ if (self->_refCount == 0) {
+ if (self->_timer) {
+ [self->_timer invalidate];
+ }
+ self->_timer = [NSTimer scheduledTimerWithTimeInterval:self->_destroyDelay
+ target:self
+ selector:@selector(timerFire:)
+ userInfo:nil
+ repeats:NO];
+ }
+}
+
+- (void)timerFire:(NSTimer *)timer {
+ dispatch_sync(_dispatchQueue, ^{
+ if (self->_timer == nil || self->_timer != timer) {
+ return;
+ }
+ self->_timer = nil;
+ self->_destroyChannel(self->_configuration);
+ });
+}
+
+@end
+
+#pragma mark GRPCChannelPool
+
+@implementation GRPCChannelPool {
+ NSTimeInterval _channelDestroyDelay;
+ NSMutableDictionary<GRPCChannelConfiguration *, GRPCChannel *> *_channelPool;
+ NSMutableDictionary<GRPCChannelConfiguration *, GRPCChannelCallRef *> *_callRefs;
+ // Dedicated queue for timer
+ dispatch_queue_t _dispatchQueue;
+}
+
+- (instancetype)init {
+ return [self initWithChannelDestroyDelay:kChannelDestroyDelay];
+}
+
+- (instancetype)initWithChannelDestroyDelay:(NSTimeInterval)channelDestroyDelay {
+ if ((self = [super init])) {
+ _channelDestroyDelay = channelDestroyDelay;
+ _channelPool = [NSMutableDictionary dictionary];
+ _callRefs = [NSMutableDictionary dictionary];
+ _dispatchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
+
+ // Connectivity monitor is not required for CFStream
+ char *enableCFStream = getenv(kCFStreamVarName);
+ if (enableCFStream == nil || enableCFStream[0] != '1') {
+ [GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)];
+ }
+ }
+ return self;
+}
+
+- (void)dealloc {
+ // Connectivity monitor is not required for CFStream
+ char *enableCFStream = getenv(kCFStreamVarName);
+ if (enableCFStream == nil || enableCFStream[0] != '1') {
+ [GRPCConnectivityMonitor unregisterObserver:self];
+ }
+}
+
+- (GRPCChannel *)channelWithConfiguration:(GRPCChannelConfiguration *)configuration
+ createChannel:(GRPCChannel * (^)(void))createChannel {
+ __block GRPCChannel *channel;
+ dispatch_sync(_dispatchQueue, ^{
+ if ([self->_channelPool objectForKey:configuration]) {
+ [self->_callRefs[configuration] refChannel];
+ channel = self->_channelPool[configuration];
+ } else {
+ channel = createChannel();
+ self->_channelPool[configuration] = channel;
+
+ GRPCChannelCallRef *callRef = [[GRPCChannelCallRef alloc]
+ initWithChannelConfiguration:configuration
+ destroyDelay:self->_channelDestroyDelay
+ dispatchQueue:self->_dispatchQueue
+ destroyChannel:^(GRPCChannelConfiguration *configuration) {
+ [self->_channelPool removeObjectForKey:configuration];
+ [self->_callRefs removeObjectForKey:configuration];
+ }];
+ [callRef refChannel];
+ self->_callRefs[configuration] = callRef;
+ }
+ });
+ return channel;
+}
+
+- (void)unrefChannelWithConfiguration:configuration {
+ dispatch_sync(_dispatchQueue, ^{
+ if ([self->_channelPool objectForKey:configuration]) {
+ [self->_callRefs[configuration] unrefChannel];
+ }
+ });
+}
+
+- (void)clear {
+ dispatch_sync(_dispatchQueue, ^{
+ self->_channelPool = [NSMutableDictionary dictionary];
+ self->_callRefs = [NSMutableDictionary dictionary];
+ });
+}
+
+- (void)connectivityChange:(NSNotification *)note {
+ [self clear];
+}
+
+@end
diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h
new file mode 100644
index 0000000000..97e1ae920a
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.h
@@ -0,0 +1,36 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCChannelFactory.h"
+
+@class GRPCChannel;
+typedef struct stream_engine stream_engine;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GRPCCronetChannelFactory : NSObject<GRPCChannelFactory>
+
++ (nullable instancetype)sharedInstance;
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSMutableDictionary *)args;
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m
new file mode 100644
index 0000000000..5c0fe16d39
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCCronetChannelFactory.m
@@ -0,0 +1,94 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCCronetChannelFactory.h"
+
+#import "ChannelArgsUtil.h"
+#import "GRPCChannel.h"
+
+#ifdef GRPC_COMPILE_WITH_CRONET
+
+#import <Cronet/Cronet.h>
+#include <grpc/grpc_cronet.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation GRPCCronetChannelFactory {
+ stream_engine *_cronetEngine;
+}
+
++ (nullable instancetype)sharedInstance {
+ static GRPCCronetChannelFactory *instance;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ instance = [[self alloc] initWithEngine:[Cronet getGlobalEngine]];
+ });
+ return instance;
+}
+
+- (nullable instancetype)initWithEngine:(stream_engine *)engine {
+ if (!engine) {
+ [NSException raise:NSInvalidArgumentException format:@"Cronet engine is NULL. Set it first."];
+ return nil;
+ }
+ if ((self = [super init])) {
+ _cronetEngine = engine;
+ }
+ return self;
+}
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSMutableDictionary *)args {
+ // Remove client authority filter since that is not supported
+ args[@GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER] = [NSNumber numberWithInt:1];
+
+ grpc_channel_args *channelArgs = BuildChannelArgs(args);
+ grpc_channel *unmanagedChannel =
+ grpc_cronet_secure_channel_create(_cronetEngine, host.UTF8String, channelArgs, NULL);
+ FreeChannelArgs(channelArgs);
+ return unmanagedChannel;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#else
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation GRPCCronetChannelFactory
+
++ (nullable instancetype)sharedInstance {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel."];
+ return nil;
+}
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSMutableArray *)args {
+ [NSException raise:NSInvalidArgumentException
+ format:@"Must enable macro GRPC_COMPILE_WITH_CRONET to build Cronet channel."];
+ return nil;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
+
+#endif
diff --git a/src/objective-c/GRPCClient/private/GRPCHost.h b/src/objective-c/GRPCClient/private/GRPCHost.h
index 291b07df37..32d3585351 100644
--- a/src/objective-c/GRPCClient/private/GRPCHost.h
+++ b/src/objective-c/GRPCClient/private/GRPCHost.h
@@ -20,6 +20,10 @@
#import <grpc/impl/codegen/compression_types.h>
+#import "GRPCChannelFactory.h"
+
+#import <GRPCClient/GRPCCallOptions.h>
+
NS_ASSUME_NONNULL_BEGIN
@class GRPCCompletionQueue;
@@ -28,12 +32,10 @@ struct grpc_channel_credentials;
@interface GRPCHost : NSObject
-+ (void)flushChannelCache;
+ (void)resetAllHostSettings;
@property(nonatomic, readonly) NSString *address;
@property(nonatomic, copy, nullable) NSString *userAgentPrefix;
-@property(nonatomic, nullable) struct grpc_channel_credentials *channelCreds;
@property(nonatomic) grpc_compression_algorithm compressAlgorithm;
@property(nonatomic) int keepaliveInterval;
@property(nonatomic) int keepaliveTimeout;
@@ -44,14 +46,14 @@ struct grpc_channel_credentials;
@property(nonatomic) unsigned int initialConnectBackoff;
@property(nonatomic) unsigned int maxConnectBackoff;
-/** The following properties should only be modified for testing: */
+@property(nonatomic) id<GRPCChannelFactory> channelFactory;
-@property(nonatomic, getter=isSecure) BOOL secure;
+/** The following properties should only be modified for testing: */
@property(nonatomic, copy, nullable) NSString *hostNameOverride;
/** The default response size limit is 4MB. Set this to override that default. */
-@property(nonatomic, strong, nullable) NSNumber *responseSizeLimitOverride;
+@property(nonatomic) NSUInteger responseSizeLimitOverride;
- (nullable instancetype)init NS_UNAVAILABLE;
/** Host objects initialized with the same address are the same. */
@@ -62,19 +64,12 @@ struct grpc_channel_credentials;
withCertChain:(nullable NSString *)pemCertChain
error:(NSError **)errorPtr;
-/** Create a grpc_call object to the provided path on this host. */
-- (nullable struct grpc_call *)unmanagedCallWithPath:(NSString *)path
- serverName:(NSString *)serverName
- timeout:(NSTimeInterval)timeout
- completionQueue:(GRPCCompletionQueue *)queue;
-
-// TODO: There's a race when a new RPC is coming through just as an existing one is getting
-// notified that there's no connectivity. If connectivity comes back at that moment, the new RPC
-// will have its channel destroyed by the other RPC, and will never get notified of a problem, so
-// it'll hang (the C layer logs a timeout, with exponential back off). One solution could be to pass
-// the GRPCChannel to the GRPCCall, renaming this as "disconnectChannel:channel", which would only
-// act on that specific channel.
-- (void)disconnect;
+@property(atomic, readwrite) GRPCTransportType transportType;
+
+@property(readonly) GRPCMutableCallOptions *callOptions;
+
++ (BOOL)isHostConfigured:(NSString *)address;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCHost.m b/src/objective-c/GRPCClient/private/GRPCHost.m
index 862909f238..0e3fa610f9 100644
--- a/src/objective-c/GRPCClient/private/GRPCHost.m
+++ b/src/objective-c/GRPCClient/private/GRPCHost.m
@@ -18,46 +18,35 @@
#import "GRPCHost.h"
+#import <GRPCClient/GRPCCall+Cronet.h>
#import <GRPCClient/GRPCCall.h>
+
#include <grpc/grpc.h>
#include <grpc/grpc_security.h>
-#ifdef GRPC_COMPILE_WITH_CRONET
-#import <GRPCClient/GRPCCall+ChannelArg.h>
-#import <GRPCClient/GRPCCall+Cronet.h>
-#endif
-#import "GRPCChannel.h"
+#import <GRPCClient/GRPCCallOptions.h>
+#import "GRPCChannelFactory.h"
#import "GRPCCompletionQueue.h"
#import "GRPCConnectivityMonitor.h"
+#import "GRPCCronetChannelFactory.h"
+#import "GRPCSecureChannelFactory.h"
#import "NSDictionary+GRPC.h"
#import "version.h"
NS_ASSUME_NONNULL_BEGIN
-extern const char *kCFStreamVarName;
-
static NSMutableDictionary *kHostCache;
@implementation GRPCHost {
- // TODO(mlumish): Investigate whether caching channels with strong links is a good idea.
- GRPCChannel *_channel;
+ NSString *_pemRootCerts;
+ NSString *_pemPrivateKey;
+ NSString *_pemCertChain;
}
+ (nullable instancetype)hostWithAddress:(NSString *)address {
return [[self alloc] initWithAddress:address];
}
-- (void)dealloc {
- if (_channelCreds != nil) {
- grpc_channel_credentials_release(_channelCreds);
- }
- // Connectivity monitor is not required for CFStream
- char *enableCFStream = getenv(kCFStreamVarName);
- if (enableCFStream == nil || enableCFStream[0] != '1') {
- [GRPCConnectivityMonitor unregisterObserver:self];
- }
-}
-
// Default initializer.
- (nullable instancetype)initWithAddress:(NSString *)address {
if (!address) {
@@ -86,230 +75,58 @@ static NSMutableDictionary *kHostCache;
if ((self = [super init])) {
_address = address;
- _secure = YES;
- kHostCache[address] = self;
- _compressAlgorithm = GRPC_COMPRESS_NONE;
_retryEnabled = YES;
- }
-
- // Connectivity monitor is not required for CFStream
- char *enableCFStream = getenv(kCFStreamVarName);
- if (enableCFStream == nil || enableCFStream[0] != '1') {
- [GRPCConnectivityMonitor registerObserver:self selector:@selector(connectivityChange:)];
+ kHostCache[address] = self;
}
}
return self;
}
-+ (void)flushChannelCache {
- @synchronized(kHostCache) {
- [kHostCache enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, GRPCHost *_Nonnull host,
- BOOL *_Nonnull stop) {
- [host disconnect];
- }];
- }
-}
-
+ (void)resetAllHostSettings {
@synchronized(kHostCache) {
kHostCache = [NSMutableDictionary dictionary];
}
}
-- (nullable grpc_call *)unmanagedCallWithPath:(NSString *)path
- serverName:(NSString *)serverName
- timeout:(NSTimeInterval)timeout
- completionQueue:(GRPCCompletionQueue *)queue {
- // The __block attribute is to allow channel take refcount inside @synchronized block. Without
- // this attribute, retain of channel object happens after objc_sync_exit in release builds, which
- // may result in channel released before used. See grpc/#15033.
- __block GRPCChannel *channel;
- // This is racing -[GRPCHost disconnect].
- @synchronized(self) {
- if (!_channel) {
- _channel = [self newChannel];
- }
- channel = _channel;
- }
- return [channel unmanagedCallWithPath:path
- serverName:serverName
- timeout:timeout
- completionQueue:queue];
-}
-
-- (NSData *)nullTerminatedDataWithString:(NSString *)string {
- // dataUsingEncoding: does not return a null-terminated string.
- NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
- NSMutableData *nullTerminated = [NSMutableData dataWithData:data];
- [nullTerminated appendBytes:"\0" length:1];
- return nullTerminated;
-}
-
- (BOOL)setTLSPEMRootCerts:(nullable NSString *)pemRootCerts
withPrivateKey:(nullable NSString *)pemPrivateKey
withCertChain:(nullable NSString *)pemCertChain
error:(NSError **)errorPtr {
- static NSData *kDefaultRootsASCII;
- static NSError *kDefaultRootsError;
- static dispatch_once_t loading;
- dispatch_once(&loading, ^{
- NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem
- // Do not use NSBundle.mainBundle, as it's nil for tests of library projects.
- NSBundle *bundle = [NSBundle bundleForClass:self.class];
- NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"];
- NSError *error;
- // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the
- // issuer). Load them as UTF8 and produce an ASCII equivalent.
- NSString *contentInUTF8 =
- [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
- if (contentInUTF8 == nil) {
- kDefaultRootsError = error;
- return;
- }
- kDefaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8];
- });
-
- NSData *rootsASCII;
- if (pemRootCerts != nil) {
- rootsASCII = [self nullTerminatedDataWithString:pemRootCerts];
- } else {
- if (kDefaultRootsASCII == nil) {
- if (errorPtr) {
- *errorPtr = kDefaultRootsError;
- }
- NSAssert(
- kDefaultRootsASCII,
- @"Could not read gRPCCertificates.bundle/roots.pem. This file, "
- "with the root certificates, is needed to establish secure (TLS) connections. "
- "Because the file is distributed with the gRPC library, this error is usually a sign "
- "that the library wasn't configured correctly for your project. Error: %@",
- kDefaultRootsError);
- return NO;
- }
- rootsASCII = kDefaultRootsASCII;
- }
-
- grpc_channel_credentials *creds;
- if (pemPrivateKey == nil && pemCertChain == nil) {
- creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL);
- } else {
- grpc_ssl_pem_key_cert_pair key_cert_pair;
- NSData *privateKeyASCII = [self nullTerminatedDataWithString:pemPrivateKey];
- NSData *certChainASCII = [self nullTerminatedDataWithString:pemCertChain];
- key_cert_pair.private_key = privateKeyASCII.bytes;
- key_cert_pair.cert_chain = certChainASCII.bytes;
- creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL);
- }
-
- @synchronized(self) {
- if (_channelCreds != nil) {
- grpc_channel_credentials_release(_channelCreds);
- }
- _channelCreds = creds;
- }
-
+ _pemRootCerts = pemRootCerts;
+ _pemPrivateKey = pemPrivateKey;
+ _pemCertChain = pemCertChain;
return YES;
}
-- (NSDictionary *)channelArgsUsingCronet:(BOOL)useCronet {
- NSMutableDictionary *args = [NSMutableDictionary dictionary];
-
- // TODO(jcanizales): Add OS and device information (see
- // https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#user-agents ).
- NSString *userAgent = @"grpc-objc/" GRPC_OBJC_VERSION_STRING;
- if (_userAgentPrefix) {
- userAgent = [_userAgentPrefix stringByAppendingFormat:@" %@", userAgent];
- }
- args[@GRPC_ARG_PRIMARY_USER_AGENT_STRING] = userAgent;
-
- if (_secure && _hostNameOverride) {
- args[@GRPC_SSL_TARGET_NAME_OVERRIDE_ARG] = _hostNameOverride;
- }
-
- if (_responseSizeLimitOverride) {
- args[@GRPC_ARG_MAX_RECEIVE_MESSAGE_LENGTH] = _responseSizeLimitOverride;
- }
-
- if (_compressAlgorithm != GRPC_COMPRESS_NONE) {
- args[@GRPC_COMPRESSION_CHANNEL_DEFAULT_ALGORITHM] = [NSNumber numberWithInt:_compressAlgorithm];
- }
-
- if (_keepaliveInterval != 0) {
- args[@GRPC_ARG_KEEPALIVE_TIME_MS] = [NSNumber numberWithInt:_keepaliveInterval];
- args[@GRPC_ARG_KEEPALIVE_TIMEOUT_MS] = [NSNumber numberWithInt:_keepaliveTimeout];
- }
-
- id logContext = self.logContext;
- if (logContext != nil) {
- args[@GRPC_ARG_MOBILE_LOG_CONTEXT] = logContext;
- }
-
- if (useCronet) {
- args[@GRPC_ARG_DISABLE_CLIENT_AUTHORITY_FILTER] = [NSNumber numberWithInt:1];
- }
-
- if (_retryEnabled == NO) {
- args[@GRPC_ARG_ENABLE_RETRIES] = [NSNumber numberWithInt:0];
- }
-
- if (_minConnectTimeout > 0) {
- args[@GRPC_ARG_MIN_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_minConnectTimeout];
- }
- if (_initialConnectBackoff > 0) {
- args[@GRPC_ARG_INITIAL_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_initialConnectBackoff];
- }
- if (_maxConnectBackoff > 0) {
- args[@GRPC_ARG_MAX_RECONNECT_BACKOFF_MS] = [NSNumber numberWithInt:_maxConnectBackoff];
- }
-
- return args;
-}
-
-- (GRPCChannel *)newChannel {
- BOOL useCronet = NO;
-#ifdef GRPC_COMPILE_WITH_CRONET
- useCronet = [GRPCCall isUsingCronet];
-#endif
- NSDictionary *args = [self channelArgsUsingCronet:useCronet];
- if (_secure) {
- GRPCChannel *channel;
- @synchronized(self) {
- if (_channelCreds == nil) {
- [self setTLSPEMRootCerts:nil withPrivateKey:nil withCertChain:nil error:nil];
- }
-#ifdef GRPC_COMPILE_WITH_CRONET
- if (useCronet) {
- channel = [GRPCChannel secureCronetChannelWithHost:_address channelArgs:args];
- } else
-#endif
- {
- channel =
- [GRPCChannel secureChannelWithHost:_address credentials:_channelCreds channelArgs:args];
- }
- }
- return channel;
- } else {
- return [GRPCChannel insecureChannelWithHost:_address channelArgs:args];
- }
-}
-
-- (NSString *)hostName {
- // TODO(jcanizales): Default to nil instead of _address when Issue #2635 is clarified.
- return _hostNameOverride ?: _address;
+- (GRPCCallOptions *)callOptions {
+ GRPCMutableCallOptions *options = [[GRPCMutableCallOptions alloc] init];
+ options.userAgentPrefix = _userAgentPrefix;
+ options.responseSizeLimit = _responseSizeLimitOverride;
+ options.compressAlgorithm = (GRPCCompressAlgorithm)_compressAlgorithm;
+ options.enableRetry = _retryEnabled;
+ options.keepaliveInterval = (NSTimeInterval)_keepaliveInterval / 1000;
+ options.keepaliveTimeout = (NSTimeInterval)_keepaliveTimeout / 1000;
+ options.connectMinTimeout = (NSTimeInterval)_minConnectTimeout / 1000;
+ options.connectInitialBackoff = (NSTimeInterval)_initialConnectBackoff / 1000;
+ options.connectMaxBackoff = (NSTimeInterval)_maxConnectBackoff / 1000;
+ options.pemRootCert = _pemRootCerts;
+ options.pemPrivateKey = _pemPrivateKey;
+ options.pemCertChain = _pemCertChain;
+ options.hostNameOverride = _hostNameOverride;
+ options.transportType = _transportType;
+ options.logContext = _logContext;
+
+ return options;
}
-- (void)disconnect {
- // This is racing -[GRPCHost unmanagedCallWithPath:completionQueue:].
- @synchronized(self) {
- _channel = nil;
++ (BOOL)isHostConfigured:(NSString *)address {
+ // TODO (mxyan): Remove when old API is deprecated
+ NSURL *hostURL = [NSURL URLWithString:[@"https://" stringByAppendingString:address]];
+ if (hostURL.host && !hostURL.port) {
+ address = [hostURL.host stringByAppendingString:@":443"];
}
-}
-
-// Flushes the host cache when connectivity status changes or when connection switch between Wifi
-// and Cellular data, so that a new call will use a new channel. Otherwise, a new call will still
-// use the cached channel which is no longer available and will cause gRPC to hang.
-- (void)connectivityChange:(NSNotification *)note {
- [self disconnect];
+ GRPCHost *cachedHost = kHostCache[address];
+ return (cachedHost != nil);
}
@end
diff --git a/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h
new file mode 100644
index 0000000000..905a181ca7
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.h
@@ -0,0 +1,35 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCChannelFactory.h"
+
+@class GRPCChannel;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GRPCInsecureChannelFactory : NSObject<GRPCChannelFactory>
+
++ (nullable instancetype)sharedInstance;
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSMutableDictionary *)args;
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m
new file mode 100644
index 0000000000..41860bf6ff
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCInsecureChannelFactory.m
@@ -0,0 +1,48 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCInsecureChannelFactory.h"
+
+#import "ChannelArgsUtil.h"
+#import "GRPCChannel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation GRPCInsecureChannelFactory
+
++ (nullable instancetype)sharedInstance {
+ static GRPCInsecureChannelFactory *instance;
+ static dispatch_once_t onceToken;
+ dispatch_once(&onceToken, ^{
+ instance = [[self alloc] init];
+ });
+ return instance;
+}
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSMutableDictionary *)args {
+ grpc_channel_args *coreChannelArgs = BuildChannelArgs([args copy]);
+ grpc_channel *unmanagedChannel =
+ grpc_insecure_channel_create(host.UTF8String, coreChannelArgs, NULL);
+ FreeChannelArgs(coreChannelArgs);
+ return unmanagedChannel;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h
new file mode 100644
index 0000000000..ab5eee478e
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.h
@@ -0,0 +1,38 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCChannelFactory.h"
+
+@class GRPCChannel;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface GRPCSecureChannelFactory : NSObject<GRPCChannelFactory>
+
++ (nullable instancetype)factoryWithPEMRootCerts:(nullable NSString *)rootCerts
+ privateKey:(nullable NSString *)privateKey
+ certChain:(nullable NSString *)certChain
+ error:(NSError **)errorPtr;
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSMutableDictionary *)args;
+
+- (nullable instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
new file mode 100644
index 0000000000..a0d56e4bdc
--- /dev/null
+++ b/src/objective-c/GRPCClient/private/GRPCSecureChannelFactory.m
@@ -0,0 +1,129 @@
+/*
+ *
+ * Copyright 2018 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 "GRPCSecureChannelFactory.h"
+
+#include <grpc/grpc_security.h>
+
+#import "ChannelArgsUtil.h"
+#import "GRPCChannel.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation GRPCSecureChannelFactory {
+ grpc_channel_credentials *_channelCreds;
+}
+
++ (nullable instancetype)factoryWithPEMRootCerts:(nullable NSString *)rootCerts
+ privateKey:(nullable NSString *)privateKey
+ certChain:(nullable NSString *)certChain
+ error:(NSError **)errorPtr {
+ return [[self alloc] initWithPEMRootCerts:rootCerts
+ privateKey:privateKey
+ certChain:certChain
+ error:errorPtr];
+}
+
+- (NSData *)nullTerminatedDataWithString:(NSString *)string {
+ // dataUsingEncoding: does not return a null-terminated string.
+ NSData *data = [string dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
+ NSMutableData *nullTerminated = [NSMutableData dataWithData:data];
+ [nullTerminated appendBytes:"\0" length:1];
+ return nullTerminated;
+}
+
+- (nullable instancetype)initWithPEMRootCerts:(nullable NSString *)rootCerts
+ privateKey:(nullable NSString *)privateKey
+ certChain:(nullable NSString *)certChain
+ error:(NSError **)errorPtr {
+ static NSData *kDefaultRootsASCII;
+ static NSError *kDefaultRootsError;
+ static dispatch_once_t loading;
+ dispatch_once(&loading, ^{
+ NSString *defaultPath = @"gRPCCertificates.bundle/roots"; // .pem
+ // Do not use NSBundle.mainBundle, as it's nil for tests of library projects.
+ NSBundle *bundle = [NSBundle bundleForClass:self.class];
+ NSString *path = [bundle pathForResource:defaultPath ofType:@"pem"];
+ NSError *error;
+ // Files in PEM format can have non-ASCII characters in their comments (e.g. for the name of the
+ // issuer). Load them as UTF8 and produce an ASCII equivalent.
+ NSString *contentInUTF8 =
+ [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:&error];
+ if (contentInUTF8 == nil) {
+ kDefaultRootsError = error;
+ return;
+ }
+ kDefaultRootsASCII = [self nullTerminatedDataWithString:contentInUTF8];
+ });
+
+ NSData *rootsASCII;
+ if (rootCerts != nil) {
+ rootsASCII = [self nullTerminatedDataWithString:rootCerts];
+ } else {
+ if (kDefaultRootsASCII == nil) {
+ if (errorPtr) {
+ *errorPtr = kDefaultRootsError;
+ }
+ NSAssert(
+ kDefaultRootsASCII,
+ @"Could not read gRPCCertificates.bundle/roots.pem. This file, "
+ "with the root certificates, is needed to establish secure (TLS) connections. "
+ "Because the file is distributed with the gRPC library, this error is usually a sign "
+ "that the library wasn't configured correctly for your project. Error: %@",
+ kDefaultRootsError);
+ return nil;
+ }
+ rootsASCII = kDefaultRootsASCII;
+ }
+
+ grpc_channel_credentials *creds;
+ if (privateKey == nil && certChain == nil) {
+ creds = grpc_ssl_credentials_create(rootsASCII.bytes, NULL, NULL, NULL);
+ } else {
+ grpc_ssl_pem_key_cert_pair key_cert_pair;
+ NSData *privateKeyASCII = [self nullTerminatedDataWithString:privateKey];
+ NSData *certChainASCII = [self nullTerminatedDataWithString:certChain];
+ key_cert_pair.private_key = privateKeyASCII.bytes;
+ key_cert_pair.cert_chain = certChainASCII.bytes;
+ creds = grpc_ssl_credentials_create(rootsASCII.bytes, &key_cert_pair, NULL, NULL);
+ }
+
+ if ((self = [super init])) {
+ _channelCreds = creds;
+ }
+ return self;
+}
+
+- (nullable grpc_channel *)createChannelWithHost:(NSString *)host
+ channelArgs:(nullable NSMutableArray *)args {
+ grpc_channel_args *coreChannelArgs = BuildChannelArgs([args copy]);
+ grpc_channel *unmanagedChannel =
+ grpc_secure_channel_create(_channelCreds, host.UTF8String, coreChannelArgs, NULL);
+ FreeChannelArgs(coreChannelArgs);
+ return unmanagedChannel;
+}
+
+- (void)dealloc {
+ if (_channelCreds != nil) {
+ grpc_channel_credentials_release(_channelCreds);
+ }
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h
index f711850c2f..19aa5367c7 100644
--- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.h
+++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.h
@@ -74,9 +74,8 @@
@interface GRPCWrappedCall : NSObject
- (instancetype)initWithHost:(NSString *)host
- serverName:(NSString *)serverName
path:(NSString *)path
- timeout:(NSTimeInterval)timeout NS_DESIGNATED_INITIALIZER;
+ callOptions:(GRPCCallOptions *)callOptions NS_DESIGNATED_INITIALIZER;
- (void)startBatchWithOperations:(NSArray *)ops errorHandler:(void (^)(void))errorHandler;
diff --git a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m
index 7781d27ca4..1c03bc9efd 100644
--- a/src/objective-c/GRPCClient/private/GRPCWrappedCall.m
+++ b/src/objective-c/GRPCClient/private/GRPCWrappedCall.m
@@ -23,6 +23,7 @@
#include <grpc/grpc.h>
#include <grpc/support/alloc.h>
+#import "GRPCChannel.h"
#import "GRPCCompletionQueue.h"
#import "GRPCHost.h"
#import "NSData+GRPC.h"
@@ -235,31 +236,28 @@
@implementation GRPCWrappedCall {
GRPCCompletionQueue *_queue;
+ GRPCChannel *_channel;
grpc_call *_call;
}
- (instancetype)init {
- return [self initWithHost:nil serverName:nil path:nil timeout:0];
+ return [self initWithHost:nil path:nil callOptions:[[GRPCCallOptions alloc] init]];
}
- (instancetype)initWithHost:(NSString *)host
- serverName:(NSString *)serverName
path:(NSString *)path
- timeout:(NSTimeInterval)timeout {
+ callOptions:(GRPCCallOptions *)callOptions {
if (!path || !host) {
[NSException raise:NSInvalidArgumentException format:@"path and host cannot be nil."];
}
- if (self = [super init]) {
+ if ((self = [super init])) {
// Each completion queue consumes one thread. There's a trade to be made between creating and
// consuming too many threads and having contention of multiple calls in a single completion
// queue. Currently we use a singleton queue.
_queue = [GRPCCompletionQueue completionQueue];
-
- _call = [[GRPCHost hostWithAddress:host] unmanagedCallWithPath:path
- serverName:serverName
- timeout:timeout
- completionQueue:_queue];
+ _channel = [GRPCChannel channelWithHost:host callOptions:callOptions];
+ _call = [_channel unmanagedCallWithPath:path completionQueue:_queue callOptions:callOptions];
if (_call == NULL) {
return nil;
}
@@ -313,6 +311,8 @@
- (void)dealloc {
grpc_call_unref(_call);
+ [_channel unmanagedCallUnref];
+ _channel = nil;
}
@end