Created
August 24, 2013 06:56
-
-
Save firelizzard18/6326536 to your computer and use it in GitHub Desktop.
Objective-C Dictionary with Zeroing Weak References
This file contains 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
#import <Foundation/Foundation.h> | |
@protocol DeallocListener <NSObject> | |
- (void)objectDidDeallocateForKey:(id)key object:(id)obj; | |
@end | |
@interface WeakDictionary : NSMutableDictionary <DeallocListener> | |
@end | |
#import <objc/runtime.h> | |
#import <objc/message.h> | |
@interface __ObjectPair : NSObject | |
@property (readonly) id first, second; | |
@end | |
@implementation __ObjectPair | |
- (id)initWithObject:(id)first andObject:(id)second | |
{ | |
if (!(self = [super init])) | |
return nil; | |
_first = [first retain]; | |
_second = [second retain]; | |
return self; | |
} | |
- (void)dealloc | |
{ | |
[_first release]; | |
[_second release]; | |
[super dealloc]; | |
} | |
- (BOOL)isEqual:(id)object | |
{ | |
return [self.first isEqual:object]; | |
} | |
@end | |
void proxy_addDeallocListener_forKey(id self, SEL _cmd, id<DeallocListener, NSCopying> listener, id key) { | |
NSMutableArray * arr = objc_getAssociatedObject(self, class_getName([self class])); | |
if (!arr) | |
objc_setAssociatedObject(self, class_getName([self class]), arr = [[NSMutableArray alloc] init], OBJC_ASSOCIATION_ASSIGN); | |
[arr addObject:[[[__ObjectPair alloc] initWithObject:listener andObject:key] autorelease]]; | |
} | |
void proxy_removeDeallocListener(id self, SEL _cmd, id<DeallocListener> listener) { | |
NSMutableArray * arr = objc_getAssociatedObject(self, class_getName([self class])); | |
[arr removeObject:listener]; | |
} | |
void proxy_dealloc(id self, SEL _cmd) { | |
NSMutableArray * arr = objc_getAssociatedObject(self, class_getName([self class])); | |
for (__ObjectPair * pair in arr) | |
[pair.first objectDidDeallocateForKey:pair.second object:self]; | |
[arr release]; | |
// [super dealloc] | |
objc_msgSendSuper(&((struct objc_super){self, [self superclass]}), _cmd); | |
} | |
@protocol DeallocNotifier <NSObject> | |
- (void)addDeallocListener:(id<DeallocListener, NSCopying>)listener forKey:(id)key; | |
- (void)removeDeallocListener:(id<DeallocListener, NSCopying>)listener; | |
@end | |
@implementation WeakDictionary { | |
NSMutableDictionary * _backing; | |
} | |
- (void)dealloc | |
{ | |
[_backing release]; | |
[super dealloc]; | |
} | |
- (id)initWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt | |
{ | |
if (!(self = [super init])) | |
return nil; | |
_backing = [[NSMutableDictionary alloc] initWithObjects:objects forKeys:keys count:cnt]; | |
return self; | |
} | |
- (NSUInteger)count | |
{ | |
return _backing.count; | |
} | |
- (id)objectForKey:(id)aKey | |
{ | |
return [_backing objectForKey:aKey]; | |
} | |
- (NSEnumerator *)keyEnumerator | |
{ | |
return [_backing keyEnumerator]; | |
} | |
- (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey | |
{ | |
[self removeObjectForKey:aKey]; | |
if (!anObject) | |
return; | |
Class original = [anObject class]; | |
NSString * newName = [NSStringFromClass(original) stringByAppendingString:@"__WeakDictionary__DeallocNotifier"]; | |
Class newClass = NSClassFromString(newName); | |
if (!newClass) { | |
newClass = objc_allocateClassPair(original, [newName cStringUsingEncoding:NSASCIIStringEncoding], 0); | |
class_addMethod(newClass, @selector(addDeallocListener:forKey:), (IMP)&proxy_addDeallocListener_forKey, "v@:@@"); | |
class_addMethod(newClass, @selector(removeDeallocListener:), (IMP)&proxy_removeDeallocListener, "v@:@"); | |
class_addMethod(newClass, @selector(dealloc), (IMP)&proxy_dealloc, "v@:"); | |
} | |
object_setClass(anObject, newClass); | |
id<DeallocNotifier> newObject = anObject; | |
[newObject addDeallocListener:self forKey:aKey]; | |
[_backing setObject:[NSValue valueWithNonretainedObject:anObject] forKey:aKey]; | |
} | |
- (void)removeObjectForKey:(id)aKey | |
{ | |
NSValue * value = _backing[aKey]; | |
id<DeallocNotifier> object = value.nonretainedObjectValue; | |
if (!object) | |
return; | |
[_backing removeObjectForKey:aKey]; | |
[object removeDeallocListener:self]; | |
object_setClass(object, [object superclass]); | |
} | |
- (void)objectDidDeallocateForKey:(id)key object:(id)obj | |
{ | |
if ([_backing[key] nonretainedObjectValue] == obj) | |
[_backing removeObjectForKey:key]; | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment