Signal-iOS/YapDatabase/Internal/YapDatabaseString.h
2012-12-24 11:46:50 -06:00

118 lines
4.3 KiB
C

/**
* There are a LOT of conversions from NSString to char array.
* This happens in almost every method, where we bind text to prepared sqlite3 statements.
*
* It is inefficient to use [key UTF8String] for these situations.
* The Apple documentation is very explicit concerning the UTF8String method:
*
* > The returned C string is automatically freed just as a returned object would be released;
* > you should copy the C string if [you need] to store it outside of the
* > autorelease context in which the C string is created.
*
* In other words, the UTF8String method does a malloc for character buffer, copies the characters,
* and autoreleases the buffer (just like an autoreleased NSData instance).
*
* Thus we suffer a bunch of malloc's if we use UTF8String.
*
* Considering that almost all keys are likely to be relatively small,
* a much faster technique is to use the stack instead of the heap (with obvious precautions, see below).
*
* Note: This technique ONLY applies to key names and collection names.
* It does NOT apply to object/primitiveData or metadata. Those are binded to sqlite3 statements using binary blobs.
**/
/**
* We must be cautious and conservative so as to avoid stack overflow.
* This is possibly if really huge key names or collection names are used.
*
* The number below represents the largest amount of memory (in bytes) that will be allocated on the stack per string.
**/
#define YapDatabaseStringMaxStackLength (1024 * 4)
/**
* Struct designed to be allocated on the stack.
* You then use the inline functions below to "setup" and "teardown" the struct.
* For example:
*
* > YapDatabaseString myKeyChar;
* > MakeYapDatabaseString(&myKeyChar, myNSStringKey);
* > ...
* > sqlite3_bind_text(statement, position, myKeyChar.str, myKeyChar.length, SQLITE_STATIC);
* > ...
* > sqlite3_clear_bindings(statement);
* > sqlite3_reset(statement);
* > FreeYapDatabaseString(&myKeyChar);
*
* There are 2 "public" fields:
* str - Pointer to the char[] string.
* length - Represents the length (in bytes) of the char[] str (excluding the NULL termination byte, as usual).
*
* The other 2 "private" fielda are for internal use:
* strStack - If the string doesn't exceed YapDatabaseStringMaxStackLength,
* then the bytes are copied here (onto stack storage), and str actually points to strStack.
* strHeap - If the string exceeds YapDatabaseStringMaxStackLength,
* the space is allocated on the heap, strHeap holds the pointer, and str has the same pointer.
*
* Thus the "setup" and "teardown" methods below will automatically switch to heap storage (just like UTF8String),
* if the key/collection name is too long, and performance will be equivalent.
* But in the common case of short key/collection names, we can skip the more expensive heap allocation/deallocation.
**/
struct YapDatabaseString {
NSUInteger length;
char strStack[YapDatabaseStringMaxStackLength];
char *strHeap;
char *str; // Pointer to either strStack or strHeap
};
typedef struct YapDatabaseString YapDatabaseString;
/**
* Initializes the YapDatabaseString structure.
* It will automatically use heap storage if the given NSString is too long.
*
* This method should always be balanced with a call to FreeYapDatabaseString.
**/
NS_INLINE void MakeYapDatabaseString(YapDatabaseString *dbStr, NSString *nsStr)
{
if (nsStr)
{
dbStr->length = [nsStr lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
if ((dbStr->length + 1) <= YapDatabaseStringMaxStackLength)
{
dbStr->strHeap = NULL;
dbStr->str = dbStr->strStack;
}
else
{
dbStr->strHeap = (char *)malloc((dbStr->length + 1));
dbStr->str = dbStr->strHeap;
}
[nsStr getCString:dbStr->str maxLength:(dbStr->length + 1) encoding:NSUTF8StringEncoding];
}
else
{
dbStr->length = 0;
dbStr->strHeap = NULL;
dbStr->str = NULL;
}
}
/**
* If heap storage was needed (because the string length exceeded YapDatabaseStringMaxStackLength),
* this method frees the heap allocated memory.
*
* In the common case of stack storage, strHeap will be NULL, and this method is essentially a no-op.
*
* This method should be invoked AFTER sqlite3_clear_bindings (assuming SQLITE_STATIC is used).
**/
NS_INLINE void FreeYapDatabaseString(YapDatabaseString *dbStr)
{
if (dbStr->strHeap)
{
free(dbStr->strHeap);
dbStr->strHeap = NULL;
}
}