Last active
September 19, 2024 20:25
-
-
Save libsteve/2c24e11355d32054b67828e93c57aeb2 to your computer and use it in GitHub Desktop.
Compile-time constant classes in Objective-C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// ConstantClass.m | |
// | |
// An example for how to create compile-time constant Objective-C objects that | |
// can be assigned to any static const variable. | |
// | |
// Compile with -fno-objc-arc to allow overriding of retain/release methods. | |
// | |
#import <Foundation/Foundation.h> | |
#import <objc/objc.h> | |
#import <objc/runtime.h> | |
// Use pointer authentication if available. | |
#if defined(__has_include) && __has_include(<ptrauth.h>) | |
#import <ptrauth.h> | |
#endif | |
// Define the pointer authentication attribute as nothing if it doesn't exist. | |
#ifndef __ptrauth_objc_isa_pointer | |
#define __ptrauth_objc_isa_pointer | |
#endif | |
#pragma mark - Rectangle | |
@interface Rectangle : NSObject | |
@property (nonatomic, readonly) NSPoint origin; | |
@property (nonatomic, readonly) NSSize size; | |
- (instancetype)initWithOrigin:(NSPoint)origin size:(NSSize)size; | |
@end | |
#pragma mark - Constant Rectangle Structure | |
#if __OBJC2__ | |
/// The _ConstantRectangle class object, stored as a text section symbol. | |
/// https://stackoverflow.com/questions/24968690/objective-c-mangled-names-objc-class-vs-objc-class-name | |
extern void const OBJC_CLASS_$__ConstantRectangle; | |
#elif __OBJC__ | |
/// The _ConstantRectangle class object, stored as an absolute global symbol. | |
/// https://stackoverflow.com/questions/24968690/objective-c-mangled-names-objc-class-vs-objc-class-name | |
extern void const OBJC_CLASS_$__ConstantRectangle __asm(".objc_class_name__ConstantRectangle"); | |
#endif | |
/// The structure we'll use for instances of our _ConstantRectangle class. | |
struct _ConstantRectangle { | |
Class __ptrauth_objc_isa_pointer isa; | |
NSRect rect; | |
}; | |
#if __OBJC2__ | |
/// A convenience macro for creating our compile-time constant object. | |
#define ConstRect(x, y, width, height) \ | |
((__bridge Rectangle *)(void *)&(struct _ConstantRectangle){ \ | |
(__bridge Class)&OBJC_CLASS_$__ConstantRectangle, \ | |
{{ x, y }, { width, height }} \ | |
}) | |
#elif __OBJC__ | |
/// https://lists.llvm.org/pipermail/llvm-dev/2007-July/010004.html | |
#error "Static class references in const expressions are only reliable in the Objective-C 2.0 runtime." | |
#endif | |
#pragma mark - Main Program | |
/// A compile-time constant rectangle object. | |
static Rectangle *const rect = ConstRect(0, 0, 100, 100); | |
int main(int argc, char **argv) { | |
@autoreleasepool { | |
printf("%s", [[rect description] UTF8String]); | |
} | |
return 0; | |
} | |
#pragma mark - Rectangle Implementation | |
@implementation Rectangle | |
@synthesize origin = _origin; | |
@synthesize size = _size; | |
- (instancetype)initWithOrigin:(NSPoint)origin size:(NSSize)size { | |
if (self = [self init]) { | |
_origin = origin; | |
_size = size; | |
} | |
return self; | |
} | |
- (NSString *)description { | |
return [NSString stringWithFormat:@"[Rectangle origin:%@ size:%@]", | |
NSStringFromPoint(self.origin), NSStringFromSize(self.size)]; | |
} | |
- (BOOL)isEqual:(id)object { | |
if (self == object) { return YES; } | |
if ([object isKindOfClass:[Rectangle class]]) { | |
Rectangle *other = object; | |
return NSEqualPoints(self.origin, other.origin) | |
&& NSEqualSizes(self.size, other.size); | |
} | |
return NO; | |
} | |
@end | |
#pragma mark - Constant Rectangle Implementation | |
@interface _ConstantRectangle : Rectangle | |
@end | |
@implementation _ConstantRectangle | |
- (NSPoint)origin { | |
return ((struct _ConstantRectangle *)(__bridge void *)self)->rect.origin; | |
} | |
- (NSSize)size { | |
return ((struct _ConstantRectangle *)(__bridge void *)self)->rect.size; | |
} | |
#if defined(__has_feature) && __has_feature(objc_arc) | |
/// We can't deallocate constant objects. | |
static void Dealloc(id self, SEL _cmd) {} | |
/// No use retaining constant objects. | |
static void *Retain(void *self, SEL _cmd) { return self; } | |
/// No use releasing constant objects. | |
static void Release(void *self, SEL _cmd) {} | |
/// No use autoreleasing constant objects. | |
static void *Autorelease(void *self, SEL _cmd) { return self; } | |
/// No use keeping track of a constant object's retain count. | |
static NSUInteger RetainCount(void *self, SEL _cmd) { return 1; } | |
/// Override an existing method on the given class. | |
static BOOL AddMethodOverride(Class self, SEL selector, IMP function) { | |
Method method = class_getInstanceMethod(self, selector); | |
char const *types = method_getTypeEncoding(method); | |
return class_addMethod(self, selector, function, types); | |
} | |
/// If ARC is enabled, we can't simply override the existing retain/release | |
/// methods, so instead we manually swizzle in our overrides when the class | |
/// is loaded into the runtime. | |
+ (void)load | |
{ | |
AddMethodOverride(self, sel_getUid("dealloc"), (IMP)Dealloc); | |
AddMethodOverride(self, sel_getUid("retain"), (IMP)Retain); | |
AddMethodOverride(self, sel_getUid("release"), (IMP)Release); | |
AddMethodOverride(self, sel_getUid("autorelease"), (IMP)Autorelease); | |
AddMethodOverride(self, sel_getUid("retainCount"), (IMP)RetainCount); | |
} | |
#else /* if !defined(__has_feature) || !__has_feature(objc_arc) */ | |
#pragma clang diagnostic push | |
#pragma clang diagnostic ignored "-Wobjc-missing-super-calls" | |
/// We can't deallocate constant objects. | |
- (void)dealloc {} | |
#pragma clang diagnostic pop | |
/// No use retaining constant objects. | |
- (instancetype)retain { return self; } | |
/// No use releasing constant objects. | |
- (oneway void)release {} | |
/// No use autoreleasing constant objects. | |
- (instancetype)autorelease { return self; } | |
/// No use keeping track of a constant object's retain count. | |
- (NSUInteger)retainCount { return 1; } | |
#endif /* !defined(__has_feature) || !__has_feature(objc_arc) */ | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment