Skip to content

Instantly share code, notes, and snippets.

@trenskow
Created September 11, 2017 11:30
Show Gist options
  • Save trenskow/b9397f3ec8c4f87bab84024891398803 to your computer and use it in GitHub Desktop.
Save trenskow/b9397f3ec8c4f87bab84024891398803 to your computer and use it in GitHub Desktop.
//
// NSObject+NSObject_AssociatedObjects.h
// Created by Kristian Trenskow.
//
#import <Foundation/Foundation.h>
/**
Associated objects additions to `NSObject`.
*/
@interface NSObject (AssociatedObjects)
/**
Returns an `NSMutableDictionary` instance with associated objects.
*/
@property (nonatomic,readonly,nonnull) NSMutableDictionary<NSString *, id> *associatedObjects;
@end
//
// NSObject+AssociatedObjects.m
// Created by Kristian Trenskow.
//
#import <objc/runtime.h>
#import "NSObject+AssociatedObjects.h"
unsigned int NSObjectAssociatedObjectsKey = 0;
@implementation NSObject (AssociatedObjects)
- (NSMutableDictionary<NSString *,id> *)associatedObjects {
// If no associated object has been set - we create one, set it and return it.
NSMutableDictionary<NSString *, id> *associatedObjects = objc_getAssociatedObject(self, &NSObjectAssociatedObjectsKey);
if (!associatedObjects) {
associatedObjects = [NSMutableDictionary new];
objc_setAssociatedObject(self, &NSObjectAssociatedObjectsKey, associatedObjects, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
return associatedObjects;
}
@end
//
// NSObject+Properties.h
// Created by Kristian Trenskow.
//
#import <Foundation/Foundation.h>
/**
A class representing runtime information a property.
*/
@interface NSPropertyInfo : NSObject
/**
Returns the properties name.
*/
@property (nonatomic,readonly,nonnull) NSString *name;
/**
Returns `YES` if property is read-only.
*/
@property (nonatomic,readonly,getter=isReadOnly) BOOL readOnly;
/**
Returns `YES` if property's value is copied on set.
*/
@property (nonatomic,readonly,getter=isCopy) BOOL copy;
/**
Returns `YES` if property's value is holds a strong reference.
*/
@property (nonatomic,readonly,getter=isStrong) BOOL strong;
/**
Returns `YES` if property is atomic.
*/
@property (nonatomic,readonly,getter=isAtomic) BOOL atomic;
/**
Returns the selector for the getter.
*/
@property (nonatomic,readonly,nonnull) SEL getter;
/**
Returns the selector for the setter.
*/
@property (nonatomic,readonly,nonnull) SEL setter;
/**
Returns `YES` if the property's implementation is dynamic - added at runtime.
*/
@property (nonatomic,readonly,getter=isDynamic) BOOL dynamic;
/**
Returns `YES` if the property holds a weak reference.
*/
@property (nonatomic,readonly,getter=isWeak) BOOL weak;
/**
Return `YES` if the property can be garbage collected (Mac OS X only).
*/
@property (nonatomic,readonly,getter=isGarbageCollectable) BOOL garbageCollectable;
/**
Returns the type encoding for the property.
*/
@property (nonatomic,readonly,nonnull) NSString *typeEncoding;
/**
When `typeEncoding` is `@` it returns the `Class` of the property - otherwise nil.
@note This is only non-nil if the information is available at runtime.
*/
@property (nonatomic,readonly,nullable) Class type;
@end
/**
Properties additions to `NSObject`.
*/
@interface NSObject (Properties)
/**
Returns an `NSDictionary` instance where the key is the property name and the value is an `NSPropertyInfo` object with property information.
*/
@property (nonatomic,readonly,nonnull) NSDictionary<NSString *, NSPropertyInfo *> *properties;
@end
//
// NSObject+Properties.m
// Created by Kristian Trenskow.
//
#import <objc/runtime.h>
#import "NSObject+AssociatedObjects.h"
#import "NSObject+Properties.h"
@interface NSPropertyInfo ()
@property (nonatomic,readwrite,nonnull) NSString *name;
@property (nonatomic,readwrite,getter=isReadOnly) BOOL readOnly;
@property (nonatomic,readwrite,getter=isCopy) BOOL copy;
@property (nonatomic,readwrite,getter=isStrong) BOOL strong;
@property (nonatomic,readwrite,getter=isAtomic) BOOL atomic;
@property (nonatomic,readwrite,nonnull) SEL getter;
@property (nonatomic,readwrite,nonnull) SEL setter;
@property (nonatomic,readwrite,getter=isDynamic) BOOL dynamic;
@property (nonatomic,readwrite,getter=isWeak) BOOL weak;
@property (nonatomic,readwrite,getter=isGarbageCollectable) BOOL garbageCollectable;
@property (nonatomic,readwrite,nonnull) NSString *typeEncoding;
@property (nonatomic,readwrite,nullable) Class type;
@end
NSString * const NSPropertiesKey = @"NSPropertiesKey";
@implementation NSPropertyInfo
- (instancetype)initWithProperty:(objc_property_t)property {
if ((self = [super init])) {
_name = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
// ARC default property attributes.
_strong = YES;
_atomic = YES;
_getter = NSSelectorFromString(_name);
_setter = NSSelectorFromString([NSString stringWithFormat:@"set%@%@",
[[_name substringWithRange:NSMakeRange(0, 1)] uppercaseString],
[_name substringFromIndex:1]]);
NSArray<NSString *> *attributes = [[NSString stringWithCString:property_getAttributes(property)
encoding:NSUTF8StringEncoding]
componentsSeparatedByString:@","];
for (NSString *attribute in attributes) {
switch ([attribute characterAtIndex:0]) {
case 'R':
_readOnly = YES;
break;
case 'C':
_copy = YES;
break;
case '&':
_strong = YES;
break;
case 'N':
_atomic = NO;
break;
case 'G':
_getter = NSSelectorFromString([attribute substringFromIndex:1]);
break;
case 'S':
_setter = NSSelectorFromString([attribute substringFromIndex:1]);
break;
case 'D':
_dynamic = YES;
break;
case 'W':
_weak = YES;
break;
case 'P':
_garbageCollectable = YES;
break;
case 'T':
_typeEncoding = [attribute substringFromIndex:1];
if ([_typeEncoding characterAtIndex:0] == '@' && [_typeEncoding length] > 1) {
_type = NSClassFromString([_typeEncoding substringWithRange:NSMakeRange(2, [_typeEncoding length] - 3)]);
_typeEncoding = @"@";
}
break;
}
}
}
return self;
}
@end
@implementation NSObject (Properties)
- (NSDictionary<NSString *,NSObjectPropertyInfo *> *)properties {
NSDictionary<NSString *, NSObjectPropertyInfo *> *ret = self.associatedObjects[NSObjectPropertiesKey];
if (!ret) {
NSMutableDictionary<NSString *, NSObjectPropertyInfo *> *infos = [NSMutableDictionary new];
Class currentClass = [self class];
while (currentClass != [NSObject class]) {
unsigned int count;
objc_property_t *properties = class_copyPropertyList(currentClass, &count);
for (unsigned int idx = 0 ; idx < count ; idx++) {
NSObjectPropertyInfo *propertyInfo = [[NSObjectPropertyInfo alloc] initWithProperty:properties[idx]];
infos[propertyInfo.name] = propertyInfo;
}
currentClass = [currentClass superclass];
free(properties);
}
ret = self.associatedObjects[NSObjectPropertiesKey] = [infos copy];
}
return ret;
}
@end
@trenskow
Copy link
Author

I know it's really a "no no!" to name things in the NS namespace - but this is just an example - so just rename if you don't like it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment