Skip to content

Commit

Permalink
Add ObjC helpers for Any WKT.
Browse files Browse the repository at this point in the history
- Capture the ObjC prefix used when generating the the file.
- Track the containing type on descriptors.
- Mark descriptors where the message class name got a suffix added to it.
- Expose a fullName property on Descriptors.
- Add helpers for packing/unpacking Any messages.
- Bump the ObjC runtime version number. Since we added methods and invoke them
  in the generated code, ensure the code is running against a matching version.
  Otherwise, someone could compile against headers, but run with a framework
  that is older and get unknown selector failures.  This should trip clearer
  messaging.

Fixes protocolbuffers#1674
  • Loading branch information
thomasvl committed Sep 8, 2016
1 parent 4bc1657 commit 337ec30
Show file tree
Hide file tree
Showing 34 changed files with 557 additions and 38 deletions.
1 change: 1 addition & 0 deletions objectivec/DevTools/compile_testing_protos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ compile_protos() {
# sources can be generated from them.

CORE_PROTO_FILES=(
src/google/protobuf/any_test.proto
src/google/protobuf/unittest_arena.proto
src/google/protobuf/unittest_custom_options.proto
src/google/protobuf/unittest_enormous_descriptor.proto
Expand Down
2 changes: 1 addition & 1 deletion objectivec/GPBBootstrap.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,4 @@
// regenerated.
//
// Meant to be used internally by generated code.
#define GOOGLE_PROTOBUF_OBJC_GEN_VERSION 30001
#define GOOGLE_PROTOBUF_OBJC_GEN_VERSION 30002
9 changes: 9 additions & 0 deletions objectivec/GPBDescriptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ typedef NS_ENUM(uint8_t, GPBFieldType) {
@property(nonatomic, readonly, getter=isWireFormat) BOOL wireFormat;
/** The class of this message. */
@property(nonatomic, readonly) Class messageClass;
/** Containing message descriptor if this message is nested, or nil otherwise. */
@property(readonly, nullable) GPBDescriptor *containingType;
/**
* Fully qualified name for this message (package.message). Can be nil if the
* value is unable to be computed.
*/
@property(readonly, nullable) NSString *fullName;

/**
* Gets the field for the given number.
Expand Down Expand Up @@ -118,6 +125,8 @@ typedef NS_ENUM(uint8_t, GPBFieldType) {

/** The package declared in the proto file. */
@property(nonatomic, readonly, copy) NSString *package;
/** The objc prefix declared in the proto file. */
@property(nonatomic, readonly, copy, nullable) NSString *objcPrefix;
/** The syntax of the proto file. */
@property(nonatomic, readonly) GPBFileSyntax syntax;

Expand Down
114 changes: 113 additions & 1 deletion objectivec/GPBDescriptor.m
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdirect-ivar-access"

// The address of this variable is used as a key for obj_getAssociatedObject.
// The addresses of these variables are used as keys for objc_getAssociatedObject.
static const char kTextFormatExtraValueKey = 0;
static const char kParentClassNameValueKey = 0;
static const char kClassNameSuffixKey = 0;

// Utility function to generate selectors on the fly.
static SEL SelFromStrings(const char *prefix, const char *middle,
Expand Down Expand Up @@ -215,10 +217,102 @@ - (void)setupExtensionRanges:(const GPBExtensionRange *)ranges count:(int32_t)co
extensionRangesCount_ = count;
}

- (void)setupContainingMessageClassName:(const char *)msgClassName {
// Note: Only fetch the class here, can't send messages to it because
// that could cause cycles back to this class within +initialize if
// two messages have each other in fields (i.e. - they build a graph).
NSAssert(objc_getClass(msgClassName), @"Class %s not defined", msgClassName);
NSValue *parentNameValue = [NSValue valueWithPointer:msgClassName];
objc_setAssociatedObject(self, &kParentClassNameValueKey,
parentNameValue,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (void)setupMessageClassNameSuffix:(NSString *)suffix {
if (suffix.length) {
objc_setAssociatedObject(self, &kClassNameSuffixKey,
suffix,
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}

- (NSString *)name {
return NSStringFromClass(messageClass_);
}

- (GPBDescriptor *)containingType {
NSValue *parentNameValue =
objc_getAssociatedObject(self, &kParentClassNameValueKey);
if (!parentNameValue) {
return nil;
}
const char *parentName = [parentNameValue pointerValue];
Class parentClass = objc_getClass(parentName);
NSAssert(parentClass, @"Class %s not defined", parentName);
return [parentClass descriptor];
}

- (NSString *)fullName {
NSString *className = NSStringFromClass(self.messageClass);
GPBFileDescriptor *file = self.file;
NSString *objcPrefix = file.objcPrefix;
if (objcPrefix && ![className hasPrefix:objcPrefix]) {
NSAssert(0,
@"Class didn't have correct prefix? (%@ - %@)",
className, objcPrefix);
return nil;
}
GPBDescriptor *parent = self.containingType;

NSString *name = nil;
if (parent) {
NSString *parentClassName = NSStringFromClass(parent.messageClass);
// The generator will add _Class to avoid reserved words, drop it.
NSString *suffix = objc_getAssociatedObject(parent, &kClassNameSuffixKey);
if (suffix) {
if (![parentClassName hasSuffix:suffix]) {
NSAssert(0,
@"ParentMessage class didn't have correct suffix? (%@ - %@)",
className, suffix);
return nil;
}
parentClassName =
[parentClassName substringToIndex:(parentClassName.length - suffix.length)];
}
NSString *parentPrefix = [parentClassName stringByAppendingString:@"_"];
if (![className hasPrefix:parentPrefix]) {
NSAssert(0,
@"Class didn't have the correct parent name prefix? (%@ - %@)",
parentPrefix, className);
return nil;
}
name = [className substringFromIndex:parentPrefix.length];
} else {
name = [className substringFromIndex:objcPrefix.length];
}

// The generator will add _Class to avoid reserved words, drop it.
NSString *suffix = objc_getAssociatedObject(self, &kClassNameSuffixKey);
if (suffix) {
if (![name hasSuffix:suffix]) {
NSAssert(0,
@"Message class didn't have correct suffix? (%@ - %@)",
name, suffix);
return nil;
}
name = [name substringToIndex:(name.length - suffix.length)];
}

NSString *prefix = (parent != nil ? parent.fullName : file.package);
NSString *result;
if (prefix.length > 0) {
result = [NSString stringWithFormat:@"%@.%@", prefix, name];
} else {
result = name;
}
return result;
}

- (id)copyWithZone:(NSZone *)zone {
#pragma unused(zone)
return [self retain];
Expand Down Expand Up @@ -255,12 +349,26 @@ - (GPBOneofDescriptor *)oneofWithName:(NSString *)name {

@implementation GPBFileDescriptor {
NSString *package_;
NSString *objcPrefix_;
GPBFileSyntax syntax_;
}

@synthesize package = package_;
@synthesize objcPrefix = objcPrefix_;
@synthesize syntax = syntax_;

- (instancetype)initWithPackage:(NSString *)package
objcPrefix:(NSString *)objcPrefix
syntax:(GPBFileSyntax)syntax {
self = [super init];
if (self) {
package_ = [package copy];
objcPrefix_ = [objcPrefix copy];
syntax_ = syntax;
}
return self;
}

- (instancetype)initWithPackage:(NSString *)package
syntax:(GPBFileSyntax)syntax {
self = [super init];
Expand All @@ -273,6 +381,7 @@ - (instancetype)initWithPackage:(NSString *)package

- (void)dealloc {
[package_ release];
[objcPrefix_ release];
[super dealloc];
}

Expand Down Expand Up @@ -416,6 +525,9 @@ - (instancetype)initWithFieldDescription:(void *)description
// Extra type specific data.
if (isMessage) {
const char *className = coreDesc->dataTypeSpecific.className;
// Note: Only fetch the class here, can't send messages to it because
// that could cause cycles back to this class within +initialize if
// two messages have each other in fields (i.e. - they build a graph).
msgClass_ = objc_getClass(className);
NSAssert(msgClass_, @"Class %s not defined", className);
} else if (dataType == GPBDataTypeEnum) {
Expand Down
5 changes: 5 additions & 0 deletions objectivec/GPBDescriptor_PackagePrivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,15 @@ typedef NS_OPTIONS(uint32_t, GPBDescriptorInitializationFlags) {
firstHasIndex:(int32_t)firstHasIndex;
- (void)setupExtraTextInfo:(const char *)extraTextFormatInfo;
- (void)setupExtensionRanges:(const GPBExtensionRange *)ranges count:(int32_t)count;
- (void)setupContainingMessageClassName:(const char *)msgClassName;
- (void)setupMessageClassNameSuffix:(NSString *)suffix;

@end

@interface GPBFileDescriptor ()
- (instancetype)initWithPackage:(NSString *)package
objcPrefix:(NSString *)objcPrefix
syntax:(GPBFileSyntax)syntax;
- (instancetype)initWithPackage:(NSString *)package
syntax:(GPBFileSyntax)syntax;
@end
Expand Down
125 changes: 125 additions & 0 deletions objectivec/GPBWellKnownTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,32 @@
#endif

#if GPB_USE_PROTOBUF_FRAMEWORK_IMPORTS
#import <Protobuf/Any.pbobjc.h>
#import <Protobuf/Duration.pbobjc.h>
#import <Protobuf/Timestamp.pbobjc.h>
#else
#import "google/protobuf/Any.pbobjc.h"
#import "google/protobuf/Duration.pbobjc.h"
#import "google/protobuf/Timestamp.pbobjc.h"
#endif

NS_ASSUME_NONNULL_BEGIN

#pragma mark - Errors

/** NSError domain used for errors. */
extern NSString *const GPBWellKnownTypesErrorDomain;

/** Error code for NSError with GPBWellKnownTypesErrorDomain. */
typedef NS_ENUM(NSInteger, GPBWellKnownTypesErrorCode) {
/** The type_url could not be computed for the requested GPBMessage class. */
GPBWellKnownTypesErrorCodeFailedToComputeTypeURL = -100,
/** type_url in a Any doesn’t match that of the requested GPBMessage class. */
GPBWellKnownTypesErrorCodeTypeURLMismatch = -101,
};

#pragma mark - GPBTimestamp

/**
* Category for GPBTimestamp to work with standard Foundation time/date types.
**/
Expand Down Expand Up @@ -77,6 +94,8 @@ NS_ASSUME_NONNULL_BEGIN

@end

#pragma mark - GPBDuration

/**
* Category for GPBDuration to work with standard Foundation time type.
**/
Expand All @@ -96,4 +115,110 @@ NS_ASSUME_NONNULL_BEGIN

@end

#pragma mark - GPBAny

/**
* Category for GPBAny to help work with the message within the object.
**/
@interface GPBAny (GBPWellKnownTypes)

/**
* Convenience method to create a GPBAny containing the serialized message.
* This uses type.googleapis.com/ as the type_url's prefix.
*
* @param message The message to be packed into the GPBAny.
* @param errorPtr Pointer to an error that will be populated if something goes
* wrong.
*
* @return A newly configured GPBAny with the given message, or nil on failure.
*/
+ (nullable instancetype)anyWithMessage:(nonnull GPBMessage *)message
error:(NSError **)errorPtr;

/**
* Convenience method to create a GPBAny containing the serialized message.
*
* @param message The message to be packed into the GPBAny.
* @param typeURLPrefix The URL prefix to apply for type_url.
* @param errorPtr Pointer to an error that will be populated if something
* goes wrong.
*
* @return A newly configured GPBAny with the given message, or nil on failure.
*/
+ (nullable instancetype)anyWithMessage:(nonnull GPBMessage *)message
typeURLPrefix:(nonnull NSString *)typeURLPrefix
error:(NSError **)errorPtr;

/**
* Initializes a GPBAny to contain the serialized message. This uses
* type.googleapis.com/ as the type_url's prefix.
*
* @param message The message to be packed into the GPBAny.
* @param errorPtr Pointer to an error that will be populated if something goes
* wrong.
*
* @return A newly configured GPBAny with the given message, or nil on failure.
*/
- (nullable instancetype)initWithMessage:(nonnull GPBMessage *)message
error:(NSError **)errorPtr;

/**
* Initializes a GPBAny to contain the serialized message.
*
* @param message The message to be packed into the GPBAny.
* @param typeURLPrefix The URL prefix to apply for type_url.
* @param errorPtr Pointer to an error that will be populated if something
* goes wrong.
*
* @return A newly configured GPBAny with the given message, or nil on failure.
*/
- (nullable instancetype)initWithMessage:(nonnull GPBMessage *)message
typeURLPrefix:(nonnull NSString *)typeURLPrefix
error:(NSError **)errorPtr;

/**
* Packs the serialized message into this GPBAny. This uses
* type.googleapis.com/ as the type_url's prefix.
*
* @param message The message to be packed into the GPBAny.
* @param errorPtr Pointer to an error that will be populated if something goes
* wrong.
*
* @return Whether the packing was successful or not.
*/
- (BOOL)packWithMessage:(nonnull GPBMessage *)message
error:(NSError **)errorPtr;

/**
* Packs the serialized message into this GPBAny.
*
* @param message The message to be packed into the GPBAny.
* @param typeURLPrefix The URL prefix to apply for type_url.
* @param errorPtr Pointer to an error that will be populated if something
* goes wrong.
*
* @return Whether the packing was successful or not.
*/
- (BOOL)packWithMessage:(nonnull GPBMessage *)message
typeURLPrefix:(nonnull NSString *)typeURLPrefix
error:(NSError **)errorPtr;

/**
* Unpacks the serialized message as if it was an instance of the given class.
*
* @note When checking type_url, the base URL is not checked, only the fully
* qualified name.
*
* @param messageClass The class to use to deserialize the contained message.
* @param errorPtr Pointer to an error that will be populated if something
* goes wrong.
*
* @return An instance of the given class populated with the contained data, or
* nil on failure.
*/
- (nullable GPBMessage *)unpackMessageClass:(Class)messageClass
error:(NSError **)errorPtr;

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 337ec30

Please sign in to comment.