Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

initial commit

  • Loading branch information...
commit 47056c4f0c5cc81ddeda4343be7c0cf4cbd51b2b 0 parents
Whitney Young wbyoung authored
2  .gitignore
... ... @@ -0,0 +1,2 @@
  1 +.DS_Store
  2 +build
49 FRClassSetup.h
... ... @@ -0,0 +1,49 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <Foundation/Foundation.h>
  19 +
  20 +typedef void (*FRClassSetupCallback)(Class class, BOOL dynamicLoaded);
  21 +
  22 +/*!
  23 + \brief Add a class setup callback
  24 + \details The class setup callback will be called immediately for all classes in the runtime.
  25 + It will also be called when FREnsureClassCallbacksHaveBeenCalled is called (either
  26 + explicaitly or because a library was loaded). Returns TRUE if the callback was added.
  27 + */
  28 +BOOL FRAddClassSetupCallback(FRClassSetupCallback);
  29 +
  30 +/*!
  31 + \brief Remove a class setup callback
  32 + \details The class setup callback will no longer be used. Returns TRUE if the callback was removed.
  33 + */
  34 +BOOL FRRemoveClassSetupCallback(FRClassSetupCallback);
  35 +
  36 +/*!
  37 + \brief Ensure the class setup callbacks have been performed for all classes.
  38 + \details This will make class setup callbacks for any classes that have been added to the runtime
  39 + (the method may safely be called multiple times). You should call this method if you're
  40 + dynamically adding classes that need to use features depending on class setup callbacks.
  41 + Additionally, the implementation that is paired with this file will watch for any images
  42 + that get loaded into the process. This is useful, but uses a private function from the dyld
  43 + API. If you wish to disable this feature, define RED_SINGLETON_DISABLE_DYLD_PRIVATE_API_USE
  44 + during compilation. If you choose to disable the use of the dyld API, you may wish to call
  45 + FREnsureClassCallbacksHaveBeenCalled early on in your application's startup (and/or) after
  46 + you load any code in order to ensure that features depending on class setup callbacks work
  47 + properly.
  48 + */
  49 +void FREnsureClassCallbacksHaveBeenCalled(void);
155 FRClassSetup.m
... ... @@ -0,0 +1,155 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <pthread.h>
  19 +#import <mach-o/dyld.h>
  20 +#import <mach-o/dyld_images.h>
  21 +#import <objc/runtime.h>
  22 +
  23 +#import "FRClassSetup.h"
  24 +
  25 +#if __has_feature(objc_arc)
  26 +#define bridge_ptr __bridge void *
  27 +#else
  28 +#define bridge_ptr void *
  29 +#endif
  30 +
  31 +typedef struct {
  32 + CFMutableSetRef functions;
  33 + pthread_mutex_t lock;
  34 +} FRCallbackInfo;
  35 +
  36 +static FRCallbackInfo FRClassSetupCallbackInfo(void);
  37 +static FRCallbackInfo FRClassSetupCallbackInfo(void) {
  38 + static FRCallbackInfo callbackFunctions = {};
  39 + static dispatch_once_t once;
  40 + dispatch_once(&once, ^{
  41 + callbackFunctions.functions = CFSetCreateMutable(NULL, 0, &(CFSetCallBacks){});
  42 + pthread_mutex_init(&callbackFunctions.lock, NULL);
  43 + });
  44 + return callbackFunctions;
  45 +}
  46 +
  47 +BOOL FRAddClassSetupCallback(FRClassSetupCallback callback) {
  48 + FRCallbackInfo callbackInfo = FRClassSetupCallbackInfo();
  49 + pthread_mutex_lock(&callbackInfo.lock);
  50 + BOOL add = !CFSetContainsValue(callbackInfo.functions, callback);
  51 + if (add) {
  52 + CFSetAddValue(callbackInfo.functions, callback);
  53 +
  54 + // call for all classes in the runtime
  55 + int classCount = objc_getClassList(NULL, 0);
  56 + if (classCount > 0) {
  57 + Class *classes = (Class *)malloc(sizeof(Class) * classCount);
  58 + classCount = objc_getClassList(classes, classCount);
  59 + for (int i = 0; i < classCount; i++) {
  60 + callback(classes[i], FALSE);
  61 + }
  62 + free(classes);
  63 + }
  64 + }
  65 + pthread_mutex_unlock(&callbackInfo.lock);
  66 + return add;
  67 +}
  68 +
  69 +BOOL FRRemoveClassSetupCallback(FRClassSetupCallback callback) {
  70 + FRCallbackInfo callbackInfo = FRClassSetupCallbackInfo();
  71 + pthread_mutex_lock(&callbackInfo.lock);
  72 + BOOL remove = CFSetContainsValue(callbackInfo.functions, callback);
  73 + if (remove) {
  74 + CFSetRemoveValue(callbackInfo.functions, callback);
  75 + }
  76 + pthread_mutex_unlock(&callbackInfo.lock);
  77 + return remove;
  78 +}
  79 +
  80 +static void _FREnsureClassCallbacksHaveBeenCalled(BOOL dynamic);
  81 +static void _FREnsureClassCallbacksHaveBeenCalled(BOOL dynamic) {
  82 + static CFMutableSetRef setupClasses = NULL;
  83 + static dispatch_once_t once;
  84 + dispatch_once(&once, ^{
  85 + setupClasses = CFSetCreateMutable(NULL, 0, &(CFSetCallBacks){});
  86 + });
  87 +
  88 + FRCallbackInfo callbackInfo = FRClassSetupCallbackInfo();
  89 + pthread_mutex_lock(&callbackInfo.lock);
  90 +
  91 + int classCount = objc_getClassList(NULL, 0);
  92 + CFIndex callbackCount = CFSetGetCount(callbackInfo.functions);
  93 +
  94 + // this is declared in the dynamic support header file. it can be used to re-setup the runtime
  95 + // support for singletons after images have been added or after classes have been created at
  96 + // runtime.
  97 + if (classCount > 0 && callbackCount > 0) {
  98 + Class *classes = (Class *)malloc(sizeof(Class) * classCount);
  99 + classCount = objc_getClassList(classes, classCount);
  100 +
  101 + FRClassSetupCallback *callbacks = malloc(sizeof(FRClassSetupCallback) * callbackCount);
  102 + CFSetGetValues(callbackInfo.functions, (const void **)callbacks);
  103 +
  104 + for (int i = 0; i < classCount; i++) {
  105 + Class class = classes[i];
  106 + if (!CFSetContainsValue(setupClasses, (bridge_ptr)class)) {
  107 + for (int j = 0; j < callbackCount; j++) {
  108 + callbacks[j](class, dynamic);
  109 + }
  110 + }
  111 + }
  112 + free(classes);
  113 + free(callbacks);
  114 + }
  115 +
  116 + pthread_mutex_unlock(&callbackInfo.lock);
  117 +}
  118 +
  119 +void FREnsureClassCallbacksHaveBeenCalled(void) {
  120 + _FREnsureClassCallbacksHaveBeenCalled(FALSE);
  121 +}
  122 +
  123 +#ifndef RED_SINGLETON_DISABLE_DYLD_PRIVATE_API_USE
  124 +
  125 +// dyld private
  126 +enum dyld_image_states {
  127 + dyld_image_state_mapped = 10, // no batch
  128 + dyld_image_state_dependents_mapped = 20, // only batch
  129 + dyld_image_state_rebased = 30,
  130 + dyld_image_state_bound = 40,
  131 + dyld_image_state_dependents_initialized = 45, // single notification
  132 + dyld_image_state_initialized = 50,
  133 + dyld_image_state_terminated = 60, // single notification
  134 +};
  135 +typedef const char *(*dyld_image_state_change_handler)(enum dyld_image_states, uint32_t, const struct dyld_image_info[]);
  136 +extern void dyld_register_image_state_change_handler(enum dyld_image_states, bool, dyld_image_state_change_handler);
  137 +
  138 +static bool gHandlerSetup = false;
  139 +static const char* FRImageInitializedHandler(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]);
  140 +static const char* FRImageInitializedHandler(enum dyld_image_states state, uint32_t infoCount, const struct dyld_image_info info[]) {
  141 + if (gHandlerSetup) {
  142 + // only ensure runtime setup once the handler is fully set up since there's
  143 + // no need to ensure setup for all images already in the intialized state.
  144 + _FREnsureClassCallbacksHaveBeenCalled(TRUE);
  145 + }
  146 + return NULL;
  147 +}
  148 +
  149 +static void initialize(void) __attribute__((constructor));
  150 +static void initialize(void) {
  151 + dyld_register_image_state_change_handler(dyld_image_state_initialized, true, FRImageInitializedHandler);
  152 + gHandlerSetup = true;
  153 +}
  154 +
  155 +#endif
41 FRRuntimeAdditions.h
... ... @@ -0,0 +1,41 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <Foundation/Foundation.h>
  19 +
  20 +typedef IMP *IMPPointer;
  21 +
  22 +@interface NSObject (FRRuntimeAdditions)
  23 +
  24 +/*!
  25 + \brief Performs a method swizzle
  26 + \details Swizzles original with replacement.
  27 + This is a little different from traditional method swizzling, but the concept is the same. The
  28 + original method will be replaced by a new method, and you can have the old implementation stored
  29 + in an IMP pointer for use in the swizzled method. This works out to be a little safer than method
  30 + swizzling since the _cmd argument can be passed through to the original IMP unaltered.
  31 + */
  32 ++ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store;
  33 +
  34 +/*!
  35 + \brief Performs a class method swizzle
  36 + \details Swizzles original with replacement.
  37 + \see NSObject::swizzle:with:store: for details.
  38 + */
  39 ++ (BOOL)swizzleClassMethod:(SEL)original with:(IMP)replacement store:(IMPPointer)store;
  40 +
  41 +@end
50 FRRuntimeAdditions.m
... ... @@ -0,0 +1,50 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <objc/runtime.h>
  19 +
  20 +#import "FRRuntimeAdditions.h"
  21 +
  22 +BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store);
  23 +BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
  24 + BOOL success = FALSE;
  25 + IMP imp = NULL;
  26 + Method method = class_getInstanceMethod(class, original);
  27 + if (method) {
  28 + // ensure the method is defined in this class by trying to add it to the class, get the new method
  29 + // pointer if it was actually added (this is now considered the original), then get the imp for the
  30 + // original method & replace the method.
  31 + const char *type = method_getTypeEncoding(method);
  32 + if (class_addMethod(class, original, method_getImplementation(method), type)) {
  33 + method = class_getInstanceMethod(class, original);
  34 + }
  35 + imp = method_getImplementation(method);
  36 + success = TRUE;
  37 + class_replaceMethod(class, original, replacement, type);
  38 + }
  39 + if (imp && store) { *store = imp; }
  40 + return success;
  41 +}
  42 +
  43 +@implementation NSObject (FRRuntimeAdditions)
  44 ++ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
  45 + return class_swizzleMethodAndStore(self, original, replacement, store);
  46 +}
  47 ++ (BOOL)swizzleClassMethod:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
  48 + return class_swizzleMethodAndStore(object_getClass(self), original, replacement, store);
  49 +}
  50 +@end
28 FRSingleton.h
... ... @@ -0,0 +1,28 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <Foundation/Foundation.h>
  19 +
  20 +/*!
  21 + \brief Classes that implement this protocol will be singletons
  22 + \details The class will return the same object every time alloc is called. The alloc method is
  23 + still thread safe and can be used to simply implement shared instance class methods.
  24 + Relies on class setup callbacks.
  25 + \see FREnsureClassCallbacksHaveBeenCalled
  26 + */
  27 +@protocol FRSingleton
  28 +@end
257 FRSingleton.m
... ... @@ -0,0 +1,257 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <dlfcn.h>
  19 +#import <objc/runtime.h>
  20 +
  21 +#import "FRSingleton.h"
  22 +#import "FRClassSetup.h"
  23 +#import "FRRuntimeAdditions.h"
  24 +
  25 +#if __has_feature(objc_arc)
  26 +typedef __unsafe_unretained id unid;
  27 +#define bridge_ptr __bridge void *
  28 +#define bridge_obj __bridge id
  29 +#define obj_explicit_retain(x) (__bridge id)(__bridge_retained void *)x
  30 +#else
  31 +typedef id unid;
  32 +#define bridge_ptr void *
  33 +#define bridge_obj id
  34 +#define obj_explicit_retain(x) [x retain]
  35 +#endif
  36 +
  37 +@interface NSObject (FRObjectSingletonAdditionsDynamic)
  38 ++ (BOOL)hasSingletonProtocol;
  39 ++ (unid)singletonObjectWithZone:(NSZone *)zone; // must call hasSingletonProtocol first
  40 +@end
  41 +
  42 +// load & setup
  43 +static void FRSingletonLoad(unid self);
  44 +static void FRSingletonClassSetup(Class self, BOOL dynamic);
  45 +
  46 +// swizzling
  47 +static void *(*SSingletonAllocWithZone)(unid self, SEL _cmd, NSZone *zone) = NULL;
  48 +static void *(FRSingletonAllocWithZone)(unid self, SEL _cmd, NSZone *zone);
  49 +
  50 +// dynamic methods
  51 +static BOOL FRSingletonProtocolYes(unid self, SEL _cmd);
  52 +static BOOL FRSingletonProtocolNo(unid self, SEL _cmd);
  53 +static BOOL FRSingletonProtocolInitialCheck(unid self, SEL _cmd);
  54 +static unid FRSingletonAllocWithZoneBlockIMP(unid self, SEL _cmd, NSZone *zone);
  55 +static unid FRSingletonAllocWithZoneAssociations(unid self, SEL _cmd, NSZone *zone);
  56 +
  57 +// helpers & debugging
  58 +static unid FRSingletonCreate(unid self, SEL _cmd, NSZone *zone);
  59 +static unid FRSingletonVerify(unid self, SEL _cmd, unid singleton, Class assocclass);
  60 +static Class FRSingletonAssociationClass(unid self);
  61 +unid red_singleton_debug(void);
  62 +
  63 +// globals (constant, calculated during load)
  64 +static const char *gBoolMethodType = NULL;
  65 +static const char *gAllocMethodType = NULL;
  66 +IMP (*gIMP_implementationWithBlockFunc)(void *block) = NULL;
  67 +
  68 +
  69 +@implementation NSObject (FRObjectSingletonAdditions)
  70 +
  71 +#pragma mark -
  72 +#pragma mark general setup
  73 +// ----------------------------------------------------------------------------------------------------
  74 +// general setup
  75 +// ----------------------------------------------------------------------------------------------------
  76 +
  77 ++ (void)load {
  78 + // need to get the type encoding for bool methods
  79 + unid objectMetaClass = object_getClass([NSObject class]);
  80 + Method boolMethod = class_getInstanceMethod(objectMetaClass, @selector(isProxy));
  81 + Method objectMethod = class_getInstanceMethod(objectMetaClass, @selector(allocWithZone:));
  82 + gBoolMethodType = method_getTypeEncoding(boolMethod);
  83 + gAllocMethodType = method_getTypeEncoding(objectMethod);
  84 + gIMP_implementationWithBlockFunc = dlsym(RTLD_DEFAULT, "imp_implementationWithBlock");
  85 +
  86 + // perform general setup
  87 + FRAddClassSetupCallback(FRSingletonClassSetup);
  88 + FRSingletonLoad(self);
  89 +}
  90 +
  91 +
  92 +#pragma mark -
  93 +#pragma mark singleton load
  94 +// ----------------------------------------------------------------------------------------------------
  95 +// singleton load
  96 +// ----------------------------------------------------------------------------------------------------
  97 +
  98 +static void FRSingletonLoad(unid self) {
  99 + [self swizzleClassMethod:@selector(allocWithZone:)
  100 + with:(IMP)FRSingletonAllocWithZone
  101 + store:(IMPPointer)&SSingletonAllocWithZone];
  102 +}
  103 +
  104 +
  105 +#pragma mark -
  106 +#pragma mark singleton setup (for every class)
  107 +// ----------------------------------------------------------------------------------------------------
  108 +// singleton setup (for every class)
  109 +// ----------------------------------------------------------------------------------------------------
  110 +
  111 +static void FRSingletonClassSetup(Class class, BOOL dynamic) {
  112 + // we explicitly add a class method for singleton checks to every class to ensure that all classes
  113 + // handle singleton checking on their own.
  114 + Class metaClass = object_getClass(class);
  115 + SEL selector = @selector(hasSingletonProtocol);
  116 + class_addMethod(metaClass, selector, (IMP)FRSingletonProtocolInitialCheck, gBoolMethodType);
  117 +}
  118 +
  119 +
  120 +#pragma mark -
  121 +#pragma mark helper methods
  122 +// ----------------------------------------------------------------------------------------------------
  123 +// helper methods
  124 +// ----------------------------------------------------------------------------------------------------
  125 +
  126 +static unid FRSingletonCreate(unid self, SEL _cmd, NSZone *zone) {
  127 + id singleton = [(bridge_obj)SSingletonAllocWithZone(self, _cmd, zone) init];
  128 + // from now on, init should simply return self. the standard alloc, init sequence will always return
  129 + // the singleton and will only perform init on it once. this allows use of the singleton in nib files
  130 + // when it has already been used before nib loading occurs.
  131 + [self swizzle:@selector(init) with:[self instanceMethodForSelector:@selector(self)] store:NULL];
  132 + return singleton;
  133 +}
  134 +
  135 +static unid FRSingletonVerify(unid self, SEL _cmd, unid singleton, Class assocclass) {
  136 + if (assocclass != self && ![singleton isKindOfClass:self]) {
  137 + NSLog(@"An attempt to access a singleton through %@ was "
  138 + @"made after first creating the singleton through %@. "
  139 + @"The result of this atempt has been set to is nil. "
  140 + @"Break on red_singleton_debug to debug.", self, assocclass);
  141 + singleton = red_singleton_debug();
  142 + }
  143 + return singleton;
  144 +}
  145 +
  146 +static Class FRSingletonAssociationClass(unid self) {
  147 + Class assocclass = self;
  148 + Class superclass = [self superclass];
  149 + while (superclass) {
  150 + if (![superclass hasSingletonProtocol]) { break; }
  151 + assocclass = superclass;
  152 + superclass = [superclass superclass];
  153 + }
  154 + return assocclass;
  155 +}
  156 +
  157 +
  158 +#pragma mark -
  159 +#pragma mark debug methods
  160 +// ----------------------------------------------------------------------------------------------------
  161 +// debug methods
  162 +// ----------------------------------------------------------------------------------------------------
  163 +
  164 +unid red_singleton_debug(void) {
  165 + return nil;
  166 +}
  167 +
  168 +
  169 +#pragma mark -
  170 +#pragma mark dynamic methods
  171 +// ----------------------------------------------------------------------------------------------------
  172 +// dynamic methods
  173 +// ----------------------------------------------------------------------------------------------------
  174 +
  175 +static BOOL FRSingletonProtocolYes(unid self, SEL _cmd) { return YES; }
  176 +static BOOL FRSingletonProtocolNo(unid self, SEL _cmd) { return NO; }
  177 +static BOOL FRSingletonProtocolInitialCheck(unid self, SEL _cmd) {
  178 + // we check this one time and then from then on, we just use a cached version. it's okay to swizzle
  179 + // this since every class has it's own implementation of this method (thanks to the work done in
  180 + // the singleton setup).
  181 +
  182 + // race condition: the intial check could happen for a class at the same time. if this method is
  183 + // called simultaneously, the result will be the same, and there will be no negative side effects.
  184 + BOOL has = [self conformsToProtocol:@protocol(FRSingleton)];
  185 + BOOL (*method)(unid, SEL) = has ?
  186 + FRSingletonProtocolYes :
  187 + FRSingletonProtocolNo;
  188 + [self swizzleClassMethod:@selector(hasSingletonProtocol) with:(IMP)method store:NULL];
  189 +
  190 + // add the object calculation method in on the assocation class (the top most ancestor class of
  191 + // self that supports the singleton protocol).
  192 + Class assocmeta = object_getClass(FRSingletonAssociationClass(self));
  193 + unid (*lookup)(unid, SEL, NSZone *) = gIMP_implementationWithBlockFunc ?
  194 + FRSingletonAllocWithZoneBlockIMP :
  195 + FRSingletonAllocWithZoneAssociations;
  196 + class_addMethod(assocmeta, @selector(singletonObjectWithZone:), (IMP)lookup, gAllocMethodType);
  197 +
  198 + return [self hasSingletonProtocol];
  199 +}
  200 +
  201 +static unid FRSingletonAllocWithZoneBlockIMP(unid self, SEL _cmd, NSZone *zone) {
  202 + Class assocclass = FRSingletonAssociationClass(self);
  203 +
  204 + __block unid singleton = nil;
  205 + IMP result = gIMP_implementationWithBlockFunc((bridge_ptr)^(unid _self, NSZone *_zone) {
  206 + return FRSingletonVerify(_self, _cmd, singleton, assocclass);
  207 + });
  208 +
  209 + @synchronized(self) {
  210 + unid (*lookup)(unid, SEL, NSZone *) = (void *)[assocclass methodForSelector:@selector(singletonObjectWithZone:)];
  211 + if (lookup == FRSingletonAllocWithZoneBlockIMP) {
  212 + singleton = FRSingletonCreate(self, _cmd, zone);
  213 + [assocclass swizzleClassMethod:@selector(singletonObjectWithZone:) with:(IMP)result store:NULL];
  214 + }
  215 + }
  216 +
  217 + return [self singletonObjectWithZone:zone];
  218 +}
  219 +
  220 +static unid FRSingletonAllocWithZoneAssociations(unid self, SEL _cmd, NSZone *zone) {
  221 + // storing associations in a global object to avoid issues with associations when
  222 + // using garbage collection and classes sometimes not being allocated in a gc zone.
  223 + static unid FRSingletonObjects = @"FRSingletonObjects";
  224 + void *assocclass = (bridge_ptr)FRSingletonAssociationClass(self);
  225 + unid singleton = nil;
  226 + if ((singleton = objc_getAssociatedObject(FRSingletonObjects, assocclass)) == nil) {
  227 + @synchronized(self) {
  228 + if ((singleton = objc_getAssociatedObject(FRSingletonObjects, assocclass)) == nil) {
  229 + singleton = FRSingletonCreate(self, _cmd, zone);
  230 + objc_setAssociatedObject(FRSingletonObjects, assocclass, singleton, OBJC_ASSOCIATION_ASSIGN);
  231 + }
  232 + }
  233 + }
  234 + return FRSingletonVerify(self, _cmd, singleton, (bridge_obj)assocclass);
  235 +}
  236 +
  237 +
  238 +#pragma mark -
  239 +#pragma mark swizzling (implementation)
  240 +// ----------------------------------------------------------------------------------------------------
  241 +// swizzling (implementation)
  242 +// ----------------------------------------------------------------------------------------------------
  243 +
  244 +static void *FRSingletonAllocWithZone(unid self, SEL _cmd, NSZone *zone) {
  245 + if ([self hasSingletonProtocol]) {
  246 + // calling through imp to preserve the _cmd argument
  247 + IMP singletonObjectWithZone = [self methodForSelector:@selector(singletonObjectWithZone:)];
  248 + void *singleton = nil;
  249 + if (singletonObjectWithZone) {
  250 + singleton = (bridge_ptr)obj_explicit_retain(singletonObjectWithZone(self, _cmd, zone));
  251 + }
  252 + return singleton;
  253 + }
  254 + else { return SSingletonAllocWithZone(self, _cmd, zone); }
  255 +}
  256 +
  257 +@end
14 License
... ... @@ -0,0 +1,14 @@
  1 +Copyright (c) 2011 FadingRed LLC
  2 +
  3 +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  4 +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  5 +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  6 +permit persons to whom the Software is furnished to do so, subject to the following conditions:
  7 +
  8 +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  9 +Software.
  10 +
  11 +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  12 +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  13 +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  14 +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
62 Makefile
... ... @@ -0,0 +1,62 @@
  1 +#
  2 +# Copyright (c) 2011 FadingRed LLC
  3 +#
  4 +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +# permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +#
  9 +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +# Software.
  11 +#
  12 +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +#
  17 +
  18 +# Flags for clang with ARC
  19 +CC=clang
  20 +CFLAGS=-std=c99 -Wall -fobjc-arc
  21 +LDFLAGS=
  22 +
  23 +# # Flags for clang with GC
  24 +# CC=clang
  25 +# CFLAGS=-std=c99 -Wall -fobjc-gc-only
  26 +# LDFLAGS=
  27 +
  28 +# # Flags for gcc
  29 +# CC=gcc
  30 +# CFLAGS=-std=c99 -Wall
  31 +# LDFLAGS=
  32 +
  33 +SOURCES:=$(shell echo *.m)
  34 +HEADERS:=$(shell echo *.h)
  35 +OBJECTS:=$(foreach obj,$(SOURCES:.m=.o),build/$(obj))
  36 +TEST_SOURCES:=$(shell echo tests/test_*.m)
  37 +TEST_LIBSRCS:=$(shell echo tests/lib_*.m)
  38 +TEST_HEADERS:=$(shell echo tests/test_*.h)
  39 +TEST_EXECUTABLES:=$(foreach test,$(TEST_SOURCES:.m=),$(subst tests/,build/,$(test))) build/test_speedwithout
  40 +TEST_LIBS:=$(foreach lib,$(TEST_LIBSRCS:.m=.dylib),$(subst tests/,build/,$(lib)))
  41 +
  42 +all: build $(TEST_EXECUTABLES) $(TEST_LIBS) $(OBJECTS)
  43 +
  44 +build:
  45 + mkdir -p build
  46 +
  47 +build/%.o: %.m $(HEADERS)
  48 + $(CC) -c $(CFLAGS) $< -o $@
  49 +
  50 +build/%.dylib: tests/%.m $(TEST_HEADERS)
  51 + $(CC) -dynamiclib -framework Foundation $(CFLAGS) $< -o $@
  52 +
  53 +build/%: tests/%.m $(OBJECTS) $(TEST_HEADERS)
  54 + $(CC) $(CFLAGS) -I. -framework Foundation $< $(OBJECTS) -o $@
  55 +
  56 +build/test_speedwithout: tests/test_speed.m $(TEST_HEADERS)
  57 + $(CC) $(CFLAGS) -I. -framework Foundation $< -o $@
  58 +
  59 +clean:
  60 + rm -rf build
  61 +
  62 +.PHONY: all clean
29 Readme.md
Source Rendered
... ... @@ -0,0 +1,29 @@
  1 +# Simple Singletons
  2 +
  3 +This project allows you to easily make a class a singleton in Objective-C. With this project you can write:
  4 +
  5 + @interface FRDatabaseManager : NSObject <FRSingleton>
  6 + @end
  7 +
  8 +And your class is now a singleton -- that's all there is to it!
  9 +
  10 +This method works by returning your singleton object from the alloc method. For convenience and clarity, you can also implement an accessor for the singleton:
  11 +
  12 + @implementation FRDatabaseManager
  13 + + (id)sharedDatabaseManager { return [[self alloc] autorelease]; }
  14 + @end
  15 +
  16 +
  17 +## Background
  18 +
  19 +The project was first presented at [SpikedCocoa](http://www.spikedcocoa.com/) as an example of using the Objective-C runtime to our advantage. This project is by no means intended to promote wide use of the singleton design pattern. You should be aware of the drawbacks that singletons have before using them. If you decide to use singletons, though, this will significantly decrease the code you write and you'll have a better, more consistent implementation.
  20 +
  21 +
  22 +## Considerations
  23 +
  24 +Singleton support is achieved by this project by [swizzling](http://www.cocoadev.com/index.pl?MethodSwizzling) the `NSObject::allocWithZone:` method. The implementation takes into consideration the safety and speed implications of swizzling this method.
  25 +
  26 +
  27 +## Compatibility
  28 +
  29 +This code is compatible with Mac OS X 10.6 and later. It is most optimized on Mac OS X 10.7, as it is able to use `IMP_implementationWithBlock`. It will compile with `gcc` and `clang` and works with Automatic Reference Counting (ARC) as well as Garbage Collection.
28 tests/lib_loadable.m
... ... @@ -0,0 +1,28 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <Foundation/Foundation.h>
  19 +
  20 +@protocol FRSingleton
  21 +@end
  22 +
  23 +@interface FRDynamicExample : NSObject <FRSingleton> {
  24 +}
  25 +@end
  26 +
  27 +@implementation FRDynamicExample
  28 +@end
67 tests/test_compare_speed.m
... ... @@ -0,0 +1,67 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <Foundation/Foundation.h>
  19 +
  20 +#import "FRSingleton.h"
  21 +#import "test_macros.h"
  22 +
  23 +@interface FRSingletonAuto : NSObject <FRSingleton>
  24 +@end
  25 +@implementation FRSingletonAuto
  26 ++ (id)sharedInstance { return obj_autorelease([self alloc]); }
  27 +@end
  28 +
  29 +// directly from apple's recommendation
  30 +@interface FRSingletonManual : NSObject
  31 +@end
  32 +@implementation FRSingletonManual
  33 +static FRSingletonManual *sharedInstance = nil;
  34 ++ (id)sharedInstance {
  35 + if (sharedInstance == nil) {
  36 + sharedInstance = [[super allocWithZone:NULL] init];
  37 + }
  38 + return sharedInstance;
  39 +}
  40 ++ (id)allocWithZone:(NSZone *)zone {
  41 + return obj_retain([self sharedInstance]);
  42 +}
  43 +@end
  44 +
  45 +
  46 +int main() {
  47 + AUTORELEASE_BEGIN;
  48 +
  49 + // allocation speed
  50 + // --------------------------------------------------------------------------------
  51 + void (^go)(Class class, unsigned int count) = nil;
  52 + go = ^void(Class class, unsigned int count) {
  53 + NSDate *date = [NSDate date];
  54 + for (NSUInteger i = 0; i < count; i++) {
  55 + obj_release([class alloc]);
  56 + }
  57 + fprintf(stderr, "Averaged %f miliseconds for %u calls to %s.\n",
  58 + (float)(-[date timeIntervalSinceNow] * 1000), count, [[class description] UTF8String]);
  59 + };
  60 +
  61 + unsigned int count = 100000;
  62 + go([FRSingletonAuto class], count);
  63 + go([FRSingletonManual class], count);
  64 +
  65 + AUTORELEASE_END;
  66 + return 0;
  67 +}
95 tests/test_general.m
... ... @@ -0,0 +1,95 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <Foundation/Foundation.h>
  19 +
  20 +#import "FRSingleton.h"
  21 +#import "test_macros.h"
  22 +
  23 +static bool deallocated;
  24 +
  25 +@interface FRSingleton : NSObject <FRSingleton>
  26 +@end
  27 +@implementation FRSingleton
  28 +- (void)dealloc {
  29 + deallocated = TRUE;
  30 + #if !__has_feature(objc_arc)
  31 + [super dealloc];
  32 + #endif
  33 +}
  34 +@end
  35 +
  36 +@interface FRSingletonBase1 : NSObject <FRSingleton>
  37 +@end
  38 +@implementation FRSingletonBase1
  39 +@end
  40 +
  41 +@interface FRSingletonSubclass1 : FRSingletonBase1
  42 +@end
  43 +@implementation FRSingletonSubclass1
  44 +@end
  45 +
  46 +@interface FRSingletonBase2 : NSObject <FRSingleton>
  47 +@end
  48 +@implementation FRSingletonBase2
  49 +@end
  50 +
  51 +@interface FRSingletonSubclass2 : FRSingletonBase2
  52 +@end
  53 +@implementation FRSingletonSubclass2
  54 +@end
  55 +
  56 +#import <objc/message.h>
  57 +
  58 +int main() {
  59 + BOOL gcEnabled = [NSClassFromString(@"NSGarbageCollector") defaultCollector] != nil;
  60 + AUTORELEASE_BEGIN;
  61 +
  62 + id base = nil;
  63 + id subclass = nil;
  64 +
  65 + // test 1
  66 + // --------------------------------------------------------------------------------
  67 + FRAssertEquals(obj_autorelease([FRSingleton alloc]), obj_autorelease([[FRSingleton alloc] init]), nil);
  68 +
  69 + // test 2
  70 + // --------------------------------------------------------------------------------
  71 + subclass = obj_autorelease([FRSingletonSubclass1 alloc]);
  72 + base = obj_autorelease([FRSingletonBase1 alloc]);
  73 + FRAssertEquals([subclass class], [FRSingletonSubclass1 class], nil);
  74 + FRAssertEquals([base class], [FRSingletonSubclass1 class], nil);
  75 + FRAssertEquals(subclass, base, nil);
  76 +
  77 + // test 3
  78 + // --------------------------------------------------------------------------------
  79 + base = obj_autorelease([FRSingletonBase2 alloc]);
  80 + NSLog(@"------------- expecting error output -------------");
  81 + subclass = obj_autorelease([FRSingletonSubclass2 alloc]);
  82 + NSLog(@"-------- no longer expecting error output --------");
  83 + FRAssertEquals([base class], [FRSingletonBase2 class], nil);
  84 + FRAssertNotNil(base, nil);
  85 + FRAssertNil(subclass, nil);
  86 +
  87 + FRAssertFalse(deallocated, nil);
  88 + id singleton = obj_autorelease([[FRSingleton alloc] init]);
  89 + obj_explicit_release(singleton);
  90 + FRAssertFalse(deallocated, nil);
  91 +
  92 + AUTORELEASE_END;
  93 + FRAssertTrue(deallocated || gcEnabled, nil); // must check outside of autorelease pool
  94 + return 0;
  95 +}
38 tests/test_loading.m
... ... @@ -0,0 +1,38 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <Foundation/Foundation.h>
  19 +#import <dlfcn.h>
  20 +
  21 +#import "FRSingleton.h"
  22 +#import "test_macros.h"
  23 +
  24 +int main(int argc, char **argv) {
  25 + AUTORELEASE_BEGIN;
  26 +
  27 + NSString *executablePath = [NSString stringWithUTF8String:argv[0]];
  28 + NSString *directory = [executablePath stringByDeletingLastPathComponent];
  29 + NSString *dylibPath = [directory stringByAppendingPathComponent:@"lib_loadable.dylib"];
  30 +
  31 + dlopen([dylibPath fileSystemRepresentation], RTLD_NOW);
  32 + Class dynamicClass = NSClassFromString(@"FRDynamicExample");
  33 + FRAssertNotNil(dynamicClass, nil);
  34 + FRAssertEquals(obj_autorelease([dynamicClass alloc]), obj_autorelease([dynamicClass alloc]), nil);
  35 +
  36 + AUTORELEASE_END;
  37 + return 0;
  38 +}
51 tests/test_macros.h
... ... @@ -0,0 +1,51 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#define FRAssertGeneral(condition, default_msg, fmt, ...) do {\
  19 + if (!(condition)) { \
  20 + NSString *__reason = fmt ? \
  21 + [NSString stringWithFormat:fmt ? fmt : @"", ##__VA_ARGS__] : default_msg; \
  22 + [[NSException exceptionWithName:@"TestFailure" reason:__reason userInfo:nil] raise]; \
  23 + } \
  24 +} while (0)
  25 +
  26 +#define FRAssertEquals(arg1, arg2, fmt, ...) \
  27 + FRAssertGeneral(arg1 == arg2, ([NSString stringWithFormat:@"%s == %s failed", #arg1, #arg2]), fmt, ##__VA_ARGS__)
  28 +
  29 +#define FRAssertNotEquals(arg1, arg2, fmt, ...) \
  30 + FRAssertGeneral(arg1 != arg2, ([NSString stringWithFormat:@"%s != %s failed", #arg1, #arg2]), fmt, ##__VA_ARGS__)
  31 +
  32 +#define FRAssertNil(arg, fmt, ...) FRAssertEquals(arg, nil, fmt, ##__VA_ARGS__)
  33 +#define FRAssertNotNil(arg, fmt, ...) FRAssertNotEquals(arg, nil, fmt, ##__VA_ARGS__)
  34 +#define FRAssertFalse(arg, fmt, ...) FRAssertEquals(arg, FALSE, fmt, ##__VA_ARGS__)
  35 +#define FRAssertTrue(arg, fmt, ...) FRAssertEquals(arg, TRUE, fmt, ##__VA_ARGS__)
  36 +
  37 +#if __has_feature(objc_arc)
  38 +#define AUTORELEASE_BEGIN @autoreleasepool {
  39 +#define AUTORELEASE_END }
  40 +#define obj_retain(x) x
  41 +#define obj_release(x) (void)x
  42 +#define obj_autorelease(x) x
  43 +#define obj_explicit_release(x) (void)(__bridge_transfer id)(__bridge void *)x
  44 +#else
  45 +#define AUTORELEASE_BEGIN NSAutoreleasePool *__pool = [[NSAutoreleasePool alloc] init];
  46 +#define AUTORELEASE_END [__pool release];
  47 +#define obj_retain(x) [x retain]
  48 +#define obj_release(x) [x release]
  49 +#define obj_autorelease(x) [x autorelease]
  50 +#define obj_explicit_release(x) [x release]
  51 +#endif
51 tests/test_speed.m
... ... @@ -0,0 +1,51 @@
  1 +//
  2 +// Copyright (c) 2011 FadingRed LLC
  3 +//
  4 +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5 +// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6 +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7 +// permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8 +//
  9 +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10 +// Software.
  11 +//
  12 +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13 +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
  14 +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15 +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16 +//
  17 +
  18 +#import <Foundation/Foundation.h>
  19 +
  20 +#import "FRSingleton.h"
  21 +#import "test_macros.h"
  22 +
  23 +int main() {
  24 + AUTORELEASE_BEGIN;
  25 +
  26 + // allocation speed
  27 + // --------------------------------------------------------------------------------
  28 + void (^go)(Class class, SEL through, unsigned int count) = nil;
  29 + go = ^void(Class class, SEL through, unsigned int count) {
  30 + NSDate *date = [NSDate date];
  31 + for (NSUInteger i = 0; i < count; i++) {
  32 + id object = [class alloc];
  33 + if (through == @selector(alloc)) { continue; }
  34 + object = [object init];
  35 + if (through == @selector(init)) { continue; }
  36 + obj_release(object);
  37 + object = nil;
  38 + }
  39 + fprintf(stderr, "Averaged %f miliseconds for %u allocations through %s %s.\n",
  40 + (float)(-[date timeIntervalSinceNow] * 1000), count, [NSStringFromSelector(through) UTF8String],
  41 + [NSObject respondsToSelector:@selector(hasSingletonProtocol)] ? "with singleton support" : "normally");
  42 + };
  43 +
  44 + unsigned int count = 100000;
  45 + go([NSMutableDictionary class], @selector(self), count);
  46 + go([NSMutableDictionary class], @selector(init), count);
  47 + go([NSMutableDictionary class], @selector(alloc), count);
  48 +
  49 + AUTORELEASE_END;
  50 + return 0;
  51 +}

0 comments on commit 47056c4

Please sign in to comment.
Something went wrong with that request. Please try again.