-
Notifications
You must be signed in to change notification settings - Fork 0
/
NSDictionary+KVC.m
360 lines (328 loc) · 8.41 KB
/
NSDictionary+KVC.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
/* @(#)NSDictionary+KVC.m
* Released under the MIT license.
* See LICENSE.txt for details.
*/
#import <JRSwizzle.h>
/*
#import "NSDictionary+KVC.h"
/*/
#import <Foundation/Foundation.h>
@interface NSDictionary(KVC)
-(id)other_valueForKeyPath:(NSString*)path;
@end
/**/
#if OBJC_API_VERSION >= 2
# define FOREACH(ITEM,ITEMS) \
for (id key in ITEMS) { \
id ITEM=[self objectForKey:key];
# define FORBLOCK(block) \
block; \
}
#else
# define FOREACH(ITEM,ITEMS) \
NSEnumerator objects = [ITEMS objectEnumerator]; \
id ITEM; \
while ((ITEM = [objects nextObject]))
# define FORBLOCK(block) { \
block; \
}
#endif
@implementation NSDictionary (KVC)
#if defined(__GNUC__)
__attribute__((constructor))
static void init_NSDictionary_KVC() {
[NSDictionary jr_swizzleMethod:@selector(valueForKeyPath:) withMethod:@selector(other_valueForKeyPath:) error:NULL];
}
#else // !defined(__GNUC__)
/*
# if __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 1040
# warning "+[NSDictionary(KVC) load] may not be called before NSObject(JRSwizzle) is loaded."
# else
*/
# warning "+[NSDictionary(KVC) load] may not be called before NSObject(JRSwizzle) is loaded."
+(void)load {
[self jr_swizzleMethod:@selector(valueForKeyPath:) withMethod:@selector(other_valueForKeyPath:) error:NULL];
}
//# endif
#endif // !defined(__GNUC__)
-(id)other_valueForKeyPath:(NSString*)path {
NSRange division = [path rangeOfString:@"."];
if ([path hasPrefix:@"@"]) {
NSRange start = {1, division.location-1};
NSString *rest, *kvc_op_str;
SEL kvc_op;
if (division.location == NSNotFound) {
start.length=[path length]-1;
// first, try for a nullary kvc operator
kvc_op_str = [[NSString alloc] initWithFormat:@"_kvc_operator_%@",[path substringFromIndex:1]];
kvc_op = NSSelectorFromString(kvc_op_str);
[kvc_op_str release];
if ([self respondsToSelector:kvc_op]) {
return [self performSelector:kvc_op];
}
// nope, so go on to a unary kvc operator
rest=nil;
} else {
rest=[path substringFromIndex:division.location+1];
}
kvc_op_str = [[NSString alloc] initWithFormat:@"_kvc_operator_%@:",[path substringWithRange:start]];
kvc_op = NSSelectorFromString(kvc_op_str);
[kvc_op_str release];
if ([self respondsToSelector:kvc_op]) {
return [self performSelector:kvc_op withObject:rest];
}
/* if this object doesn't support the operator, rely on default behavior of valueForKeyPath:
(which is to strip the '@' and call super's `valueForKey`).
*/
}
// call original valueForKeyPath:
return [self other_valueForKeyPath:path];
}
-(id)anyObject {
#if OBJC_API_VERSION >= 2
// the docs say "It is more efficient to use the fast enumeration protocol".
// Is that true in this case?
for (id key in self) {
return [self objectForKey:key];
}
#else
NSEnumerator *pobj = [self objectEnumerator];
return [pobj nextObject];
#endif
}
-(id)_someUnionOfObjects:(id)items {
FOREACH(item,self)
FORBLOCK([items addObject:item]);
/*
#if OBJC_API_VERSION >= 2
for (id key in self) {
[items addObject:[self objectForKey:key]];
}
#else
FOREACH(item,self) {
[items addObject:item];
}
#endif
*/
return items;
}
-(id)_someUnionOfArrays:(id)items {
#if OBJC_API_VERSION >= 2
for (id key in self) {
[items addObjectsFromArray:[self objectForKey:key]];
}
#else
FOREACH(item,self) {
[items addObjectsFromArray:item];
}
#endif
return items;
}
-(id)_someUnionOfObjects:(id)items withPath:(NSString*)path {
if (path == nil || [path length]==0) {
return [self _someUnionOfObjects:items];
} else {
#if OBJC_API_VERSION >= 2
for (id key in self) {
[items addObject:[[self objectForKey:key] valueForKeyPath:path]];
}
#else
FOREACH(item,items) {
[items addObject:[item valueForKeyPath:path]];
}
#endif
return items;
}
}
-(id)_someUnionOfArrays:(id)items withPath:(NSString*)path {
if (path == nil || [path length]==0) {
return [self _someUnionOfArrays:items];
} else {
#if OBJC_API_VERSION >= 2
for (id key in self) {
// could be an NSMutableSet, but both support -addObjectsFromArrays:
[(NSMutableArray*)items addObjectsFromArray:[[self objectForKey:key] valueForKeyPath:path]];
}
#else
FOREACH(item,self) {
// could be an NSMutableSet, but both support -addObjectsFromArrays:
[(NSMutableArray*)items addObjectsFromArray:[item valueForKeyPath:path]]
}
#endif
return items;
}
}
-(id)_kvc_operator_distinctUnionOfObjects {
return [[self _someUnionOfObjects:[NSMutableSet setWithCapacity:[self count]]]
allObjects];
}
-(id)_kvc_operator_distinctUnionOfObjects:(NSString*)path {
return [[self _someUnionOfObjects:[NSMutableSet setWithCapacity:[self count]] withPath:path]
allObjects];
}
-(id)_kvc_operator_unionOfObjects {
return [self allValues];
}
-(id)_kvc_operator_unionOfObjects:(NSString*)path {
return [self _someUnionOfObjects:[NSMutableArray arrayWithCapacity:[self count]] withPath:path];
}
-(id)_kvc_operator_distinctUnionOfArrays {
return [[self _someUnionOfArrays:[NSMutableSet setWithCapacity:[self count]]]
allObjects];
}
-(id)_kvc_operator_distinctUnionOfArrays:(NSString*)path {
return [[self _someUnionOfArrays:[NSMutableSet setWithCapacity:[self count]] withPath:path]
allObjects];
}
-(id)_kvc_operator_unionOfArrays {
return [self _someUnionOfArrays:[NSMutableArray arrayWithCapacity:[self count]]];
}
-(id)_kvc_operator_unionOfArrays:(NSString*)path {
return [self _someUnionOfArrays:[NSMutableArray arrayWithCapacity:[self count]] withPath:path];
}
-(id)_kvc_operator_avg {
long double avg=0;
long double count = [self count];
#if OBJC_API_VERSION >= 2
for (id key in self) {
avg += [[self objectForKey:key] doubleValue] / count;
}
#else
FOREACH(item,self) {
avg += [item doubleValue] / count;
}
#endif
return [NSNumber numberWithDouble:avg];
}
-(id)_kvc_operator_avg:(NSString*)path {
if (path == nil || [path length]==0) {
return [self _kvc_operator_avg];
} else {
long double count = [self count];
long double avg=0;
#if OBJC_API_VERSION >= 2
for (id key in self) {
avg += [[[self objectForKey:key] valueForKeyPath:path] doubleValue] / count;
}
#else
FOREACH(item,self) {
avg += [[item valueForKeyPath:path] doubleValue] / count;
}
#endif
return [NSNumber numberWithDouble:avg];
}
}
-(id)_kvc_operator_count:(NSString*)path {
return [NSNumber numberWithUnsignedInt: [self count]];
}
-(id)_kvc_operator_min {
id smallest=[self anyObject],
item;
#if OBJC_API_VERSION >= 2
for (id key in self)
#else
FOREACH(item,self)
#endif
{
#if OBJC_API_VERSION >= 2
item = [self objectForKey:key];
#endif
if ([smallest compare:item] > 0) {
smallest = item;
}
}
return smallest;
}
-(id)_kvc_operator_min:(NSString*)path {
if (path == nil || [path length]==0) {
return [self _kvc_operator_min];
} else {
id smallest=[[self anyObject] valueForKeyPath:path],
item;
#if OBJC_API_VERSION >= 2
for (id key in self)
#else
FOREACH(item,self)
#endif
{
#if OBJC_API_VERSION >= 2
item=[[self objectForKey:key] valueForKeyPath:path];
#endif
if ([smallest compare:item] > 0) {
smallest = item;
}
}
return smallest;
}
}
-(id)_kvc_operator_max {
id biggest=[self anyObject],
item;
#if OBJC_API_VERSION >= 2
for (id key in self)
#else
FOREACH(item,self)
#endif
{
#if OBJC_API_VERSION >= 2
item = [self objectForKey:key];
#endif
if ([biggest compare:item] < 0) {
biggest = item;
}
}
return biggest;
}
-(id)_kvc_operator_max:(NSString*)path {
if (path == nil || [path length]==0) {
return [self _kvc_operator_max];
} else {
id biggest=[[self anyObject] valueForKeyPath:path],
item;
#if OBJC_API_VERSION >= 2
for (id key in self)
#else
FOREACH(item,self)
#endif
{
#if OBJC_API_VERSION >= 2
item=[[self objectForKey:key] valueForKeyPath:path];
#endif
if ([biggest compare:item] < 0) {
biggest = item;
}
}
return biggest;
}
}
-(id)_kvc_operator_sum {
double sum=0;
#if OBJC_API_VERSION >= 2
for (id key in self) {
sum += [[self objectForKey:key] doubleValue];
}
#else
FOREACH(item,self) {
sum += [item doubleValue];
}
#endif
return [NSNumber numberWithDouble:sum];
}
-(id)_kvc_operator_sum:(NSString*)path {
if (path == nil || [path length]==0) {
return [self _kvc_operator_sum];
} else {
double sum=0;
#if OBJC_API_VERSION >= 2
for (id key in self) {
sum += [[[self objectForKey:key] valueForKeyPath:path] doubleValue];
}
#else
FOREACH(item,self) {
sum += [[item valueForKeyPath:path] doubleValue];
}
#endif
return [NSNumber numberWithDouble:sum];
}
}
@end