Merge commit '36bca61764984ff5395653cf8377ec5daa71b709' as 'libs/protobuf'
This commit is contained in:
237
libs/protobuf/objectivec/GPBRootObject.m
Normal file
237
libs/protobuf/objectivec/GPBRootObject.m
Normal file
@@ -0,0 +1,237 @@
|
||||
// Protocol Buffers - Google's data interchange format
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// https://developers.google.com/protocol-buffers/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
#import "GPBRootObject_PackagePrivate.h"
|
||||
|
||||
#import <objc/runtime.h>
|
||||
#import <os/lock.h>
|
||||
|
||||
#import <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#import "GPBDescriptor.h"
|
||||
#import "GPBExtensionRegistry.h"
|
||||
#import "GPBUtilities_PackagePrivate.h"
|
||||
|
||||
@interface GPBExtensionDescriptor (GPBRootObject)
|
||||
// Get singletonName as a c string.
|
||||
- (const char *)singletonNameC;
|
||||
@end
|
||||
|
||||
// We need some object to conform to the MessageSignatureProtocol to make sure
|
||||
// the selectors in it are recorded in our Objective C runtime information.
|
||||
// GPBMessage is arguably the more "obvious" choice, but given that all messages
|
||||
// inherit from GPBMessage, conflicts seem likely, so we are using GPBRootObject
|
||||
// instead.
|
||||
@interface GPBRootObject () <GPBMessageSignatureProtocol>
|
||||
@end
|
||||
|
||||
@implementation GPBRootObject
|
||||
|
||||
// Taken from http://www.burtleburtle.net/bob/hash/doobs.html
|
||||
// Public Domain
|
||||
static uint32_t jenkins_one_at_a_time_hash(const char *key) {
|
||||
uint32_t hash = 0;
|
||||
for (uint32_t i = 0; key[i] != '\0'; ++i) {
|
||||
hash += key[i];
|
||||
hash += (hash << 10);
|
||||
hash ^= (hash >> 6);
|
||||
}
|
||||
hash += (hash << 3);
|
||||
hash ^= (hash >> 11);
|
||||
hash += (hash << 15);
|
||||
return hash;
|
||||
}
|
||||
|
||||
// Key methods for our custom CFDictionary.
|
||||
// Note that the dictionary lasts for the lifetime of our app, so no need
|
||||
// to worry about deallocation. All of the items are added to it at
|
||||
// startup, and so the keys don't need to be retained/released.
|
||||
// Keys are NULL terminated char *.
|
||||
static const void *GPBRootExtensionKeyRetain(__unused CFAllocatorRef allocator, const void *value) {
|
||||
return value;
|
||||
}
|
||||
|
||||
static void GPBRootExtensionKeyRelease(__unused CFAllocatorRef allocator,
|
||||
__unused const void *value) {}
|
||||
|
||||
static CFStringRef GPBRootExtensionCopyKeyDescription(const void *value) {
|
||||
const char *key = (const char *)value;
|
||||
return CFStringCreateWithCString(kCFAllocatorDefault, key, kCFStringEncodingUTF8);
|
||||
}
|
||||
|
||||
static Boolean GPBRootExtensionKeyEqual(const void *value1, const void *value2) {
|
||||
const char *key1 = (const char *)value1;
|
||||
const char *key2 = (const char *)value2;
|
||||
return strcmp(key1, key2) == 0;
|
||||
}
|
||||
|
||||
static CFHashCode GPBRootExtensionKeyHash(const void *value) {
|
||||
const char *key = (const char *)value;
|
||||
return jenkins_one_at_a_time_hash(key);
|
||||
}
|
||||
|
||||
// Long ago, this was an OSSpinLock, but then it came to light that there were issues for that on
|
||||
// iOS:
|
||||
// http://mjtsai.com/blog/2015/12/16/osspinlock-is-unsafe/
|
||||
// https://lists.swift.org/pipermail/swift-dev/Week-of-Mon-20151214/000372.html
|
||||
// It was changed to a dispatch_semaphore_t, but that has potential for priority inversion issues.
|
||||
// The minOS versions are now high enough that os_unfair_lock can be used, and should provide
|
||||
// all the support we need. For more information in the concurrency/locking space see:
|
||||
// https://gist.github.com/tclementdev/6af616354912b0347cdf6db159c37057
|
||||
// https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html
|
||||
// https://developer.apple.com/videos/play/wwdc2017/706/
|
||||
static os_unfair_lock gExtensionSingletonDictionaryLock = OS_UNFAIR_LOCK_INIT;
|
||||
static CFMutableDictionaryRef gExtensionSingletonDictionary = NULL;
|
||||
static GPBExtensionRegistry *gDefaultExtensionRegistry = NULL;
|
||||
|
||||
+ (void)initialize {
|
||||
// Ensure the global is started up.
|
||||
if (!gExtensionSingletonDictionary) {
|
||||
CFDictionaryKeyCallBacks keyCallBacks = {
|
||||
// See description above for reason for using custom dictionary.
|
||||
0,
|
||||
GPBRootExtensionKeyRetain,
|
||||
GPBRootExtensionKeyRelease,
|
||||
GPBRootExtensionCopyKeyDescription,
|
||||
GPBRootExtensionKeyEqual,
|
||||
GPBRootExtensionKeyHash,
|
||||
};
|
||||
gExtensionSingletonDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &keyCallBacks,
|
||||
&kCFTypeDictionaryValueCallBacks);
|
||||
gDefaultExtensionRegistry = [[GPBExtensionRegistry alloc] init];
|
||||
}
|
||||
|
||||
if ([self superclass] == [GPBRootObject class]) {
|
||||
// This is here to start up all the per file "Root" subclasses.
|
||||
// This must be done in initialize to enforce thread safety of start up of
|
||||
// the protocol buffer library.
|
||||
[self extensionRegistry];
|
||||
}
|
||||
}
|
||||
|
||||
+ (GPBExtensionRegistry *)extensionRegistry {
|
||||
// Is overridden in all the subclasses that provide extensions to provide the
|
||||
// per class one.
|
||||
return gDefaultExtensionRegistry;
|
||||
}
|
||||
|
||||
+ (void)globallyRegisterExtension:(GPBExtensionDescriptor *)field {
|
||||
const char *key = [field singletonNameC];
|
||||
os_unfair_lock_lock(&gExtensionSingletonDictionaryLock);
|
||||
CFDictionarySetValue(gExtensionSingletonDictionary, key, field);
|
||||
os_unfair_lock_unlock(&gExtensionSingletonDictionaryLock);
|
||||
}
|
||||
|
||||
static id ExtensionForName(id self, SEL _cmd) {
|
||||
// Really fast way of doing "classname_selName".
|
||||
// This came up as a hotspot (creation of NSString *) when accessing a
|
||||
// lot of extensions.
|
||||
const char *selName = sel_getName(_cmd);
|
||||
if (selName[0] == '_') {
|
||||
return nil; // Apple internal selector.
|
||||
}
|
||||
size_t selNameLen = 0;
|
||||
while (1) {
|
||||
char c = selName[selNameLen];
|
||||
if (c == '\0') { // String end.
|
||||
break;
|
||||
}
|
||||
if (c == ':') {
|
||||
return nil; // Selector took an arg, not one of the runtime methods.
|
||||
}
|
||||
++selNameLen;
|
||||
}
|
||||
|
||||
const char *className = class_getName(self);
|
||||
size_t classNameLen = strlen(className);
|
||||
char key[classNameLen + selNameLen + 2];
|
||||
memcpy(key, className, classNameLen);
|
||||
key[classNameLen] = '_';
|
||||
memcpy(&key[classNameLen + 1], selName, selNameLen);
|
||||
key[classNameLen + 1 + selNameLen] = '\0';
|
||||
|
||||
// NOTE: Even though this method is called from another C function,
|
||||
// gExtensionSingletonDictionaryLock and gExtensionSingletonDictionary
|
||||
// will always be initialized. This is because this call flow is just to
|
||||
// lookup the Extension, meaning the code is calling an Extension class
|
||||
// message on a Message or Root class. This guarantees that the class was
|
||||
// initialized and Message classes ensure their Root was also initialized.
|
||||
NSAssert(gExtensionSingletonDictionary, @"Startup order broken!");
|
||||
|
||||
os_unfair_lock_lock(&gExtensionSingletonDictionaryLock);
|
||||
id extension = (id)CFDictionaryGetValue(gExtensionSingletonDictionary, key);
|
||||
// We can't remove the key from the dictionary here (as an optimization),
|
||||
// two threads could have gone into +resolveClassMethod: for the same method,
|
||||
// and ended up here; there's no way to ensure both return YES without letting
|
||||
// both try to wire in the method.
|
||||
os_unfair_lock_unlock(&gExtensionSingletonDictionaryLock);
|
||||
return extension;
|
||||
}
|
||||
|
||||
BOOL GPBResolveExtensionClassMethod(Class self, SEL sel) {
|
||||
// Another option would be to register the extensions with the class at
|
||||
// globallyRegisterExtension:
|
||||
// Timing the two solutions, this solution turned out to be much faster
|
||||
// and reduced startup time, and runtime memory.
|
||||
// The advantage to globallyRegisterExtension is that it would reduce the
|
||||
// size of the protos somewhat because the singletonNameC wouldn't need
|
||||
// to include the class name. For a class with a lot of extensions it
|
||||
// can add up. You could also significantly reduce the code complexity of this
|
||||
// file.
|
||||
id extension = ExtensionForName(self, sel);
|
||||
if (extension != nil) {
|
||||
const char *encoding = GPBMessageEncodingForSelector(@selector(getClassValue), NO);
|
||||
Class metaClass = objc_getMetaClass(class_getName(self));
|
||||
IMP imp = imp_implementationWithBlock(^(__unused id obj) {
|
||||
return extension;
|
||||
});
|
||||
BOOL methodAdded = class_addMethod(metaClass, sel, imp, encoding);
|
||||
// class_addMethod() is documented as also failing if the method was already
|
||||
// added; so we check if the method is already there and return success so
|
||||
// the method dispatch will still happen. Why would it already be added?
|
||||
// Two threads could cause the same method to be bound at the same time,
|
||||
// but only one will actually bind it; the other still needs to return true
|
||||
// so things will dispatch.
|
||||
if (!methodAdded) {
|
||||
methodAdded = GPBClassHasSel(metaClass, sel);
|
||||
}
|
||||
return methodAdded;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)resolveClassMethod:(SEL)sel {
|
||||
if (GPBResolveExtensionClassMethod(self, sel)) {
|
||||
return YES;
|
||||
}
|
||||
return [super resolveClassMethod:sel];
|
||||
}
|
||||
|
||||
@end
|
||||
Reference in New Issue
Block a user