Forks

Revisions

gist: 153676 Download_button fork
public
Description:
KVO+Blocks
Public Clone URL: git://gist.github.com/153676.git
Embed All Files: show embed
NSObject+BlockObservation.h #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//
// NSObject+BlockObservation.h
// Version 1.0
//
// Andy Matuschak
// andy@andymatuschak.org
// Public domain because I love you. Let me know how you use it.
//
 
#import <Cocoa/Cocoa.h>
 
typedef NSString AMBlockToken;
typedef void (^AMBlockTask)(id obj, NSDictionary *change);
 
@interface NSObject (AMBlockObservation)
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath task:(AMBlockTask)task;
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath onQueue:(NSOperationQueue *)queue task:(AMBlockTask)task;
- (void)removeObserverWithBlockToken:(AMBlockToken *)token;
@end
 
NSObject+BlockObservation.m #
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//
// NSObject+BlockObservation.h
// Version 1.0
//
// Andy Matuschak
// andy@andymatuschak.org
// Public domain because I love you. Let me know how you use it.
//
 
#import "NSObject+BlockObservation.h"
#import <dispatch/dispatch.h>
#import <objc/runtime.h>
 
@interface AMObserverTrampoline : NSObject
{
    __weak id observee;
    NSString *keyPath;
    AMBlockTask task;
    NSOperationQueue *queue;
}
 
- (AMObserverTrampoline *)initObservingObject:(id)obj keyPath:(NSString *)keyPath onQueue:(NSOperationQueue *)queue task:(AMBlockTask)task;
- (void)cancelObservation;
@end
 
@implementation AMObserverTrampoline
 
static NSString *AMObserverTrampolineContext = @"AMObserverTrampolineContext";
 
- (AMObserverTrampoline *)initObservingObject:(id)obj keyPath:(NSString *)newKeyPath onQueue:(NSOperationQueue *)newQueue task:(AMBlockTask)newTask
{
    if (!(self = [super init])) return nil;
    task = [newTask copy];
    keyPath = [newKeyPath copy];
    queue = [newQueue retain];
    observee = obj;
    [observee addObserver:self forKeyPath:keyPath options:0 context:AMObserverTrampolineContext];
    return self;
}
 
- (void)observeValueForKeyPath:(NSString *)aKeyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if (context == AMObserverTrampolineContext)
    {
        if (queue)
            [queue addOperationWithBlock:^{ task(object, change); }];
        else
            task(object, change);
    }
    else
    {
        [super observeValueForKeyPath:aKeyPath ofObject:object change:change context:context];
    }
}
 
- (void)cancelObservation
{
    [observee removeObserver:self forKeyPath:keyPath];
    observee = nil;
}
 
- (void)dealloc
{
    if (observee)
        [self cancelObservation];
    [task release];
    [keyPath release];
    [queue release];
    [super dealloc];
}
 
@end
 
static NSString *AMObserverMapKey = @"org.andymatuschak.observerMap";
 
@implementation NSObject (AMBlockObservation)
 
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath task:(AMBlockTask)task
{
    return [self addObserverForKeyPath:keyPath onQueue:nil task:task];
}
 
- (AMBlockToken *)addObserverForKeyPath:(NSString *)keyPath onQueue:(NSOperationQueue *)queue task:(AMBlockTask)task
{
    AMBlockToken *token = [[NSProcessInfo processInfo] globallyUniqueString];
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        if (!objc_getAssociatedObject(self, AMObserverMapKey))
            objc_setAssociatedObject(self, AMObserverMapKey, [NSMutableDictionary dictionary], OBJC_ASSOCIATION_RETAIN);
        AMObserverTrampoline *trampoline = [[[AMObserverTrampoline alloc] initObservingObject:self keyPath:keyPath onQueue:queue task:task] autorelease];
        [objc_getAssociatedObject(self, AMObserverMapKey) setObject:trampoline forKey:token];
    });
    return token;
}
 
- (void)removeObserverWithBlockToken:(AMBlockToken *)token
{
    NSMutableDictionary *observationDictionary = objc_getAssociatedObject(self, AMObserverMapKey);
    AMObserverTrampoline *trampoline = [observationDictionary objectForKey:token];
    if (!trampoline)
    {
        NSLog(@"Tried to remove non-existent observer on %@ for token %@", self, token);
        return;
    }
    [trampoline cancelObservation];
    [observationDictionary removeObjectForKey:token];
}
 
@end