// // NSDictionaryHOMAdditions.m // HigherOrderMessaging // // Created by Ofri Wolfus on 24/02/06. // Copyright 2006 Ofri Wolfus. All rights reserved. // #import "NSDictionaryHOMAdditions.h" #import "NSArrayHOMAdditions.h" #import "HOMCArrayIterations.h" #import "HOMUtilities.h" #import "HOMIteratedArgument.h" #include #include @implementation NSDictionary (NewHOMIteration) - (id)collect:(Message *)msg { SEL sel = [msg selector]; marg_list origArgs = [msg arguments]; unsigned argsSize = [msg argumentsSize]; marg_list args = malloc(malloc_size(origArgs)); unsigned argCount = hom_getNumberOfArguments(sel); unsigned i, count = [self count]; Class iteratedArgCls = [HOMIteratedArgument class]; char *types = calloc(argCount, sizeof(char)); const char *t = [msg types]; id *objects = calloc(count, sizeof(id)), *keys = calloc(count, sizeof(id)); CFMutableDictionaryRef result = CFDictionaryCreateMutable(kCFAllocatorDefault, count, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // Get all our objects and keys CFDictionaryGetKeysAndValues((CFDictionaryRef)self, (const void **)keys, (const void **)objects); // Copy the arguments list since we're going to modify it memcpy(args, origArgs, malloc_size(args)); // Scan the arguments of our message and find instances of HOMIteratedArgument. // Our types array will mark the places of these with the letter 'z' so we don't have to check again. // This loop also finds the largest number of arguments that needs to be iterated, and stores it in count. for (i = 2U; i < argCount; i++) { types[i] = *hom_getArgumentFromTypes(t, i); if (types[i] == _C_ID) { if ([marg_getValue(args, hom_getArgumentOffsetFromTypes(t, i), id) isKindOfClass:iteratedArgCls]) types[i] = 'z'; // This is our special mark for an iterated argument } } // Loop through all objects in all our iterated arguments, update our args list, and resend it for (i = 0U; i < count; i++) { unsigned j; // Update the remaining arguments in our list for (j = 2U; j < argCount; j++) { if (types[j] == 'z') { int offset = hom_getArgumentOffsetFromTypes(t, j); HOMIteratedArgument *arg = marg_getValue(origArgs, offset, HOMIteratedArgument *); marg_setValue(args, offset, id, [arg nextObject]); // When there are no arguments left for this argument iterator, we mark its type as id // so we'll skip it in the next run. if ([arg isFinished]) types[j] = _C_ID; } } // Send the updated arguments list //addObject(result, @selector(addObject:), objc_msgSendv(objects[i], sel, argsSize, args)); CFDictionaryAddValue(result, keys[i], objc_msgSendv(objects[i], sel, argsSize, args)); } // Clean up free(types); free(args); free(objects); free(keys); return [(id)result autorelease]; } - (id)getObjectsThatResponded:(BOOL)resultVal forMessage:(Message *)firstMsg :(va_list)additionalMessages { Message *msg; unsigned i, count = [self count]; CFMutableDictionaryRef result = NULL; if (count > 0U) { va_list messages; id *objects = calloc(count, sizeof(id)); id *keys = calloc(count, sizeof(id)); result = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // Get all objects and keys CFDictionaryGetKeysAndValues((CFDictionaryRef)self, (const void **)keys, (const void **)objects); // Loop through all our objects, and send it all the messages passed to us. // All messages except the last are assumed to return id, and the last one should return a BOOL // (although any integer value will work). for (i = 0U; i < count; i++) { // Send the first message to our object and store the result. // Since id is a pointer, and a pointer can hold BOOL values, // we're fine even if this is the only message. id obj = objc_msgSendv(objects[i], [firstMsg selector], [firstMsg argumentsSize], [firstMsg arguments]); // Send every other messages to the previous result. // We assume the result is an object. If it's not, bad things will happen, but that's not our fault. // NOTE: We don't verify that the receiver responds to the actual message like -select does // which give us a nice speed boost, but may also cause an exception to be thrown. // TODO: Cache the IMPs of -[Message selector], -[Message argumentsSize] and -[Message arguments]. va_copy(messages, additionalMessages); while ((msg = va_arg(messages, Message *))) obj = objc_msgSendv(obj, [msg selector], [msg argumentsSize], [msg arguments]); va_end(messages); // Check if the final result equals the result value we're looking for. // If true, we'll add the original object (and its key) to our results dictionary. if ((BOOL)(int)obj == resultVal) CFDictionarySetValue(result, keys[i], objects[i]); } // Clean up free(objects); free(keys); va_end(additionalMessages); // Make our return value pretty if (CFDictionaryGetCount(result) == 0) { CFRelease(result); result = nil; } } return [(id)result autorelease]; } - (id)selectWhere:(Message *)firstMessage, ... { va_list va; DPAssert(firstMessage != nil, @"The passed message must not be nil"); va_start(va, firstMessage); return [self getObjectsThatResponded:YES forMessage:firstMessage :va]; } - (id)rejectWhere:(Message *)firstMessage, ... { va_list va; DPAssert(firstMessage != nil, @"The passed message must not be nil"); va_start(va, firstMessage); return [self getObjectsThatResponded:NO forMessage:firstMessage :va]; } // Note this is getObject.. and not getObjets (a single object!) - (id)getObjectThatResponded:(BOOL)resultVal forMessage:(Message *)firstMsg :(va_list)additionalMessages key:(id *)key { Message *msg; unsigned i, count = [self count]; id result = nil; if (count > 0U) { va_list messages; id *objects = calloc(count, sizeof(id)); id *keys = calloc(count, sizeof(id)); // Get all objects and keys CFDictionaryGetKeysAndValues((CFDictionaryRef)self, (const void **)keys, (const void **)objects); // Loop through all our objects, and send it all the messages passed to us. // All messages except the last are assumed to return id, and the last one should return a BOOL // (although any integer value will work). for (i = 0U; i < count; i++) { // Send the first message to our object and store the result. // Since id is a pointer, and a pointer can hold BOOL values, // we're fine even if this is the only message. id obj = objc_msgSendv(objects[i], [firstMsg selector], [firstMsg argumentsSize], [firstMsg arguments]); // Send every other messages to the previous result. // We assume the result is an object. If it's not, bad things will happen, but that's not our fault. // NOTE: We don't verify that the receiver responds to the actual message like -select does // which give us a nice speed boost, but may also cause an exception to be thrown. // TODO: Cache the IMPs of -[Message selector], -[Message argumentsSize] and -[Message arguments]. va_copy(messages, additionalMessages); while ((msg = va_arg(messages, Message *))) obj = objc_msgSendv(obj, [msg selector], [msg argumentsSize], [msg arguments]); va_end(messages); // Check if the final result equals the result value we're looking for. // If true, we'll add the original object (and its key) to our results dictionary. if ((BOOL)(int)obj == resultVal) { result = objects[i]; if (key) *key = keys[i]; break; } } // Clean up free(objects); free(keys); va_end(additionalMessages); } return result; } - (id)selectSingleWhere:(Message *)firstMessage, ... { va_list va; id key = nil, obj = nil; DPAssert(firstMessage != nil, @"The passed message must not be nil"); va_start(va, firstMessage); obj = [self getObjectThatResponded:YES forMessage:firstMessage :va key:&key]; if (obj) return [NSDictionary dictionaryWithObject:obj forKey:key]; return nil; } - (id)rejectSingleWhere:(Message *)firstMessage, ... { va_list va; id key = nil; DPAssert(firstMessage != nil, @"The passed message must not be nil"); va_start(va, firstMessage); [self getObjectThatResponded:YES forMessage:firstMessage :va key:&key]; if (key) { NSMutableDictionary *dict = [self mutableCopy]; [dict removeObjectForKey:key]; return [dict autorelease]; } return nil; } - (id)each { return [[self allValues] each]; } @end