2 // DPCollectionEnumeration.m
3 // HigherOrderMessaging
5 // Created by Ofri Wolfus on 11/05/07.
6 // Copyright 2007 Ofri Wolfus. All rights reserved.
8 // Redistribution and use in source and binary forms, with or without modification,
9 // are permitted provided that the following conditions are met:
11 // 1. Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 // 2. Redistributions in binary form must reproduce the above copyright
14 // notice, this list of conditions and the following disclaimer in the
15 // documentation and/or other materials provided with the distribution.
16 // 3. Neither the name of Ofri Wolfus nor the names of his contributors
17 // may be used to endorse or promote products derived from this software
18 // without specific prior written permission.
20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 // IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
24 // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
25 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #import "DPCollectionEnumeration.h"
33 #import "DPObjCRuntime.h"
36 #include <libkern/OSAtomic.h>
39 @interface NSObject (DPIntrospectionAdditions)
42 * NOTE: Selectors of variable arguments methods must also be
43 * added to -[_DPUninitializedMessage forward::] or the arguments
44 * will get cut off in the message instance.
48 * The following two methods allow us to customize at runtime
49 * the types description used with a given message/selector.
50 * NSObject's implementation simply returns the result of
51 * class_getInstance(/Class)Method(), but NSString overrides
52 * them in order to support its format string messages.
53 * Currently NSString is the only class that has a custom
54 * implementation for these.
56 - (const char *)typeEncodingForSelector:(SEL)sel;
57 - (const char *)typeEncodingForMessage:(DPMessage *)msg;
62 @interface DPIdEnumerator : DPEnumerator {
65 - (id)initWithObjects:(id *)obj count:(unsigned)c freeWhenDone:(BOOL)flag;
68 @implementation DPIdEnumerator
72 unsigned count, index;
76 - (id)initWithObjects:(id *)obj count:(unsigned)c freeWhenDone:(BOOL)flag {
77 if ((self = [super init])) {
78 DPIDEnumContent *content = calloc(1, sizeof(DPIDEnumContent));
80 content->objects = obj;
83 content->freeWhenDone = flag;
90 if (((DPIDEnumContent *)_reserved)->freeWhenDone)
91 free(((DPIDEnumContent *)_reserved)->objects);
98 DPIDEnumContent *content = _reserved;
100 if (content->index < content->count) {
101 o = content->objects[content->index];
109 return ((DPIDEnumContent *)_reserved)->index < ((DPIDEnumContent *)_reserved)->count;
113 return ((DPIDEnumContent *)_reserved)->count;
117 ((DPIDEnumContent *)_reserved)->index = 0U;
123 @interface DPArrayEnumerator : DPEnumerator {
126 - (id)initWithArray:(NSArray *)arr;
129 @implementation DPArrayEnumerator
133 unsigned count, index;
134 IMP objectAtIndexImp;
135 } DPArrayEnumeratorContent;
137 - (id)initWithArray:(NSArray *)a {
138 if ((self = [super init])) {
139 DPArrayEnumeratorContent *content = calloc(1, sizeof(DPArrayEnumeratorContent));
141 content->arr = [a retain];
142 content->count = [a count];
144 content->objectAtIndexImp = [a methodForSelector:@selector(objectAtIndex:)];
151 [((DPArrayEnumeratorContent *)_reserved)->arr release];
158 DPArrayEnumeratorContent *content = _reserved;
160 if (content->index < content->count) {
161 o = content->objectAtIndexImp(content->arr, @selector(objectAtIndex:), content->index);
169 return ((DPArrayEnumeratorContent *)_reserved)->index < ((DPArrayEnumeratorContent *)_reserved)->count;
173 return ((DPArrayEnumeratorContent *)_reserved)->count;
177 ((DPArrayEnumeratorContent *)_reserved)->index = 0U;
182 @interface DPCollectionEnumerator : DPEnumerator {
187 @implementation DPCollectionEnumerator
190 id <DPEnumeration> collection;
191 DPEnumerationState state;
193 unsigned count, index;
194 } DPCollectionEnumeratorContent;
196 - (id)initWithCollection:(id <DPEnumeration>)c {
197 if ((self = [super init])) {
198 DPCollectionEnumeratorContent *content = calloc(1, sizeof(DPCollectionEnumeratorContent));
200 content->collection = [(id)c retain];
201 content->state.state = 0;
202 content->state.items = content->buff;
203 content->state.info = NULL;
204 content->state.release = NULL;
206 content->count = [content->collection enumerateWithState:&(content->state)
207 objects:content->buff
215 DPCollectionEnumeratorContent *content = _reserved;
216 if (content->state.release)
217 content->state.release(&(content->state));
219 [(id)(content->collection) release];
225 return ((DPCollectionEnumeratorContent *)_reserved)->count > 0;
229 DPCollectionEnumeratorContent *content = _reserved;
231 if ([(id)(content->collection) respondsToSelector:@selector(count)])
232 return [(id)(content->collection) count];
234 return content->count;
238 DPCollectionEnumeratorContent *content = _reserved;
240 if (content->state.release)
241 content->state.release(&(content->state));
242 content->state.state = 0;
243 content->state.items = content->buff;
244 content->state.info = NULL;
245 content->state.release = NULL;
247 content->count = [content->collection enumerateWithState:&(content->state)
248 objects:content->buff
253 DPCollectionEnumeratorContent *content = _reserved;
255 if (content->count == 0) {
256 if (content->state.release) {
257 content->state.release(&(content->state));
258 content->state.release = NULL;
264 if (content->index < content->count)
265 return content->state.items[content->index++];
268 content->count = [content->collection enumerateWithState:&(content->state)
269 objects:content->buff
271 return [self nextObject];
278 @implementation DPEnumerator
280 #if defined(__LP64__) || defined(__ppc64__) || defined(__i386__) || defined(__x86_64__)
281 #define AtomicCompareAndSwapBarrier OSAtomicCompareAndSwap64Barrier
283 #define AtomicCompareAndSwapBarrier OSAtomicCompareAndSwap32Barrier
286 // Nobody should ever need more than 100 enumerators at once!
287 // If you do, you're doing something horribly horribly wrong.
288 #define DP_MAX_ENUMERATORS 100
289 // This is a block of DP_MAX_ENUMERATORS DPEnumerator instances,
290 // allocated one after the other. Every instance is actually
291 // struct { Class isa; void *_reserved; }.
292 // Zeroed isa members are freed instances that can be reused.
293 static DPEnumerator *_DPEnumeratorsPool = NULL;
298 // Before we do anything, we must allocate a pool of instances.
299 _DPEnumeratorsPool = calloc(DP_MAX_ENUMERATORS, class_getInstanceSize(self));
302 // Override the default allocation method
303 // and return an instance from our pool.
304 + (id)allocWithZone:(NSZone *)zone {
306 DPEnumerator *enumerator = NULL;
308 for (i = 0; i < DP_MAX_ENUMERATORS; i++) {
309 enumerator = _DPEnumeratorsPool + i;
310 if (AtomicCompareAndSwapBarrier(0, (intptr_t)self,
311 (void *)enumerator /* we cast to void here to avoid the warning */))
317 // Make sure we got an enumerator and not ran out of space
318 NSAssert(__builtin_expect(i < DP_MAX_ENUMERATORS, 1), @"Dude, you're using waay too much enumerators at once!");
323 // We never really free enumerators. We just reuse "released" ones.
325 // Mark ourself as free
327 // Stop GCC from whining
332 + (id)enumeratorWithObjects:(id *)objects
333 count:(unsigned)count
334 freeWhenDone:(BOOL)flag
336 return [[[DPIdEnumerator alloc] initWithObjects:objects
338 freeWhenDone:flag] autorelease];
341 + (id)enumeratorForArray:(NSArray *)arr {
342 return [[[DPArrayEnumerator alloc] initWithArray:arr] autorelease];
345 + (id)enumeratorForCollection:(id <DPEnumeration>)collection {
346 return [[[DPCollectionEnumerator alloc] initWithCollection:collection] autorelease];
353 - (NSArray *)allObjects {
354 NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[self count]];
357 while ((obj = [self nextObject]))
374 - (id)forward:(SEL)sel :(marg_list)args {
376 unsigned frameSize = dp_maxArgSizeForSelector(sel);
378 while ((o = [self nextObject]))
379 objc_msgSendv(o, sel, frameSize, args);
384 BOOL DPIsEnumeratedArgument(void *ptr) {
385 return (DPEnumerator *)ptr >= _DPEnumeratorsPool &&
386 (DPEnumerator *)ptr <= _DPEnumeratorsPool + DP_MAX_ENUMERATORS;
391 #define _DP_ENUM_OBJ 'z'
393 DP_STATIC_INLINE char *DPCopyArgumentTypesMap(const char *typedesc,
395 Class enumeratorClass)
397 unsigned i, argumentsCount = dp_getNumberOfArguments(typedesc);
398 char *types = calloc(argumentsCount, sizeof(char));
400 for (i = 0U; i < argumentsCount; i++) {
404 // Get the argument's type
405 dp_getArgumentInfo(typedesc, i, &t, &offset);
408 // Check object values against our enumerator type
409 // and mark the matches
410 if (types[i] == _C_ID) {
411 // Get the object from our arguments frame
412 id obj = dp_margGetObject(argsFrame, offset, id);
413 // If the object is an instance AND it's class is enumeratorClass or a subclass of it,
414 // it's an enumerated argument.
415 if (object_isInstance(obj))
416 if (class_isSubclassOfClass(object_getClass(obj), enumeratorClass))
417 types[i] = _DP_ENUM_OBJ;
424 DP_STATIC_INLINE int *DPCopyOffsetsOfArguments(const char *typedesc) {
425 unsigned i, argumentsCount = dp_getNumberOfArguments(typedesc);
426 int *offsets = calloc(argumentsCount, sizeof(int));
429 for (i = 0U; i < argumentsCount; i++)
430 dp_getArgumentInfo(typedesc, i, &t, &(offsets[i]));
435 DP_STATIC_INLINE void DPUpdateEnumereratedArguments(marg_list origFrame,
443 for (i = 2U; i < count; i++) {
444 if (types[i] == _DP_ENUM_OBJ) {
445 // Get the enumerator from the original frame,
446 // send it a -nextObject message, and that's our new value.
447 id val = [dp_margGetObject(origFrame, offsets[i], id) nextObject];
449 // If our value != nil than we simply apply it.
450 // Otherwise, only if nil is a valid value (indicated by allowNil == YES)
451 // we apply it. This means that if allowNil == NO and an enumerated
452 // argument ends before time (i.e. it has less objects than the collection)
453 // the last value returned from it will be used repeatedly.
456 dp_margSetObject(newFrame, offsets[i], id, val);
461 DP_STATIC_INLINE BOOL DPHasEnumeratedArguments(const char *types,
465 for (i = 2U; i < count; i++)
466 if (types[i] == _DP_ENUM_OBJ)
472 typedef unsigned (*DPUIMP)(id, SEL, ...);
474 // A generic, collection independent, implementation for -collect:
475 void DPCollectObjects(id <DPEnumeration> collection,
476 id resultsCollection,
478 DPEnumerationAppendResultsCallBack callback)
480 // A buffer to which our collection copies objects
482 // Our enumeration state.
483 // TODO: Consolidate it with 10.5's enumeration state?
484 DPEnumerationState state = { 0, buff, NULL, NULL };
485 // Cache the selector, frame length and the frame to save some useless messaging
486 SEL sel = [message selector];
487 unsigned frameLength = [message sizeOfArguments];
488 marg_list origFrame = [message arguments];
489 // A copy of the frame. It's what we use if there are enumerated arguments (DPEnumerator)
490 // in the original frame. This is to avoid modifying the frame of our message.
491 marg_list frame = origFrame;
492 // IMP cacheing is good for large loops
493 SEL enumerateWithStateSel = @selector(enumerateWithState:objects:count:);
494 DPUIMP enumerateWithStateImp = (DPUIMP)[(NSObject *)collection methodForSelector:enumerateWithStateSel];
495 // Get the first 16 objects from our collection
496 // This is the same as [collection enumerateWithState:&state objects:buff count:16]
497 unsigned count = enumerateWithStateImp(collection, enumerateWithStateSel, &state, buff, 16);
498 // An array of chars representing the types of our method's arguments
500 // The offset of each argument in our marg_list
502 // Total number of arguments which is also the size of the two arrays above
503 unsigned argsCount = 0U;
505 // Give up now if our collection is empty
509 // Get the type encoding we expect from the first object we have.
510 // XXX What to do if the object can't respond to this method and returns NULL?
511 // Sooner ot later this will cause us to crash and/or throw an exception.
512 const char *typeEncoding = [state.items[0] typeEncodingForMessage:message];
513 argsCount = dp_getNumberOfArguments(typeEncoding);
516 // Get a map of our arguments' types
517 types = DPCopyArgumentTypesMap(typeEncoding, origFrame, [DPEnumerator class]);
519 if (DPHasEnumeratedArguments(types, argsCount)) {
520 unsigned int len = [message realFrameSize];
522 // Copy our arguments frame
523 frame = dp_marg_malloc(len);
524 memcpy(frame, origFrame, len);
526 // Get offsets as well
527 offsets = DPCopyOffsetsOfArguments(typeEncoding);
531 // Grab all objects from our collection, message them, and store the results
535 unsigned resultsCount = 0;
537 // Grab the results of all objects
538 for (i = 0U; i < count; i++) {
539 // Update any enumerated arguments if needed.
540 // If any enumerated arguments contain less objects than our collection has
541 // nil values will be used. This might be useful if the objects in the collection expect
542 // nil, but otherwise it'll probably cause in assertion failure in the object's
543 // implementation (which is not really our fault).
544 // For example, if arr1 contains 5 strings and arr2 contains 3 strings,
545 // [arr1 collect:MSG(stringByAppendingString:[arr2 each])] will throw an exception
546 // when trying to append nil to strings 4 and 5.
547 // NOTE: The last argument to DPUpdateEnumereratedArguments() indicates whether to use
548 // nil when an enumerated argument is empty or to use the last object repeatedly.
550 DPUpdateEnumereratedArguments(origFrame, frame, types, offsets, argsCount, YES);
552 // Send the message and store its result
553 results[resultsCount] = objc_msgSendv(state.items[i], sel, frameLength, frame);
556 // If our results buffer gets full, empty it and append the results
557 if (resultsCount == 16) {
558 // Append the results to the results collection
559 callback(resultsCollection, &state, results, resultsCount);
565 // Append the results to the results collection
567 callback(resultsCollection, &state, results, resultsCount);
569 // Get the next 16 objects from our collection
570 count = enumerateWithStateImp(collection, enumerateWithStateSel, &state, buff, 16);
574 if (argsCount > 2U) {
583 // Finally, invoke the release callback and return
584 if (state.release != NULL)
585 state.release(&state);
588 typedef BOOL (*DPBOOLMSGV)(id, SEL, unsigned arg_size, marg_list arg_frame);
590 // A generic, collection independent, implementation for -selectWhere:
592 void DPSelectObjects(id <DPEnumeration> collection,
593 id resultsCollection,
594 DPMessage **messages,
595 unsigned messageCount,
597 DPEnumerationAppendResultsCallBack callback)
599 // A buffer to which our collection copies objects
601 // Our enumeration state.
602 // TODO: Consolidate it with 10.5's enumeration state?
603 DPEnumerationState state = { 0, buff, NULL, NULL };
604 // Cache the selector, frame length and the frame to save some useless messaging
605 SEL sel[messageCount];
606 unsigned frameLength[messageCount];
607 marg_list origFrame[messageCount];
608 // A copy of the frame. It's what we use if there are enumerated arguments (DPEnumerator)
609 // in the original frame. This is to avoid modifying the frame of our message.
610 marg_list frame[messageCount];
611 // IMP cacheing is good for large loops
612 SEL enumerateWithStateSel = @selector(enumerateWithState:objects:count:);
613 DPUIMP enumerateWithStateImp = (DPUIMP)[(NSObject *)collection methodForSelector:enumerateWithStateSel];
614 // Get the first 16 objects from our collection
615 // This is the same as [collection enumerateWithState:&state objects:buff count:16]
616 unsigned count = enumerateWithStateImp(collection, enumerateWithStateSel, &state, buff, 16);
617 // An array of chars representing the types of our method's arguments
618 char *types[messageCount];
619 // The offset of each argument in our marg_list
620 int *offsets[messageCount];
621 // Total number of arguments which is also the size of the two arrays above
622 unsigned argsCount[messageCount];
624 // Just an ordinary index variable
627 // Give up now if our collection is empty
631 BOOL initialized = NO;
633 // Grab all objects from our collection, message them, and store the results
636 unsigned resultsCount = 0;
639 // Initialize everything if needed
640 if (__builtin_expect(!initialized, 0)) {
641 id obj = state.items[0];
643 // We don't have the privilege DPCollectObjects() has of working with a single
644 // message, so we must initialize everything in a loop. Fun.
645 // Everything here assumes the first object is a model to all others.
646 for (i = 0; i < messageCount; i++) {
647 // Cache all selectors, frame lengths and argument lists
648 sel[i] = [messages[i] selector];
649 frameLength[i] = [messages[i] sizeOfArguments];
650 origFrame[i] = [messages[i] arguments];
652 // Get the type encoding we expect from the first object we have.
653 // XXX: What to do if the object can't respond to this method and returns NULL?
654 // Sooner ot later this will cause us to crash and/or throw an exception.
655 const char *typeEncoding = [obj typeEncodingForMessage:messages[i]];
656 argsCount[i] = dp_getNumberOfArguments(typeEncoding);
658 // NULL offsets means no enumerated arguments
660 // Our frame is the original one
661 frame[i] = origFrame[i];
663 if (argsCount[i] > 2) {
664 // Get a map of our arguments' types
665 types[i] = DPCopyArgumentTypesMap(typeEncoding, origFrame[i], [DPEnumerator class]);
667 if (DPHasEnumeratedArguments(types[i], argsCount[i])) {
668 unsigned int len = [messages[i] realFrameSize];
670 // Copy our arguments frame
671 frame[i] = dp_marg_malloc(len);
672 memcpy(frame[i], origFrame[i], len);
674 // Get offsets as well
675 offsets[i] = DPCopyOffsetsOfArguments(typeEncoding);
679 // Update any enumerated arguments
680 if (offsets[i] != NULL)
681 DPUpdateEnumereratedArguments(origFrame[i], frame[i], types[i], offsets[i], argsCount[i], YES);
683 // The last message should return BOOL.
684 // If it's YES, we add the original object to our results array.
685 if (__builtin_expect(i == messageCount - 1, 0)) {
686 if (((DPBOOLMSGV)(objc_msgSendv))(obj, sel[i],
688 frame[i]) == returnValue)
690 results[resultsCount] = state.items[0];
694 // Send the message to our object and use the result
695 obj = objc_msgSendv(obj, sel[i], frameLength[i], frame[i]);
701 // Now start the loop from the second object
705 // Grab the results of all objects
706 for (/* i is initialized above */; i < count; i++) {
708 id obj = state.items[i];
710 for (j = 0; j < messageCount - 1; j++) {
711 // Update any enumerated arguments if needed.
712 // If any enumerated arguments contain less objects than our collection has
713 // nil values will be used. This might be useful if the objects in the collection expect
714 // nil, but otherwise it'll probably cause in assertion failure in the object's
715 // implementation (which is not really our fault).
716 // For example, if arr1 contains 5 strings and arr2 contains 3 strings,
717 // [arr1 collect:MSG(stringByAppendingString:[arr2 each])] will throw an exception
718 // when trying to append nil to strings 4 and 5.
719 // NOTE: The last argument to DPUpdateEnumereratedArguments() indicates whether to use
720 // nil when an enumerated argument is empty or to use the last object repeatedly.
721 if (offsets[j] != NULL)
722 DPUpdateEnumereratedArguments(origFrame[j], frame[j], types[j],
723 offsets[j], argsCount[j], YES);
725 // Send the message and store its result
726 obj = objc_msgSendv(obj, sel[j], frameLength[j], frame[j]);
729 if (offsets[j] != NULL)
730 DPUpdateEnumereratedArguments(origFrame[j], frame[j], types[j],
731 offsets[j], argsCount[j], YES);
733 // The last message should return BOOL.
734 // If it's YES, we add the original object to our results array.
735 if (((DPBOOLMSGV)(objc_msgSendv))(obj, sel[j],
737 frame[j]) == returnValue)
739 results[resultsCount] = state.items[i];
742 if (resultsCount == 16) {
743 // Append the results to the results collection
744 callback(resultsCollection, &state, results, resultsCount);
751 // Append the results to the results collection
753 callback(resultsCollection, &state, results, resultsCount);
755 // Get the next 16 objects from our collection
756 count = enumerateWithStateImp(collection, enumerateWithStateSel, &state, buff, 16);
760 for (i = 0; i < messageCount; i++) {
761 if (argsCount[i] > 2U) {
771 // Finally, invoke the release callback and return
772 if (state.release != NULL)
773 state.release(&state);
776 id DPFindObject(id <DPEnumeration> collection,
777 DPMessage **messages,
778 unsigned messageCount,
781 // A buffer to which our collection copies objects
783 // Our enumeration state.
784 // TODO: Consolidate it with 10.5's enumeration state?
785 DPEnumerationState state = { 0, buff, NULL, NULL };
786 // Cache the selector, frame length and the frame to save some useless messaging
787 SEL sel[messageCount];
788 unsigned frameLength[messageCount];
789 marg_list origFrame[messageCount];
790 // A copy of the frame. It's what we use if there are enumerated arguments (DPEnumerator)
791 // in the original frame. This is to avoid modifying the frame of our message.
792 marg_list frame[messageCount];
793 // IMP cacheing is good for large loops
794 SEL enumerateWithStateSel = @selector(enumerateWithState:objects:count:);
795 DPUIMP enumerateWithStateImp = (DPUIMP)[(NSObject *)collection methodForSelector:enumerateWithStateSel];
796 // Get the first 16 objects from our collection
797 // This is the same as [collection enumerateWithState:&state objects:buff count:16]
798 unsigned count = enumerateWithStateImp(collection, enumerateWithStateSel, &state, buff, 16);
799 // An array of chars representing the types of our method's arguments
800 char *types[messageCount];
801 // The offset of each argument in our marg_list
802 int *offsets[messageCount];
803 // Total number of arguments which is also the size of the two arrays above
804 unsigned argsCount[messageCount];
808 // Just an ordinary index variable
811 // Give up now if our collection is empty
815 BOOL initialized = NO;
817 // Grab all objects from our collection, message them, and store the results
821 // Initialize everything if needed
822 if (__builtin_expect(!initialized, 0)) {
823 id obj = state.items[0];
825 // We don't have the privilege DPCollectObjects() has of working with a single
826 // message, so we must initialize everything in a loop. Fun.
827 // Everything here assumes the first object is a model to all others.
828 for (i = 0; i < messageCount; i++) {
829 // Cache all selectors, frame lengths and argument lists
830 sel[i] = [messages[i] selector];
831 frameLength[i] = [messages[i] sizeOfArguments];
832 origFrame[i] = [messages[i] arguments];
834 // Get the type encoding we expect from the first object we have.
835 // XXX: What to do if the object can't respond to this method and returns NULL?
836 // Sooner ot later this will cause us to crash and/or throw an exception.
837 const char *typeEncoding = [obj typeEncodingForMessage:messages[i]];
838 argsCount[i] = dp_getNumberOfArguments(typeEncoding);
840 // NULL offsets means no enumerated arguments
842 // Our frame is the original one
843 frame[i] = origFrame[i];
845 if (argsCount[i] > 2) {
846 // Get a map of our arguments' types
847 types[i] = DPCopyArgumentTypesMap(typeEncoding, origFrame[i], [DPEnumerator class]);
849 if (DPHasEnumeratedArguments(types[i], argsCount[i])) {
850 unsigned int len = [messages[i] realFrameSize];
852 // Copy our arguments frame
853 frame[i] = dp_marg_malloc(len);
854 memcpy(frame[i], origFrame[i], len);
856 // Get offsets as well
857 offsets[i] = DPCopyOffsetsOfArguments(typeEncoding);
861 // Update any enumerated arguments
862 if (offsets[i] != NULL)
863 DPUpdateEnumereratedArguments(origFrame[i], frame[i], types[i], offsets[i], argsCount[i], YES);
865 // Send the message to our object and use the result
866 obj = objc_msgSendv(obj, sel[i], frameLength[i], frame[i]);
868 // The last message should return BOOL.
869 // The first to return 'returnValue' is our match.
870 if (i == messageCount - 1 && (BOOL)(long)obj == returnValue) {
871 result = state.items[0];
872 // Go do some final cleanups and return
879 // Now start the loop from the second object
883 // Grab the results of all objects
884 for (/* i is initialized above */; i < count; i++) {
886 id obj = state.items[i];
888 for (j = 0; j < messageCount - 1; j++) {
889 // Update any enumerated arguments if needed.
890 // If any enumerated arguments contain less objects than our collection has
891 // nil values will be used. This might be useful if the objects in the collection expect
892 // nil, but otherwise it'll probably cause in assertion failure in the object's
893 // implementation (which is not really our fault).
894 // For example, if arr1 contains 5 strings and arr2 contains 3 strings,
895 // [arr1 collect:MSG(stringByAppendingString:[arr2 each])] will throw an exception
896 // when trying to append nil to strings 4 and 5.
897 // NOTE: The last argument to DPUpdateEnumereratedArguments() indicates whether to use
898 // nil when an enumerated argument is empty or to use the last object repeatedly.
899 if (offsets[j] != NULL)
900 DPUpdateEnumereratedArguments(origFrame[j], frame[j], types[j],
901 offsets[j], argsCount[j], YES);
903 // Send the message and store its result
904 obj = objc_msgSendv(obj, sel[j], frameLength[j], frame[j]);
907 if (offsets[j] != NULL)
908 DPUpdateEnumereratedArguments(origFrame[j], frame[j], types[j],
909 offsets[j], argsCount[j], YES);
911 // The last message should return BOOL.
912 // The first object to return 'returnValue' is our match.
913 if (((DPBOOLMSGV)(objc_msgSendv))(obj, sel[j],
915 frame[j]) == returnValue)
917 result = state.items[i];
918 // Go do some final cleanups and return
923 // Get the next 16 objects from our collection
924 count = enumerateWithStateImp(collection, enumerateWithStateSel, &state, buff, 16);
929 for (i = 0; i < messageCount; i++) {
930 if (argsCount[i] > 2U) {
940 // Finally, invoke the release callback and return
941 if (state.release != NULL)
942 state.release(&state);
948 @implementation NSArray (DPCollectionEnumeration)
950 static void DPNSArrayAppendResults (id resultsCollection,
951 DPEnumerationState *state,
956 for (i = 0U; i < count; i++)
957 CFArrayAppendValue((CFMutableArrayRef)resultsCollection, objects[i]);
960 - (unsigned)enumerateWithState:(DPEnumerationState *)state
962 count:(unsigned)buffLen
964 CFIndex count = CFArrayGetCount((CFArrayRef)self);
966 if (state->state < count) {
967 CFIndex len = (state->state + buffLen < count) ? buffLen : (count - state->state);
969 CFArrayGetValues((CFArrayRef)self, CFRangeMake(state->state, len), (const void **)buff);
979 //return [DPEnumerator enumeratorForArray:self];
980 return [DPEnumerator enumeratorForCollection:self];
984 - (id)collect:(DPMessage *)argumentMessage {
985 NSMutableArray *arr = [NSMutableArray arrayWithCapacity:[self count]];
987 DPCollectObjects(self, arr, argumentMessage, DPNSArrayAppendResults);
991 - (id)pick:(BOOL)val where:(DPMessage *)firstMsg additionalMessages:(va_list)msgs {
993 unsigned i, count = 1;
994 NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count] / 2];
997 while (va_arg(va, id))
1001 DPMessage *messages[count];
1002 messages[0] = firstMsg;
1006 for (i = 1; i < count; i++) {
1007 messages[i] = va_arg(va, DPMessage *);
1012 DPSelectObjects(self, result, messages, count, val, DPNSArrayAppendResults);
1016 - (id)selectWhere:(DPMessage *)firstMessage, ... {
1020 NSAssert(firstMessage != nil, @"Passed nil message");
1022 va_start(va, firstMessage);
1023 r = [self pick:YES where:firstMessage additionalMessages:va];
1029 - (id)rejectWhere:(DPMessage *)firstMessage, ... {
1033 NSAssert(firstMessage != nil, @"Passed nil message");
1035 va_start(va, firstMessage);
1036 r = [self pick:NO where:firstMessage additionalMessages:va];
1042 - (id)find:(BOOL)val where:(DPMessage *)firstMsg additionalMessages:(va_list)msgs {
1044 unsigned i, count = 1;
1047 while (va_arg(va, id))
1051 DPMessage *messages[count];
1052 messages[0] = firstMsg;
1056 for (i = 1; i < count; i++) {
1057 messages[i] = va_arg(va, DPMessage *);
1062 return DPFindObject(self, messages, count, val);
1065 - (id)findObjectWhere:(DPMessage *)firstMessage, ... {
1069 NSAssert(firstMessage != nil, @"Passed nil message");
1071 va_start(va, firstMessage);
1072 r = [self find:YES where:firstMessage additionalMessages:va];
1081 @implementation NSSet (DPCollectionEnumeration)
1083 static void DPNSSetAppendResults (id resultsCollection,
1084 DPEnumerationState *state,
1089 for (i = 0U; i < count; i++)
1090 CFSetAddValue((CFMutableSetRef)resultsCollection, objects[i]);
1093 static void DPNSSetEnumerationCleanup(DPEnumerationState *state) {
1098 - (unsigned)enumerateWithState:(DPEnumerationState *)state
1100 count:(unsigned)buffLen
1102 CFIndex count = CFSetGetCount((CFSetRef)self);
1104 // Just copy all objects at once
1105 if (state->state == 0 && count > 0) {
1106 id *objects = calloc(count, sizeof(id));
1108 CFSetGetValues((CFSetRef)self, (const void **)objects);
1109 state->items = objects;
1110 state->state = count - 1;
1111 state->release = (void *)DPNSSetEnumerationCleanup;
1121 CFIndex count = CFSetGetCount((CFSetRef)self);
1122 id *objects = calloc(count, sizeof(id));
1124 CFSetGetValues((CFSetRef)self, (const void **)objects);
1125 return [DPEnumerator enumeratorWithObjects:objects
1131 - (id)collect:(DPMessage *)argumentMessage {
1132 NSMutableSet *arr = [NSMutableSet setWithCapacity:[self count]];
1134 DPCollectObjects(self, arr, argumentMessage, DPNSSetAppendResults);
1138 - (id)pick:(BOOL)val where:(DPMessage *)firstMsg additionalMessages:(va_list)msgs {
1140 unsigned i, count = 1;
1141 NSMutableSet *result = [NSMutableSet setWithCapacity:[self count] / 2];
1144 while (va_arg(va, id))
1148 DPMessage *messages[count];
1149 messages[0] = firstMsg;
1153 for (i = 1; i < count; i++) {
1154 messages[i] = va_arg(va, DPMessage *);
1159 DPSelectObjects(self, result, messages, count, val, DPNSSetAppendResults);
1163 - (id)selectWhere:(DPMessage *)firstMessage, ... {
1167 NSAssert(firstMessage != nil, @"Passed nil message");
1169 va_start(va, firstMessage);
1170 r = [self pick:YES where:firstMessage additionalMessages:va];
1176 - (id)rejectWhere:(DPMessage *)firstMessage, ... {
1180 NSAssert(firstMessage != nil, @"Passed nil message");
1182 va_start(va, firstMessage);
1183 r = [self pick:NO where:firstMessage additionalMessages:va];
1189 - (id)find:(BOOL)val where:(DPMessage *)firstMsg additionalMessages:(va_list)msgs {
1191 unsigned i, count = 1;
1194 while (va_arg(va, id))
1198 DPMessage *messages[count];
1199 messages[0] = firstMsg;
1203 for (i = 1; i < count; i++) {
1204 messages[i] = va_arg(va, DPMessage *);
1209 return DPFindObject(self, messages, count, val);
1212 - (id)findObjectWhere:(DPMessage *)firstMessage, ... {
1216 NSAssert(firstMessage != nil, @"Passed nil message");
1218 va_start(va, firstMessage);
1219 r = [self find:YES where:firstMessage additionalMessages:va];
1227 @implementation NSDictionary (DPCollectionEnumeration)
1229 static void DPNSDictionaryAppendResults (id resultsCollection,
1230 DPEnumerationState *state,
1235 id *obj = state->items;
1236 // We hold the keys in the info field
1237 id *key = state->info;
1239 for (i = 0U; i < count; i++) {
1242 // Find the index of the key matching the object we need to apply
1243 for (j = i; (*obj) != objects[i]; ++j)
1246 CFDictionaryAddValue((CFMutableDictionaryRef)resultsCollection, key[j], *obj);
1250 static void DPNSDictionaryEnumerationCleanup(DPEnumerationState *state) {
1258 - (unsigned)enumerateWithState:(DPEnumerationState *)state
1260 count:(unsigned)buffLen
1262 CFIndex count = CFDictionaryGetCount((CFDictionaryRef)self);
1264 // Just copy all objects at once
1265 if (state->state == 0 && count > 0) {
1266 id *values = calloc(count, sizeof(id));
1267 id *keys = calloc(count, sizeof(id));
1269 CFDictionaryGetKeysAndValues((CFDictionaryRef)self,
1270 (const void **)keys,
1271 (const void **)values);
1272 state->items = values;
1274 state->state = count - 1;
1275 state->release = (void *)DPNSDictionaryEnumerationCleanup;
1285 CFIndex count = CFSetGetCount((CFSetRef)self);
1286 id *objects = calloc(count, sizeof(id));
1288 CFDictionaryGetKeysAndValues((CFDictionaryRef)self,
1289 NULL, (const void **)objects);
1290 return [DPEnumerator enumeratorWithObjects:objects
1296 - (id)collect:(DPMessage *)argumentMessage {
1297 NSMutableDictionary *arr = [NSMutableDictionary dictionaryWithCapacity:[self count]];
1299 DPCollectObjects(self, arr, argumentMessage, DPNSDictionaryAppendResults);
1303 - (id)pick:(BOOL)val where:(DPMessage *)firstMsg additionalMessages:(va_list)msgs {
1305 unsigned i, count = 1;
1306 NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:[self count] / 2];
1309 while (va_arg(va, id))
1313 DPMessage *messages[count];
1314 messages[0] = firstMsg;
1318 for (i = 1; i < count; i++) {
1319 messages[i] = va_arg(va, DPMessage *);
1324 DPSelectObjects(self, result, messages, count, val, DPNSDictionaryAppendResults);
1328 - (id)selectWhere:(DPMessage *)firstMessage, ... {
1332 NSAssert(firstMessage != nil, @"Passed nil message");
1334 va_start(va, firstMessage);
1335 r = [self pick:YES where:firstMessage additionalMessages:va];
1341 - (id)rejectWhere:(DPMessage *)firstMessage, ... {
1345 NSAssert(firstMessage != nil, @"Passed nil message");
1347 va_start(va, firstMessage);
1348 r = [self pick:NO where:firstMessage additionalMessages:va];
1354 - (id)find:(BOOL)val where:(DPMessage *)firstMsg additionalMessages:(va_list)msgs {
1356 unsigned i, count = 1;
1359 while (va_arg(va, id))
1363 DPMessage *messages[count];
1364 messages[0] = firstMsg;
1368 for (i = 1; i < count; i++) {
1369 messages[i] = va_arg(va, DPMessage *);
1374 return DPFindObject(self, messages, count, val);
1377 - (id)findObjectWhere:(DPMessage *)firstMessage, ... {
1381 NSAssert(firstMessage != nil, @"Passed nil message");
1383 va_start(va, firstMessage);
1384 r = [self find:YES where:firstMessage additionalMessages:va];
1393 @interface NSObject (runtimeMethods)
1394 - (id)forward:(SEL)sel :(marg_list)args;
1398 @implementation NSObject (DPHOMGoodies)
1400 - (id)collect:(DPMessage *)msg {
1401 SEL sel = [msg selector];
1402 marg_list origFrame = [msg arguments], frame = origFrame;
1403 unsigned int frameSize = [msg sizeOfArguments];
1404 const char *typedesc = [self typeEncodingForMessage:msg];
1409 unsigned argsCount = dp_getNumberOfArguments(typedesc);
1410 char *types = DPCopyArgumentTypesMap(typedesc, frame, [DPEnumerator class]);
1411 int *offsets = DPCopyOffsetsOfArguments(typedesc);
1412 unsigned numberOfIterations = 0, i;
1415 for (i = 2; i < argsCount; i++) {
1416 if (types[i] == _DP_ENUM_OBJ) {
1417 unsigned c = [dp_margGetObject(frame, offsets[i], DPEnumerator *) count];
1418 numberOfIterations = MAX(numberOfIterations, c);
1422 if (numberOfIterations == 0) {
1423 result = dp_msgSendv(self, sel, frame);
1427 dp_getReturnType(typedesc, &rt, 1);
1430 size_t fsize = [msg realFrameSize];
1431 frame = dp_marg_malloc(fsize);
1432 memcpy(frame, origFrame, fsize);
1434 // Allocate an array for the results
1435 if (rt == _C_ID || rt == _C_CLASS)
1436 result = [NSMutableArray arrayWithCapacity:numberOfIterations];
1439 for (i = 0; i < numberOfIterations; ++i) {
1440 DPUpdateEnumereratedArguments(origFrame, frame, types, offsets, argsCount, YES);
1442 if (rt == _C_ID || rt == _C_CLASS)
1443 [result addObject:objc_msgSendv(self, sel, frameSize, frame)];
1445 objc_msgSendv(self, sel, frameSize, frame);
1458 - (id)receive:(DPMessage *)msg {
1459 SEL sel = [msg selector];
1460 marg_list origFrame = [msg arguments], frame = origFrame;
1461 unsigned int frameSize = [msg sizeOfArguments];
1462 const char *typedesc = [self typeEncodingForMessage:msg];
1467 unsigned argsCount = dp_getNumberOfArguments(typedesc);
1468 char *types = DPCopyArgumentTypesMap(typedesc, frame, [DPEnumerator class]);
1469 int *offsets = DPCopyOffsetsOfArguments(typedesc);
1470 unsigned numberOfIterations = 0, i;
1473 for (i = 2; i < argsCount; i++) {
1474 if (types[i] == _DP_ENUM_OBJ) {
1475 unsigned c = [dp_margGetObject(frame, offsets[i], DPEnumerator *) count];
1476 numberOfIterations = MAX(numberOfIterations, c);
1480 if (numberOfIterations == 0) {
1481 result = dp_msgSendv(self, sel, frame);
1485 dp_getReturnType(typedesc, &rt, 1);
1488 size_t fsize = [msg realFrameSize];
1489 frame = dp_marg_malloc(fsize);
1490 memcpy(frame, origFrame, fsize);
1493 for (i = 0; i < numberOfIterations - 1; ++i) {
1494 DPUpdateEnumereratedArguments(origFrame, frame, types, offsets, argsCount, YES);
1495 objc_msgSendv(self, sel, frameSize, frame);
1498 DPUpdateEnumereratedArguments(origFrame, frame, types, offsets, argsCount, YES);
1499 result = objc_msgSendv(self, sel, frameSize, frame);
1511 - (id)ifResponds:(DPMessage *)msg {
1512 if ([self respondsToSelector:[msg selector]])
1513 return [msg sendTo:self];
1519 @implementation NSString (DPHOMCompatibility)
1522 * Given a format string and an initial offset, this function builds a parital types description
1523 * from the given format string. For example, given offset of 8 and a format string "%@",
1524 * the result will be "@8" and the frame size (if not NULL) will be 12 (8 + sizeof(id)).
1525 * If startOffset is 0, the function assumes a method accepting a format string followed
1526 * by variable arguments, in which case it'll also take in account the self and _cmd parameters
1527 * of the method. Otherwise it's up to you to append them at the beginning of the description.
1528 * In any case, you must add the return type of the method at the begnning of the description
1529 * before passing it to anyone (otherwise it won't be a valid decscription).
1531 NSMutableString *DPTypeDescriptionForFormatString(NSString *str, unsigned startOffset, unsigned *frameSize) {
1532 // It's probably OK to hardcode the values of @encode() but let's do it the right way
1533 NSMutableString *description = startOffset == 0 ? [NSMutableString stringWithFormat:@"%s0" // self (offset 0,
1534 // assuming no struct return)
1536 "%s%u", // format string
1537 @encode(id), // @encode(self)
1538 @encode(SEL), // encode(_cmd)
1539 sizeof(id), // _cmd comes right after self
1540 @encode(id), // format string
1541 sizeof(id) + sizeof(SEL)]
1542 : [NSMutableString string];
1543 // I assume all constant string these days are constant CFStrings rather than NSConstantString,
1544 // so this *should* be a bit faster than repeatedly calling characterAtIndex: but I haven't
1545 // measured it. Ya ya, premature optimization, evil, whatever.
1546 CFStringInlineBuffer buff;
1547 unsigned i = 1, len = [str length];
1548 unsigned offset = startOffset == 0 ? sizeof(id) * 2 + sizeof(SEL) // self + _cmd + format
1552 CFStringInitInlineBuffer((CFStringRef)str, &buff, CFRangeMake(0, len));
1553 ch1 = CFStringGetCharacterFromInlineBuffer(&buff, 0);
1555 while ((ch2 = CFStringGetCharacterFromInlineBuffer(&buff, i))) {
1559 // Note that %@ can also mean a class (which has a different type encodign),
1560 // but id should be fine for everyone.
1561 [description appendFormat:@"%s%u", @encode(id), offset];
1562 offset += sizeof(id);
1568 // Even though the docs say %d is for "32 bit integer (long)"
1569 // we must explicitly use int32_t because OSX64 is LP64 so long
1570 // will actually be 64 bit and not 32.
1571 [description appendFormat:@"%s%u", @encode(int32_t), offset];
1572 offset += sizeof(int32_t);
1581 [description appendFormat:@"%s%u", @encode(uint32_t), offset];
1582 offset += sizeof(uint32_t);
1586 // Read the next character
1588 ch2 = CFStringGetCharacterFromInlineBuffer(&buff, i);
1591 [description appendFormat:@"%s%u", @encode(int16_t), offset];
1592 offset += sizeof(int16_t);
1598 [description appendFormat:@"%s%u", @encode(uint16_t), offset];
1599 offset += sizeof(uint16_t);
1609 // Read the next character
1611 ch2 = CFStringGetCharacterFromInlineBuffer(&buff, i);
1614 [description appendFormat:@"%s%u", @encode(int64_t), offset];
1615 offset += sizeof(int64_t);
1619 [description appendFormat:@"%s%u", @encode(uint64_t), offset];
1620 offset += sizeof(uint64_t);
1629 // Warning: if someone passes a float instead of double
1630 // this may cause an overflow somewhere (depends on how
1631 // you get our arguments from). How does NSString handle
1638 [description appendFormat:@"%s%u", @encode(double), offset];
1639 offset += sizeof(double);
1643 [description appendFormat:@"%s%u", @encode(unsigned char), offset];
1644 offset += sizeof(unsigned char);
1648 [description appendFormat:@"%s%u", @encode(unichar), offset];
1649 offset += sizeof(unichar);
1653 [description appendFormat:@"%s%u", @encode(char *), offset];
1654 offset += sizeof(char *);
1658 [description appendFormat:@"%s%u", @encode(unichar *), offset];
1659 offset += sizeof(unichar *);
1663 [description appendFormat:@"%s%u", @encode(void *), offset];
1664 offset += sizeof(void *);
1668 // No need to reuse this character as it can't be a
1669 // valid format specifier.
1672 // goto is *NOT* always evil!!
1676 // If we found a foramt specifier there's no need to repeat its
1677 // last character. Even worse, it may cause wrong behaviour.
1678 ch1 = CFStringGetCharacterFromInlineBuffer(&buff, ++i);
1688 *frameSize = offset + startOffset;
1690 if (startOffset == 0)
1691 // Apply the total length of the arguments frame as required by the runtime.
1692 [description insertString:[NSString stringWithFormat:@"%u", offset]
1698 // Support for initWithFormat:, stringByAppendingFormat:, stringWithFormat:,
1699 // appendFormat: and initWithFormat:locale:. Fun.
1700 - (const char *)typeEncodingForMessage:(DPMessage *)msg {
1701 SEL sel = [msg selector];
1703 if (sel == @selector(initWithFormat:) ||
1704 sel == @selector(stringByAppendingFormat:)||
1705 sel == @selector(stringWithFormat:))
1707 NSMutableString *desc = DPTypeDescriptionForFormatString(dp_margGetObject([msg arguments], sizeof(id) + sizeof(SEL),
1708 NSString *), 0, NULL);
1709 // Set the return type
1710 [desc insertString:[NSString stringWithCString:@encode(id)]
1712 // We return a C string that'll automatically get freed in the
1713 // next autorelease cycle, together with our mutable string.
1714 return [desc cStringUsingEncoding:NSASCIIStringEncoding];
1715 } else if (sel == @selector(appendFormat:)) {
1716 NSMutableString *desc = DPTypeDescriptionForFormatString(dp_margGetObject([msg arguments], sizeof(id) + sizeof(SEL),
1717 NSString *), 0, NULL);
1718 // Set the return type
1719 [desc insertString:[NSString stringWithCString:@encode(void)]
1721 // We return a C string that'll automatically get freed in the
1722 // next autorelease cycle, together with our mutable string.
1723 return [desc cStringUsingEncoding:NSASCIIStringEncoding];
1724 } else if (sel == @selector(initWithFormat:locale:)) {
1726 NSMutableString *desc = DPTypeDescriptionForFormatString(dp_margGetObject([msg arguments], sizeof(id) + sizeof(SEL),
1728 sizeof(id) * 2 + sizeof(SEL),
1730 // Build the begining of our description which includes (by order):
1731 // the return type, total frame size, self + offset, _cmd + offset,
1732 // format string + offset, locale variable + offset
1733 [desc insertString:[NSString stringWithFormat:@"%s%u%s0%s%d", @encode(id), frameSize,
1734 @encode(id), @encode(SEL), sizeof(id) + sizeof(SEL)]
1736 // We return a C string that'll automatically get freed in the
1737 // next autorelease cycle, together with our mutable string.
1738 return [desc cStringUsingEncoding:NSASCIIStringEncoding];
1741 return [super typeEncodingForMessage:msg];
1747 @implementation NSObject (DPIntrospectionAdditions)
1749 - (const char *)typeEncodingForSelector:(SEL)sel {
1750 Method m = dp_getMethod(self, sel);
1751 return m ? method_getTypeEncoding(m) : NULL;
1754 - (const char *)typeEncodingForMessage:(DPMessage *)msg {
1755 return [self typeEncodingForSelector:[msg selector]];
1760 * OK folks, here is a really cool voodoo.
1761 * As you probably know, the runtime's method lookup for a given class
1762 * scans its methods list and all its super classes, and if it can't find
1763 * a method it tries the instance methods list of the class's base class
1764 * (ya ya, I know it's weird).
1765 * The following code hacks around this behaviour and gets a nicer effect:
1766 * after the class and its super classes couldn't provide a method for
1767 * the selector, it looks up on the instance methods list of our class
1768 * and any of its super class, so the base class's implementation is invoked
1769 * only if non of the direct super classes could provide one.
1770 * This allows us to override only the instance methods in our custom NSObject
1771 * subclass, and still allow proper result when messaging our class.
1772 * See above how it's done with NSString.
1774 + (const char *)typeEncodingForSelector:(SEL)sel {
1775 return (void *)(method_getImplementation(class_getInstanceMethod(self, _cmd))(self, _cmd, sel));
1778 + (const char *)typeEncodingForMessage:(DPMessage *)msg {
1779 return (void *)(method_getImplementation(class_getInstanceMethod(self, _cmd))(self, _cmd, msg));