-
Notifications
You must be signed in to change notification settings - Fork 3
/
PARObjectObserver.m
152 lines (124 loc) · 4.49 KB
/
PARObjectObserver.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
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// PARViewController
// Author: Charles Parnot
// Licensed under the terms of the BSD License, as specified in the file 'LICENSE-BSD.txt' included with this distribution
#import "PARObjectObserver.h"
// thread-assertion macros can be defined outside of this file
#ifndef MainThreadOrReturn
#define MainThreadOrReturn() do {} while(0)
#endif
#ifndef SecondaryThreadOrReturn
#define SecondaryThreadOrReturn() do {} while(0)
#endif
@interface PARObjectObserver ()
@property (assign) id delegate;
@property (assign) id observedObject;
@property (retain) NSArray *observedKeys;
@end
@implementation PARObjectObserver
@synthesize delegate;
@synthesize observedObject;
@synthesize observedKeys;
@synthesize callbackMainThreadOnly;
- (id)initWithDelegate:(id)notifiedObject selector:(SEL)callback observedKeys:(NSArray *)keys observedObject:(id)object
{
self = [super init];
if (self != nil)
{
observedObject = object;
delegate = notifiedObject;
callbackSelector = callback;
observedKeys = [keys copy]; // using 'copy' to work with GC/ARC/non-ARC
//-invalidate handles removing this observing, and it's called in -dealloc, this should be safe.
for (NSString *key in observedKeys)
[observedObject addObserver:self forKeyPath:key options:0 context:NULL];
self.callbackMainThreadOnly = YES;
}
return self;
}
+ (PARObjectObserver *)observerWithDelegate:(id)notifiedObject selector:(SEL)callback observedKeys:(NSArray *)keys observedObject:(id)object
{
PARObjectObserver *newObserver = [[PARObjectObserver alloc] initWithDelegate:notifiedObject selector:callback observedKeys:keys observedObject:object];
// `autorelease` is not valid in ARC
#if ! __has_feature(objc_arc)
[newObserver autorelease];
#endif
return newObserver;
}
- (void)notifyDelegateForKeyPath:(NSString *)key
{
if (self.callbackMainThreadOnly == YES && [NSThread currentThread] != [NSThread mainThread])
{
SecondaryThreadOrReturn();
[self performSelectorOnMainThread:@selector(notifyDelegateForKeyPath:) withObject:key waitUntilDone:NO];
return;
}
if (self.callbackMainThreadOnly)
MainThreadOrReturn();
// here we do not expect a return result actually, so we should not use `performSelector:` anyway, instead we should use NSInvocation
// see also issue about using ARC and `performSelector:` http://stackoverflow.com/questions/7017281/performselector-may-cause-a-leak-because-its-selector-is-unknown
if ([delegate respondsToSelector:callbackSelector])
{
NSInvocation *callbackInvocation = [NSInvocation invocationWithMethodSignature:[delegate methodSignatureForSelector:callbackSelector]];
callbackInvocation.target = delegate;
callbackInvocation.selector = callbackSelector;
NSUInteger argumentCount = [[delegate methodSignatureForSelector:callbackSelector] numberOfArguments];
#if __has_feature(objc_arc)
if (argumentCount > 2)
{
void *arg = (__bridge void *)key;
[callbackInvocation setArgument:&arg atIndex:2];
}
if (argumentCount > 3)
{
void *arg = (__bridge void *)observedObject;
[callbackInvocation setArgument:&arg atIndex:3];
}
if (argumentCount > 4)
{
void *arg = (__bridge void *)self;
[callbackInvocation setArgument:&arg atIndex:4];
}
#else
if (argumentCount > 2)
[callbackInvocation setArgument:&key atIndex:2];
if (argumentCount > 3)
[callbackInvocation setArgument:&observedObject atIndex:3];
if (argumentCount > 4)
[callbackInvocation setArgument:&self atIndex:4];
#endif
[callbackInvocation invoke];
}
}
- (void)observeValueForKeyPath:(NSString *)key ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
[self notifyDelegateForKeyPath:key];
}
// why we need this for GC: http://stackoverflow.com/questions/13927/in-cocoa-do-i-need-to-remove-an-object-from-receiving-kvo-notifications-when-deal
- (void)invalidate
{
for (NSString *key in observedKeys)
[observedObject removeObserver:self forKeyPath:key];
self.observedKeys = nil;
self.delegate = nil;
self.observedObject = nil;
callbackSelector = NULL;
}
// compiling with ARC
#if __has_feature(objc_arc)
- (void)dealloc
{
[self invalidate];
observedObject = nil;
delegate = nil;
}
// compiling without ARC
#else
- (void)dealloc
{
[self invalidate];
observedObject = nil;
delegate = nil;
[super dealloc];
}
#endif
@end